2026年软件开发工程师高频面试题包含详细解答_第1页
2026年软件开发工程师高频面试题包含详细解答_第2页
2026年软件开发工程师高频面试题包含详细解答_第3页
2026年软件开发工程师高频面试题包含详细解答_第4页
2026年软件开发工程师高频面试题包含详细解答_第5页
已阅读5页,还剩70页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

软件开发工程师高频面试题

【精选近三年60道高频面试题】

【题目来源:学员面试分享复盘及网络真题整理】

【注:每道题含高分回答示例+避坑指南】

1.进程间通信方式有哪些?在RPC框架中底层主要使用哪种,为什么?(基本必考|背诵即

可)

2.TCP的三次握手和四次挥手原理是什么?如果服务端出现大量CLOSE_WAIT状态怎么排

查?(极高频|考察实操)

3.介绍一下Redis的几种基础数据结构,底层分别是怎么实现的,Zset为什么不用红黑树而

用跳表?(极高频|重点准备)

4.MySQL的InnoDB引擎为什么默认使用B+树而不是B树或Hash索引?(基本必考|背诵即

可)

5.什么是MVCC?它是如何解决幻读的?在RR隔离级别下彻底解决幻读了吗?(常问|需深

度思考)

6.讲讲你熟悉编程语言的垃圾回收机制,三色标记法是如何解决漏标问题的?(基本必考|

重点准备)

7.什么是CAS?在并发编程中CAS会产生哪些问题?如何解决ABA问题?(极高频|背诵即

可)

8.消息队列如何保证消息的不丢失和不重复消费?(基本必考|需深度思考)

9.什么是分布式锁?Redis和Zookeeper实现分布式锁的底层原理区别是什么,分别适用什

么场景?(极高频|重点准备)

10.挑一个你觉得最有技术含量的项目,画一下它的整体架构图,并说说你负责了哪几个核心

模块。(学员真题|考察软实力)

11.在你的项目中,为什么选择使用这套技术栈?当初有对比过其他同类方案吗?(需深度

思考|反复验证)

12.你在项目中遇到过最难的一个Bug是什么?当时你的排查思路是怎样的,最后怎么解决

的?(基本必考|重点准备)

13.如果你的项目流量突然暴增10倍,你觉得现在的架构哪个组件会最先扛不住?你会如何

演进?(常问|需深度思考)

14.讲一下你们项目中的权限控制是怎么做的?如果是微服务架构,如何实现跨服务的统一鉴

权?(学员真题|考察实操)

15.你的项目中是如何保证数据库和缓存的数据一致性的?如果采用延迟双删依然出现不一致

怎么处理?(极高频|重点准备)

16.项目中有没有用到多线程或协程去优化业务逻辑?具体是怎么落地的,遇到了什么坑?

(网友分享|考察实操)

17.你们的系统是如何处理幂等性问题的?请举个具体的业务场景进行说明。(基本必考|重

点准备)

18.遇到过超大分页查询的场景吗?比如深度分页遇到性能瓶颈,你是如何优化SQL或改变方

案的?(常问|考察实操)

19.讲一下你在项目中做过最成功的一次性能优化(比如接口响应慢或CPU利用率异常),

优化前后的数据对比是怎样的?(极高频|需深度思考)

20.分布式事务在你的项目中有落地吗?具体使用的是2PC、TCC还是最终一致性方案,为

什么这么选?(常问|重点准备)

21.在你们的秒杀/抢购等高并发场景中,是如何防止超卖的?具体代码层面是怎么实现的?

(极高频|需深度思考)

22.系统的核心接口有没有做限流和降级?使用的是什么算法,如何根据真实业务去定限流的

阈值?(常问|考察实操)

23.你在项目中负责的模块,每天产生的数据量有多大?如果单表数据量达到千万级,你会怎

么做分库分表规划?(学员真题|重点准备)

24.分库分表之后,跨库的Join查询和全局的分页查询你们是怎么解决的?(常问|需深度思

考)

25.在微服务架构中,你们的服务拆分粒度是根据什么划分的?有没有遇到过服务边界划分不

清导致的问题?(网友分享|需深度思考)

26.项目里怎么做日志收集和链路追踪的?如果一个请求经过了5个微服务最后报错了,你怎

么快速定位出问题的节点?(基本必考|考察实操)

27.如果需要你接手一个完全没有文档的祖传代码模块,并且要求一周内上线新功能,你如何

推进?(考察抗压|考察软实力)

28.在开发过程中,有没有因为需求频繁变更导致代码架构腐化的经历?你是如何进行代码重

构的?(反复验证|需深度思考)

29.描述一次你和产品经理或者测试在需求理解上产生严重分歧的经历,最后是如何沟通解决

的?(考察软实力|学员真题)

30.你们团队对代码覆盖率的要求是多少?平时写单元测试吗?谈谈你对TDD的真实看法。

(常问|考察实操)

31.线上突然报警CPU使用率达到100%,你要如何在5分钟内定位到是哪一行业务代码引起

的?说说具体的排查步骤和命令。(极高频|考察实操)

32.生产环境突发OOM,你们的应急处理流程是什么?怎么把Dump文件导出来分析?(基

本必考|考察实操)

33.Redis线上集群突然出现大量热点Key导致单节点负载过高,你们怎么快速止血和彻底根

除?(极高频|重点准备)

34.用户反馈某个核心接口偶尔会超时,但大多数时候正常,这通常是什么原因导致的?你会

怎么去排查?(常问|需深度思考)

35.线上数据库突发大量死锁报警,你会通过什么系统表和命令去排查导致死锁的源头SQL?

(学员真题|考察实操)

36.如果生产环境的Kafka集群突然宕机了一个Broker,会不会丢数据?消费端同时会发生什

么反应?(网友分享|重点准备)

37.凌晨业务低峰期,告警系统疯狂报错说数据库连接池满了,你会从哪些方向去排查这个异

常现象?(反复验证|考察实操)

38.线上发布新版本后,突然发现由于某条SQL没有走索引导致全表扫描,数据库被打挂了,

第一时间你怎么恢复业务?(极高频|考察抗压)

39.微服务中某个下游服务响应变慢,导致上游大量线程阻塞,雪崩效应已经开始,目前的止

损方案有哪些?(基本必考|需深度思考)

40.Redis里有个超大Key删不掉,或者一删就阻塞整个实例导致业务抖动,线上怎么安全处

理这种大Key的清理?(常问|考察实操)

41.有用户反馈他的账号被莫名其妙登出或者数据串户了,你怀疑是ThreadLocal内存泄漏引

起的并发污染,怎么写代码验证?(学员真题|需深度思考)

42.容器在运行过程中频繁发生OOMKilled,但是宿主机物理内存还有很多,这往往是哪里

配置出了问题?(常问|考察实操)

43.线上K8s集群中某个Pod总是处于CrashLoopBackOff状态无限重启,排查标准流程是怎样

的?(网友分享|考察实操)

44.发现线上一台服务器TIME_WAIT状态的TCP连接过多把可用端口耗尽了,怎么在Linux系

统层面快速优化配置?(反复验证|重点准备)

45.业务高峰期MQ消息出现大量积压,消费者消费速度远远跟不上生产速度,怎么快速在不

改代码的情况下提升消费能力?(极高频|需深度思考)

46.线上环境的HTTPS证书突然过期导致核心服务全部阻断,在紧急更新证书后,为什么有

些系统内的旧长连接还是报错?(学员真题|考察抗压)

47.如果发现线上的慢SQL并不是由于缺少索引导致,而是因为优化器选错了执行计划,你怎

么在生产环境强制纠正?(常问|考察实操)

48.生产环境的Elasticsearch集群发生了“脑裂”现象,你当时的紧急处理方案是什么,事后做

了哪些防御机制?(反复验证|重点准备)

49.第三方API接口突然大面积超时报错,但我们系统没做降级导致大量HTTP请求一直夯住

打满线程池,除了重启还能怎么做?(考察抗压|需深度思考)

50.发现应用服务器的磁盘IO利用率达到了100%,导致整个系统响应极慢,用什么排查工具

能查出是哪个线程在疯狂读写磁盘?(极高频|考察实操)

51.缓存穿透引发的线上数据库即将宕机,这时候给数据库疯狂增加只读从库还有用吗?正确

的抢救步骤是什么?(基本必考|需深度思考)

52.线上的分布式定时任务突然在一个周期内并发执行了两次,导致给同一个用户重复发了退

款,怎么去排查这个并发调度失控的问题?(学员真题|考察抗压)

53.面对一起严重的线上P0级生产事故,作为解决完Bug的当事人,你会从哪几个维度去写这

份复盘报告来避免下次再犯?(常问|考察软实力)

54.过去一年里,你关注过哪些软件工程领域的新技术或者新语言框架?有没有尝试运用到个

人项目或公司内部工具中?(常问|考察软实力)

55.随着云原生和Serverless的发展,底层基础设施逐渐被云厂商屏蔽,你觉得未来的后端开

发工程师核心竞争力会被削弱吗?(需深度思考|网友分享)

56.你如何看待AI编码助手对日常开发工作流的影响?你们公司有推行类似工具吗,它带来的

最大隐患是什么?(学员真题|需深度思考)

57.如果现在让你从零设计一个千万级日活的短视频推荐流后端架构,你会怎么做宏观上的技

术选型?(重点准备|需深度思考)

58.相比于传统的微服务架构,目前流行的ServiceMesh到底解决了跨语言服务治理的什么

痛点?(常问|重点准备)

59.在你的职业技术路线规划中,未来三年你是偏向于在特定中间件/底层原理深挖成为技术

专家,还是转型做业务架构师?为什么?(考察软实力|反复验证)

60.我问完了,你有什么想问我的吗?(面试收尾|面试收尾)

【软件开发工程师】高频面试题深度解答

Q1:进程间通信方式有哪些?在RPC框架中底层主要使用哪种,为什么?

❌不好的回答示例:

进程间通信方式有管道、信号量、消息队列、共享内存和Socket等。RPC底层主要

使用Socket通信,因为RPC是远程过程调用,肯定涉及跨机器了,跨机器就只能用

TCP协议通过Socket来发数据。TCP比较稳定不容易丢包,所以大家一般都选它作

为底层协议。

为什么这么回答不好:

1、完全是教科书式的死记硬背,没有结合高并发业务场景分析底层通信逻辑。

2、对RPC底层实现认知极其扁平,忽略了除了TCP之外内部也有使用HTTP/2或者

特定应用层协议的场景。

3、缺乏系统性思考,没有提到任何性能优化的关键点和底层序列化对效率的决定

性影响。

高分回答示例:

我通常的逻辑是根据通信的物理边界和性能要求来判断通信机制的选型,单纯罗列

底层八股文解决不了线上的性能瓶颈。在实际评估底层机制时,必须兼顾网络吞吐

量和资源开销的平衡。

1、在处理单机多进程或容器化部署下的高频本地通信时我会优先考虑基于mmap的

共享内存方案来替代传统的管道,这能直接省去内核态到用户态的两次数据拷贝开

销从而提升单机响应速度。

2、在构建跨网络微服务调用的RPC框架时我会直接选择基于TCP/IP的Socket通

信,并在应用层引入Netty这种高性能NIO框架,通过多路复用技术和直接内存池化

管理来承载几十万级别的并发连接。

3、在具体的协议栈调优阶段我会放弃JDK原生的序列化机制,改用Protobuf或

Hessian,确保在Socket传输时的带宽占用最小化,同时调整TCP层的Nagle算法

或KeepAlive参数来降低网络延迟极限压榨硬件性能。

在完成这些底层调优并上线后,我都会将网络通信的P99延迟指标、I/O线程组的负

载情况接入到监控看板中,设定高水位报警阈值,以便在网络发生剧烈抖动时能够

第一时间通过限流降级来防止全链路雪崩。

Q2:TCP的三次握手和四次挥手原理是什么?如果服务端出现大量

CLOSE_WAIT状态怎么排查?

❌不好的回答示例:

三次握手就是客户端发SYN,服务端回SYN-ACK,客户端再回ACK就建连了。四

次挥手就是互相发FIN和ACK断开。服务端出现大量CLOSE_WAIT说明在等待关

闭,一般是因为网络不好或者机器卡了,直接把服务器重启一下或者杀掉进程就能

解决连接积压的问题。

为什么这么回答不好:

1、理论脱离实际操作,完全没有讲透CLOSE_WAIT状态在TCP状态机中出现的本

质原因。

2、把排查思路粗暴地归结为重启机器,这是非常缺乏真实生产环境保护意识的危

险操作。

3、没有体现出利用系统底层工具链去定位异常端口和业务代码逻辑的能力。

高分回答示例:

面对生产环境的TCP连接异常,我通常的逻辑是坚决不能盲目重启业务,而是顺着

TCP状态机的流转机制从系统层向应用层做穿透式排查。CLOSE_WAIT的本质就

是客户端主动关闭了连接,但服务端的代码却没有执行close()方法向对方发送FIN

包。

1、我会第一时间登录生产宿主机,通过netstat-anp|grepCLOSE_WAIT命令

定位出堆积此状态的具体端口号和对应的业务进程PID,确认是哪个服务卡住了。

2、我会拿着进程信息去排查业务代码中涉及外部网络依赖的代码块,重点排查

HTTP/RPC调用的finally块中是否遗漏了关闭Response流和连接对象的逻辑代

码。

3、如果代码逻辑本身有close()调用,我会立刻通过jstack导出当前服务的线程快

照进行分析,确认是否是因为数据库死锁或者第三方接口假死导致处理该请求的工

作线程全部被夯住,根本没有机会执行到释放连接的那行代码。

排查并修复这段逻辑阻塞的Bug后,我会在上线前强制规定所有向外发起的网络调

用必须设定严格的ConnectTimeout和ReadTimeout,并通过CI/CD的静态代码

扫描工具拦截掉所有未在finally中规范清理资源的不安全代码。

Q3:介绍一下Redis的几种基础数据结构,底层分别是怎么实现的,Zset为什

么不用红黑树而用跳表?

❌不好的回答示例:

Redis有String、List、Hash、Set、Zset五种结构。Zset底层是用跳表实现的,

能用来做排行榜。它不用红黑树是因为红黑树太复杂了,跳表写起来比较简单,而

且跳表查找速度也很快,所以Redis的作者最后就选择了跳表来做Zset的底层数据

结构。

为什么这么回答不好:

1、对数据结构的回答停留在最浅显的表层,完全没有涉及压缩列表到哈希表的动

态转换机制。

2、分析Zset底层选型时过于主观,没有从范围查询效率、内存连续性和并发加锁

粒度去对比红黑树。

3、没有结合Redis作为内存数据库对内存极其敏感的业务痛点去深度展开。

高分回答示例:

在进行Redis存储选型时,我通常的逻辑是不仅看它的外层结构,还要推演它在数

据量膨胀时底层编码格式的动态转换行为。这是评估内存占用和性能衰减的绝对前

提。

1、我会在存储小规模对象时充分利用Redis底层把List、Hash、Zset默认初始化

为ZipList的特性,通过紧凑的连续内存布局来极大地节省服务器内存开销,直到元

素数量或大小突破阈值才会让其退化为双向链表或哈希表。

2、在评估Zset这种需要同时满足高效单点查询和范围查询的结构时,我会深入对

比跳表和红黑树,跳表通过多层级索引将区间查询的复杂度维持在极其稳定的

O(logN),而红黑树在提取范围数据时需要通过中序遍历产生大量回溯操作导致效

率低下。

3、在实际的C++底层实现层面,我会考虑到跳表插入和删除节点只需要修改局部相

邻节点的指针,相比于红黑树每次插入都要进行极其消耗性能的全树旋转和颜色重

分配,跳表的工程落地成本和运行时的缓存命中率明显优于红黑树。

在应用Zset处理线上积分排行榜业务时,最核心的风险点是底层结构的突变。我会

严格监控Zset中单个Key包含的元素总量,一旦有超过一万个元素的趋势,我会立

刻在代码层进行拆分或者缩容,防止超大Key引发主线程操作阻塞。

Q4:MySQL的InnoDB引擎为什么默认使用B+树而不是B树或Hash索引?

❌不好的回答示例:

Hash索引只能做等值查询不能做范围查询,所以不用。B树的每个节点都存了数

据,这样树的高度会变高,查询速度慢。B+树只有叶子节点存数据,而且叶子节点

还有指针连在一起,这样树就比较矮,查询速度快,所以InnoDB就选了B+树来做

默认索引。

为什么这么回答不好:

1、仅陈述了理论结论,没有结合磁盘I/O的物理特性进行系统化的量化解释。

2、缺乏对MySQL“页”这一核心存储概念的认知,没有说明树高与磁盘操作次数之

间的直接关系。

3、缺少在实际业务中如何利用B+树特性的实操经验分享。

高分回答示例:

在进行关系型数据库表结构设计时,我通常的底层逻辑是把一切性能优化点建立在

减少磁盘I/O次数的基础上。B+树之所以成为工业级标准,正是因为它在机械磁盘

寻道瓶颈和内存容量限制之间找到了最佳平衡点。

1、在评估单次查询的物理开销时,我清楚B+树非叶子节点不存储真实数据只存储

索引键这一特性,能让InnoDB默认的16KB页装载几百上千个索引键,从而将千万

级数据表的树高死死压制在3到4层,将磁盘寻道次数降至个位数。

2、在处理交易流水这种高频的范围查询业务时,我会充分利用B+树叶子节点之间

形成的双向循环链表特性,一旦在树上定位到起始主键,直接顺着链表顺序进行磁

盘顺序读取,彻底避免了B树在范围查询时必须进行的中序遍历和全树跨层跳跃。

3、在涉及高并发写操作时,我深知Hash索引不仅完全丧失了范围过滤能力,还会

引发大量哈希冲突导致索引降级,而B+树通过页分裂机制虽然有一定开销,但能保

证整体查询性能的极度稳定。

在实际的业务开发规范中,为了防止B+树因页分裂产生大量的碎片化磁盘I/O并导

致树高失控,我强制要求所有核心业务表必须使用自增ID作为主键,通过顺序追加

写入的模式将B+树的插入性能压榨到极限。

Q5:什么是MVCC?它是如何解决幻读的?在RR隔离级别下彻底解决幻读了

吗?

❌不好的回答示例:

MVCC就是多版本并发控制,通过加版本号来实现读写不冲突。它在RR隔离级别下

通过生成ReadView来解决幻读问题,只要别的事务插入的数据在我的ReadView

之后,我就看不到它。所以在RR隔离级别下,幻读已经被MVCC完全彻底地解决掉

了。

为什么这么回答不好:

1、对MVCC底层依赖的UndoLog机制只字未提,原理解释极度粗糙。

2、给出了完全错误的绝对性结论,在RR级别下单纯依靠MVCC并不能彻底解决幻

读。

3、缺乏在并发场景中对“快照读”和“当前读”这两大核心业务操作的区分说明。

高分回答示例:

我通常把MVCC看作是数据库在高并发下实现无锁化读的核心武器,但在处理严苛

的资金或库存扣减业务时,必须精准区分它的适用边界。盲目迷信MVCC能解决所

有并发问题是导致线上数据错乱的直接元凶。

1、在排查普通的只读慢查询时,我清楚这是MVCC的用武之地,它通过每行数据

隐藏的事务ID字段配合UndoLog形成版本链,利用当前事务启动时生成的Read

View规则去对比版本号,实现了业务代码无需加锁就能读取到一致性的快照数据。

2、在RR隔离级别下处理普通Select这种快照读业务时,我会向团队明确幻读确实

被MVCC的ReadView机制屏蔽了,因为整个事务期间复用同一个快照,外部事务

插入的新数据对其完全透明不可见。

3、在涉及Update或Select...ForUpdate这种必须读取最新数据的当前读业务

时,我会警告团队MVCC在此刻会失效并重新引发幻读,我必须在代码逻辑中手动

介入,依靠InnoDB底层的Next-KeyLock对数据行及其两边的间隙加锁来强制阻断

外部事务的插入行为。

经过这些底层的权衡复盘,我会在代码CR阶段严格核查所有涉及资金对账的事务逻

辑,一旦发现使用了当前读但没有命中唯一索引导致Next-KeyLock退化为锁全表

的死锁隐患时,我会直接将其打回并要求重构SQL的过滤条件。

Q6:讲讲你熟悉编程语言的垃圾回收机制,三色标记法是如何解决漏标问题

的?

❌不好的回答示例:

Java的垃圾回收主要是回收堆里的不用对象。三色标记法把对象分成白、灰、黑三

种颜色,黑色是扫过的,灰色是没扫完的,白色是没扫的垃圾。如果在并发扫的时

候,黑色对象又引用了白色对象,就会出现漏标。解决漏标就是重新扫描一下或者

用写屏障把它记录下来。

为什么这么回答不好:

1、对写屏障技术的回答过于笼统,没有点出具体语言或回收器(如CMS和G1)底

层实现算法的核心差异。

2、没有阐述漏标产生的两个绝对必要条件,导致整个逻辑推导缺乏严密性。

3、没有结合生产环境中的STW停顿时间或内存突刺现象去谈垃圾回收的工程意

义。

高分回答示例:

在排查线上接口突发的高延迟抖动时,我通常的首要逻辑就是去抓取并分析GC日

志。熟悉垃圾回收不能只停留在八股文,必须搞懂并发标记阶段底层算法由于应用

线程同步运行而导致的指针错乱问题。

1、在梳理三色标记算法的理论漏洞时,我非常清楚只有当黑色对象突然引用了白

色对象,且原本指向该白色对象的灰色对象断开了引用这两个条件同时满足时,才

会发生致命的漏标导致存活对象被误删。

2、在使用老年代回收器CMS处理线上系统时,我会重点关注它采用的增量更新策

略,即通过底层写屏障拦截黑色向白色的赋值操作,强行把这个黑色对象记录下

来,在重新标记阶段引发STW并将其作为灰色节点重新向下扫描一次来防止误杀。

3、在将大规模集群迁移到G1回收器后,我会利用它采用的SATB策略来压制延

迟,它在灰色对象断开白色引用时触发写屏障拦截,直接把这个白色对象保留下来

视为存活,宁可产生一定的浮动垃圾也要换取重新标记阶段极短的停顿时间。

通过对这些底层标记算法差异的深刻理解,一旦生产环境的可用内存阈值频繁触发

由于浮动垃圾导致的ConcurrentModeFailure或FullGC退化,我会立刻通过调

整并发标记启动的堆使用率参数或规范大对象的创建生命周期来恢复系统的健康水

位。

Q7:什么是CAS?在并发编程中CAS会产生哪些问题?如何解决ABA问题?

❌不好的回答示例:

CAS就是比较和交换,是一种乐观锁。先比较内存里的值和期望的值一不一样,一

样就换成新值。CAS的问题是自旋时间长会消耗CPU,还有就是ABA问题。解决

ABA问题就是加一个版本号,每次改数据的时候版本号加一,这样哪怕值变成了A

又变成B又变回A,也能通过版本号认出来。

为什么这么回答不好:

1、仅停留在概念解释的层面,没有列举出真实业务中CAS的高频使用场景和替代

方案。

2、对于自旋导致CPU飙高的危害缺乏实际解决思路,只提出了问题没有给出对

策。

3、对ABA问题的严重性没有结合具体的业务漏洞(如资金流转或节点替换)进行

风险说明。

高分回答示例:

在处理高并发且读多写少的业务模块时,我通常的逻辑是尽量规避操作系统内核级

的互斥锁上下文切换,直接采用基于CPU硬件指令的CAS无锁编程来榨干机器性

能。但这种乐观锁在极端高压下会暴露出一系列严重的工程隐患。

1、在实现本地内存计数器或状态机流转时我会首选CAS操作,但在流量峰值期,

我会通过APM监控到多线程争抢失败导致无限自旋,从而瞬间吃满服务器CPU资

源,针对这种情况我会直接在代码里设置最大自旋失败次数,超过后强制挂起线程

或降级为AQS阻塞锁。

2、在操作复杂业务对象时,我深知基础的CAS只能保证单一共享变量的原子性,

当遇到需要同时更新订单状态和账户余额多个变量时,我会果断放弃底层CAS,改

用ReentrantLock或者把多个变量封装进一个对象里再使用AtomicReference来保

证整体原子性。

3、在处理具有回退逻辑的核心交易链路时,最致命的风险点是ABA引发的逻辑欺

骗,例如库存数字被扣减后又被补偿回退,纯CAS会误以为没人操作过而直接放

行,我会强制在业务主表中增加递增的Version版本号字段,将判定条件从对比单

值升级为严格对比值与版本号的复合维度。

线上压测结束后,为了进一步优化CAS在极高并发下的性能瓶颈,我会在特定场景

下把普通的AtomicInteger替换为LongAdder,通过分散热点数据的思想让多个线

程去修改各自独立的变量副本,最终再做合并,从而彻底终结底层自旋冲突。

Q8:消息队列如何保证消息的不丢失和不重复消费?

❌不好的回答示例:

保证不丢失就在发消息的时候等MQ回个确认,没收到就重试。消费的时候也是处

理完了再回确认。保证不重复消费就是把处理过的消息ID存到Redis或者数据库

里,每次消费前先查一下这个ID有没有,有的话就不处理了。这样就能保证消息既

不丢也不重复了。

为什么这么回答不好:

1、回答过于宽泛,没有明确切分生产端、Broker服务端和消费端这三条独立且完

整的防线。

2、没有考虑到Broker集群宕机或主从切换时引发的数据截断问题和底层刷盘策

略。

3、防重逻辑只停留在查询层面,没有提到高并发下查询与写入操作的原子性漏

洞。

高分回答示例:

我通常的逻辑是绝不相信网络的稳定性和组件的单点可靠性,必须将消息的生命周

期严格切割为生产端、Broker服务端和消费端三段,在每一个可能发生宕机的断点

上做冗余防御。

1、在生产端我会开启事务消息或同步阻塞式的ACK确认机制,强制要求发送线程

必须收到MQ集群的成功回执才推进业务流,如果遇到网络超时,我会通过本地重

试机制配合持久化的定时任务异常补偿表来做最终的兜底发送。

2、在排查Broker服务端可能的数据蒸发风险时,我会直接审查集群的底层配置文

件,坚决摒弃性能极高但不安全的异步刷盘和单节点机制,强制要求改为同步刷盘

并且配置主从节点同步复制成功后才对生产端返回ACK,用极小的延迟换取金融级

的可靠性。

3、在最棘手的消费端防重场景中,我绝对排斥“先查后写”这种存在并发竞态漏洞的

弱校验,我会直接在核心业务表所在库建立一张带唯一索引的MessageId防重表,

将业务逻辑变更和写入防重记录打包在同一个本地事务中强制执行。

在整套防重防丢体系上线运行后,如果遇到极端情况下消费堆积导致Redis或数据

库防重层压力激增,我会引入布隆过滤器在内存层做一次前置的极速粗筛,将大量

明确已经消费过的重复报文直接丢弃,彻底保护后端存储的磁盘I/O资源。

Q9:什么是分布式锁?Redis和Zookeeper实现分布式锁的底层原理区别是什

么,分别适用什么场景?

❌不好的回答示例:

分布式锁就是控制多个不同机器上的服务不一起去抢同一个资源的工具。Redis是

用setnx加超时时间来实现的,速度特别快,但是如果超时了业务还没跑完就麻烦

了。Zookeeper是用临时节点加监听器实现的,就算宕机会自动释放锁。Redis适

合要求高的,ZK适合要求稳的。

为什么这么回答不好:

1、对Redis锁超时未完成业务的致命痛点只提出了问题,没有提到主流的

Redisson看门狗续期解决方案。

2、对ZK底层的临时“顺序”节点机制表述不完整,没有说明它是如何避免惊群效应

的。

3、缺乏基于CAP理论(AP与CP架构)对这两种中间件选型的根本性技术思考。

高分回答示例:

在进行微服务架构的并发控制选型时,我通常的首要逻辑是先用CAP定理去给业务

场景定性。分布式锁的核心并不是锁本身,而是底层协调组件在脑裂或网络分区时

的行为决策。

1、在面对秒杀系统扣减库存这种典型的AP场景下,我会直接选择基于Redis的分

布式锁来抗住极高的并发突刺,为了规避进程假死导致锁超时被误删的致命漏洞,

我会引入Redisson框架,利用其底层的WatchDog看门狗机制开启后台线程为还

未执行完的锁进行动态延期。

2、在应对财务资金流转或严格的状态机串行化这种绝对CP场景时,我会坚决抛弃

Redis,转而采用Zookeeper的临时顺序节点机制,当业务进程崩溃时TCP会话断

开,ZK服务端能立刻精准地删除对应节点释放锁,彻底消灭Redis主从异步复制时

脑裂带来的锁丢失隐患。

3、在实际编码落地Zookeeper锁的过程中,最核心的风险点是大量客户端同时监

听同一个节点引发的惊群效应导致网络瘫痪,我会严格通过代码控制每个客户端只

去监听排在自己前一个的顺序节点,从而形成一个有序唤醒的优雅队列。

业务上线后,不管底层选用的是Redis还是ZK,我都严禁开发人员将外部网络调用

或者慢查询长事务包裹在分布式锁的代码块中,我会强制要求先准备好内存数据再

加锁,将持有锁的绝对时间压缩到毫秒级,防止阻塞整个业务集群的吞吐量。

Q10:挑一个你觉得最有技术含量的项目,画一下它的整体架构图,并说说你负

责了哪几个核心模块。

❌不好的回答示例:

我做过一个电商项目,主要有用户、商品、订单这些微服务。我画的话就是前端连

着网关,下面是几个服务,最后连着MySQL和Redis。我主要负责了订单模块的增

删改查逻辑,还有购物车模块的开发。技术栈用的是SpringBoot和MyBatis,业

务运行得挺稳定的。

为什么这么回答不好:

1、对架构的描述极度扁平化,没有体现出接入层、逻辑层、数据层之间的高可用

设计和流量管控手段。

2、业务模块缺乏亮点,把“核心模块”等同于低价值的“增删改查”操作。

3、没有突出在复杂业务流转中所解决的具体技术痛点(如数据一致性、高并发瓶

颈)。

高分回答示例:

我印象最深的是去年主导重构的分布式高并发聚合支付平台。我通常的架构推演逻

辑是按照流量漏斗的自上而下进行四层纵向拆分,并对每一个容易成为瓶颈的单点

进行剥离。

1、在白板上我会首先画出接入层和网关层,接入层我通过LVS+Nginx做四七层负

载均衡,网关层我主导集成了SpringCloudGateway并自研了基于令牌桶算法的

全局动态限流过滤器,死死卡住洪峰流量防止底层微服务被瞬间打挂。

2、在中间的业务逻辑层,我负责了最核心的交易清结算流转模块,我将原先紧耦

合的同步长链路彻底拆解,引入了RocketMQ作为总线,把订单创建、风控阻断、

账户记账全部改造为事件驱动的异步状态机执行模型。

3、在最底层的数据持久层,面对每天千万级的支付流水数据量,我放弃了单库直

连,通过ShardingSphere对核心交易流水表进行了基于商户ID的哈希分库分表操

作,并在旁路架设了Canal去监听Binlog把数据准实时同步到Elasticsearch中支撑

对账中心的复杂报表查询。

在完成这一整套架构演进后,该平台成功扛住了大促期间平时十倍以上的瞬时并发

突刺。并且我在复盘时还补充了一整套全链路压测规范,在夜间低峰期向生产环境

打入染色流量,提前暴露出那些潜伏在慢SQL和死锁里的隐患。

Q11:在你的项目中,为什么选择使用这套技术栈?当初有对比过其他同类方案

吗?

❌不好的回答示例:

我们在项目中用SpringCloud和Redis是因为大家都在用,而且网上文档多,遇到

问题好解决。选MySQL是因为公司以前就是用这个,运维比较熟。当时没有考虑去

对比其他的像Go语言或者MongoDB之类的,直接就跟着以前的项目照搬过来了,

能满足业务需求就行。

为什么这么回答不好:

1、选型逻辑极其草率,把“盲从主流”和“历史遗留”作为技术决策的核心依据。

2、暴露出缺乏独立架构选型和多维度横向压测对比分析的技术能力。

3、没有结合具体的业务场景需求(如读写比、数据结构灵活性)来印证选型的合

理性。

高分回答示例:

我通常的技术选型逻辑是坚决抵制“为了炫技而引入新框架”,所有引入生产环境的

基础设施必须经过业务匹配度、团队维护成本和横向性能压测这三道严格的漏斗筛

选。

1、在决定重构订单宽表数据的检索引擎时,我没有盲目跟风直接上

Elasticsearch,而是针对性地提取了生产环境的真实脱敏数据,分别在ES和

ClickHouse上进行了百万级单表多维条件聚合压测。

2、在压测过程中我清晰地看到了核心差异,ClickHouse在列式聚合查询上比ES快

了近一个数量级,但考虑到我们业务线有极高频的并发小批量更新操作,而

ClickHouse的Update性能是毁灭性的,所以我最终拍板保留了基于倒排索引且近

实时更新极其稳定的Elasticsearch。

3、在敲定微服务配置中心组件时,我横向对比了Apollo和Nacos,虽然Apollo在

多环境灰度发布和权限管控上更加重型和完善,但基于我们目前团队只有几十人的

规模且急需一套能同时满足服务注册发现轻量级中间件的痛点,我果断选择了部署

更为敏捷的Nacos来降低整体运维心智负担。

经过这种极其务实的交叉对比后,我会在团队的Wiki上留下一份详尽的ADR(架构

决策记录)文档,把当初不选某个竞品的致命原因和当前方案的性能天花板白纸黑

字地记录下来,防止未来的接手人再次踩坑或进行无意义的重构。

Q12:你在项目中遇到过最难的一个Bug是什么?当时你的排查思路是怎样的,

最后怎么解决的?

❌不好的回答示例:

我遇到过最难的一个Bug是线上服务器突然卡死了。当时我看CPU满了,以为是有

人在做大数据量查询。我查了半天日志没看出来什么,然后就把服务器重启了。重

启之后暂时好了,后来发现是因为代码里写了个死循环导致内存溢出。最后我把那

段代码改了,加了个退出条件就解决了。

为什么这么回答不好:

1、将极其严重的生产事故排查过程描述得犹如儿戏,违背了线上故障处理的基本

原则。

2、连CPU满载和内存溢出(OOM)这两个截然不同的异常现象都混为一谈,底层排

障逻辑混乱。

3、没有使用任何专业的诊断工具箱(如jstat、jmap或MAT)去提供排查的具体数

据支撑。

高分回答示例:

在处理这种毫无征兆的线上灵异Bug时,我通常的逻辑是死死护住案发现场的尸体

(Dump文件),坚决拒绝没有保留证据的盲目重启,因为重启只会掩盖内存泄漏

这种具有潜伏期的致命代码缺陷。

1、当时告警系统疯狂推送某台核心节点频繁发生FullGC且单次停顿高达几秒,我

第一时间切断了网关流向该节点的流量进行物理隔离,并通过jmap命令立刻导出了

一份现场完整的HeapDump快照文件落到本地磁盘。

2、我将这个高达数GB的快照导入进MAT内存分析工具中,直接越过浅层对象去看

支配树(DominatorTree),精准定位到是一个异步导出报表的线程对象占据了堆

内90%以上的内存,里面密密麻麻塞满了成百上千个巨大的String实例。

3、拿着这个线索我直接杀回代码仓库进行溯源追踪,发现是某个新来的同事在处

理百万级数据导出时,在循环体内疯狂使用加号进行SQL字符串拼接,由于String

的不可变性导致在老年代瞬间打爆了内存。

修复方案非常简单粗暴,我立刻将其重构为复用的StringBuilder并在外层增加了接

口限流保护。为了彻底拔除这类毒瘤,我在次日的复盘会上强行把JVM内存配置规

范化,加入了发生OOM时自动导出Dump文件的启动参数,并将此列入CI管道的基

础压测熔断标准中。

Q13:如果你的项目流量突然暴增10倍,你觉得现在的架构哪个组件会最先扛

不住?你会如何演进?

❌不好的回答示例:

如果流量暴增10倍,我觉得MySQL数据库肯定最先挂掉,因为现在的查询大部分

都是直接查库。演进的话,我会加很多Redis缓存,把数据都放到缓存里。然后我

会把服务器从2台增加到20台,并且加个消息队列来缓冲一下。这样就能抗住10倍

的流量了。

为什么这么回答不好:

1、仅给出了粗暴的加机器和加组件方案,没有体现出资源成本意识和架构演进的

层次感。

2、没有细分流量的属性(是读流量暴增还是写流量暴增),导致解决方案缺乏精

准度。

3、忽略了高并发场景下引入中间件可能带来的副作用(如缓存雪崩、消息积

压)。

高分回答示例:

面对突发性指数级的流量暴击,我通常的防御逻辑是假设最底层的关系型数据库一

定是最先阵亡的阿喀琉斯之踵。我的演进策略不会是盲目堆机器,而是从网关层向

下进行极致的流量拦截和异步削峰。

1、在应对高达十倍的只读流量洪峰时,我会立刻实施缓存前置战略,绝不仅仅是

加一层Redis,而是要在应用服务本地开启Caffeine形成一二级多级缓存架构,将

那90%完全不需要动态计算的商品查询流量死死拦截在JVM内存中,彻底斩断它们

触达网络IO和数据库的可能。

2、面对那剩下的10%强一致性写流量突刺,我会坚决斩断核心交易链路中所有非

关键的同步RPC调用(如发短信、发积分),强行将其改造为抛入RocketMQ的异

步事件,依靠MQ底层的百万级吞吐能力去抗压,保护消费者端的DB只能以它能承

受的最大TPS去匀速拉取执行。

3、在架构的顶层入口,最核心的保命手段是丢弃,我会迅速在API网关层启动基于

令牌桶算法的严格限流策略,宁可直接向端侧返回“系统繁忙”的降级页面,也绝对

不允许哪怕一丝超出后端承载极限的脏流量涌入集群。

在度过这场流量战役后,我会主导一次全量压测复盘,将这次大促跑出来的一手真

实阈值数据固化到Sentinel等流控规则配置面板中,并制定出一套能够一键关闭非

核心边缘服务释放算力的弹性扩缩容SOP。

Q14:讲一下你们项目中的权限控制是怎么做的?如果是微服务架构,如何实现

跨服务的统一鉴权?

❌不好的回答示例:

我们项目用的是Shiro框架做权限。用户登录后查数据库,看他有什么角色和权限,

然后存到Session里。微服务的话,因为有多个服务,Session不共享,所以我们

就用Redis来存用户的登录状态。每个服务接到请求就去Redis里查一下有没有这个

用户的权限信息,有的话就放行。

为什么这么回答不好:

1、在微服务场景下依然采用类似Session集中存储的重度依赖方案,完全背离了微

服务无状态的设计初衷。

2、每个下游服务都去请求Redis进行鉴权验证,引发了极其严重的系统网络I/O冗

余和单点性能瓶颈。

3、缺乏将统一鉴权职责上浮到网关层这一核心架构演进思维。

高分回答示例:

在设计企业级权限体系时,我通常的演进逻辑是坚决摒弃中心化的状态存储。随着

架构从单体裂变为微服务,权限控制的职责必须进行物理级别的上浮和解耦,绝不

能让下层的业务代码被冗杂的鉴权逻辑所污染。

1、在底层的数据模型构建上,我严格遵循标准的RBAC模型设计出用户、角色、权

限这五张核心基础表,这确保了后续无论业务模块怎么扩张,菜单级和按钮级的权

限资源都能被精细化映射和分配。

2、在微服务实战落地中,为了避免几十个下游服务产生大量的鉴权冗余代码和内

网I/O开销,我会把整个Token的验签和鉴权动作强行上浮到API网关层通过全局过

滤器来统一拦截和阻断非法请求。

3、在解决跨服务上下文透传的核心障碍时,我放弃了依赖Redis去查状态,而是采

用了完全无状态的JWT方案,网关在校验JWT签名无篡改后,直接将其中的UserID

和角色标识解析出来,塞入内部HTTP请求的Header中向后继续透传,下游业务服

务只需像读取本地变量一样从Header或ThreadLocal里获取信息即可。

在权限模块安全平稳运行后,考虑到Token一旦签发在过期前无法作废的安全漏

洞,我会在架构的旁路专门补充一个黑名单Redis缓存机制,一旦发生用户密码重

置或权限紧急冻结的极端状况,网关层能第一时间拦截掉这些非法的遗留Token。

Q15:你的项目中是如何保证数据库和缓存的数据一致性的?如果采用延迟双删

依然出现不一致怎么处理?

❌不好的回答示例:

我们一般是先更新数据库,然后再删缓存。有时候用延迟双删,就是先删缓存,再

更新数据库,最后休眠一会再删一次缓存。如果这样还是不一致,我们就只能在代

码里写个定时任务,半夜跑一次,把数据库的数据全部查出来覆盖一下Redis里的

数据,这样肯定就一致了。

为什么这么回答不好:

1、对延迟双删的底层痛点缺乏认知,休眠时间无法精准预估,依然存在极大概率

的并发读写脏数据风险。

2、最后提出的半夜全量定时对账方案极其粗暴,实时性极差,根本无法满足高可

用业务场景的需求。

3、没有提到高并发下真正优雅且解耦的异步监听补偿方案。

高分回答示例:

在处理这种分布式系统最头疼的一致性顽疾时,我通常的逻辑是放弃对绝对强一致

性的幻想,转而在极高吞吐量和最终一致性之间寻找最佳的工程平衡点,绝不为了

微小的概率去加死锁拖垮整个交易链路。

1、在处理绝大多数常规业务时,我会坚定地遵循CacheAside模式,强制要求必

须先操作数据库更新落盘,成功后再去删除缓存而非更新缓存,从根本上消灭两个

并发写请求带来的缓存值相互覆盖错乱问题。

2、在面对读写并发极高导致删除动作依然存在极短窗口期脏数据风险时,我不会

在业务代码里引入极其恶心的Thread.sleep延迟双删去阻塞线程,而是直接在架构

底层引入Canal组件去伪装成MySQL的从节点静默监听Binlog日志。

3、一旦监听到数据库行记录发生变更,Canal会立刻把包含最新数据的主键ID作为

消息推入RocketMQ,在消费端通过一个独立且完全解耦的后台服务去精准执行缓

存的驱逐和重载逻辑,即使偶发网络抖动失败了,也能依赖MQ自身的重试机制死

磕到底。

经过这套异步监听方案的改造,我成功将业务主链路中的缓存同步代码彻底抽离斩

断。只有在面对极个别必须具备严苛强一致性的库存清算扣减模块时,我才会妥协

降级,直接使用分布式写锁把对该Key的所有并发读请求强行阻塞入列,用性能去

换取绝对的安全。

Q16:项目中有没有用到多线程或协程去优化业务逻辑?具体是怎么落地的,遇

到了什么坑?

❌不好的回答示例:

用到过,当时有一个接口要查很多不同的数据,比较慢。我就直接在方法里new了

几个Thread跑去查,查完再合起来返回,速度确实快了。遇到的坑就是有时候用户

没登录报错了,后来发现是因为新线程里拿不到用户的Session信息,我就把用户

信息当参数传进去解决了。

为什么这么回答不好:

1、在生产环境中直接newThread是极其危险的操作,没有体现出使用线程池进行

资源复用和防爆控制的专业素养。

2、没有提到如何处理并发任务的阻塞等待和超时控制,缺乏对高并发雪崩风险的

防御意识。

3、对上下文丢失(ThreadLocal问题)的解决方式过于原始,没有展现出在框架底

层拦截优化的能力。

高分回答示例:

在优化C端首页这种极重度的聚合查询接口时,我通常的逻辑是果断打破传统的串

行阻塞模型,利用多线程将下游多个不同RPC接口的调用开销从“累加”转化为“取最

大值”。但引入多线程稍有不慎就会演变成拖垮JVM的灾难。

1、在具体落地时我严禁任何人直接创建线程,而是通过配置中心动态定制了一组

专门用于I/O密集型任务的独立线程池,并结合CompletableFuture编排工具,将原

本需要耗时2秒的串行获取用户信息、商品推荐和未读消息的操作拆解为三个并行

执行的异步子任务。

2、在这个过程中最核心的风险点是单个下游接口的假死导致整个线程池资源被瞬

间抽干,我强制规定所有抛入线程池的异步任务必须配置极其严苛的get()超时时间

参数,一旦触发超时立即返回预设的默认降级兜底数据,绝不让上游链路无限夯

住。

3、遇到的最大坑是鉴权拦截器中的User上下文全部丢失,因为传统ThreadLocal

无法跨线程传递,我没有选择手动传参污染业务代码,而是直接重写了线程池的

execute和submit方法,在父线程提交任务的瞬间把上下文快照复制进去,在子线

程执行前进行无缝的加载和卸载。

优化上线后,该聚合接口的P99响应时间断崖式回落到200毫秒以内。并在复盘时

我顺势搭建了一套基于Micrometer的线程池监控大盘,紧盯队列积压量和活跃线程

数,确保在异常时刻能直接通过配置中心触发拒绝策略来保住核心进程不断联。

Q17:你们的系统是如何处理幂等性问题的?请举个具体的业务场景进行说明。

❌不好的回答示例:

我们处理幂等性主要靠前端控制,用户点了提交按钮之后就把按钮置灰,不让他点

第二次就行了。如果有人用接口刷的话,我们就在数据库里用主键冲突来报错。遇

到重复请求直接捕获那个异常就不管了。这样就能防止下订单或者扣钱的时候多

扣。

为什么这么回答不好:

1、把防线极其危险地建立在前端置灰这种纯属防君子不防小人的操作上,缺乏服

务端底线的安全思维。

2、仅仅依赖数据库主键报错作为核心逻辑,不仅严重浪费数据库I/O资源,也无法

优雅地向调用方返回原有结果。

3、缺乏应对分布式环境下多并发重试请求的系统性防抖缓存拦截方案。

高分回答示例:

我处理核心交易链路幂等性问题的绝对原则是:永远不要相信客户端的任何防抖承

诺,必须将业务的全局唯一标识作为防重放拦截的关键维度,在服务端构建出从缓

存粗筛到数据库唯一索引兜底的坚固防线。

1、在处理第三方支付平台极其高频的回调通知场景时,我坚决不以本地生成的订

单号作为防重依据,而是强制提取支付宝或微信回调报文中的外部流水号和状态

码,将它们拼接成唯一的业务Key。

2、在执行核心对账逻辑前,我会在代码最前端引入Redis的SETNX命令配合极短

的过期时间做一次第一层防抖拦截,把大部分因为网络超时引发的疯狂点击和恶意

并发重试请求以纳秒级的极速直接击碎并返回缓存中的旧处理结果,死死护住底层

的关系型数据库。

3、即使高并发的幽灵请求侥幸穿透了缓存,我在数据库层面早已针对该外部流水

号字段打上了唯一索引,通过这种最后一道物理隔离强行阻断同一笔资金流水的第

二次变更,通过捕获DuplicateKeyException去进行平滑的业务降级和告警。

经过这种多维度的幂等性改造,我们的系统彻底消灭了因为各种微服务重试和网络

抖动导致的重复发货和资损事故。在项目复盘中,我将这套包含了防重缓存拦截、

分布式锁包裹和数据库唯一约束的SOP整理成了基础组件库,强制推行到所有涉及

状态变更的接口逻辑中。

Q18:遇到过超大分页查询的场景吗?比如深度分页遇到性能瓶颈,你是如何优

化SQL或改变方案的?

❌不好的回答示例:

遇到过,当LIMIT1000000,10这种深分页的时候,MySQL会查得很慢。我的优化

方法是去把数据库的索引加一加,看看是不是没走索引。如果还是不行的话,我就

告诉前端不让他们跳页了,只能点下一页。或者把数据全查到内存里然后用Java代

码来做切片分页返回。

为什么这么回答不好:

1、全查到内存做分页的思路在几百万级数据量下会导致瞬间OOM,是极其不可取

的自杀式方案。

2、没有剖析LIMIT偏移量过大导致MySQL底层需要扫描并丢弃大量无用记录这一性

能灾难的本质。

3、缺乏利用游标或延迟关联等高阶SQL优化技巧的实操演示。

高分回答示例:

在排查由于LIMIT偏移量过大引发的慢查询告警时,我通常的分析逻辑是先用

EXPLAIN透视其底层执行计划,这绝不是单纯加个索引就能解决的,其本质原因是

MySQL优化器为了跳过前一百万条数据,不得不回表扫描出所有的完整行然后残忍

丢弃,产生了毁灭性的磁盘I/O开销。

1、在应对诸如订单流水导出这种绝对必须翻页的场景时,我会毫不犹豫地抛弃传

统的OFFSET写法,将其重构为基于上一页最大记录ID的条件推演查询,直接用

WHEREid>max_idLIMIT10的方式强制走主键聚集索引,让查询时间从恐怖的

几秒瞬间跌回两毫秒级别。

2、如果业务场景极其特殊,确实需要让用户进行大跨度、任意跳转的深度页码定

位,我会采用子查询延迟关联的折中技巧,先用极其轻量级的覆盖索引查出目标那

10条数据的主键ID,再去跟原表做INNERJOIN获取全部字段,极限规避海量无效

数据的回表开销。

3、若数据规模彻底突破千万级别且伴随着复杂的文本多维搜索,我会果断斩断在

MySQL内挣扎的幻想,通过监听Binlog将数据全量异构同步至Elasticsearch,利

用ES原生提供的Scroll游标机制去承接这种纯粹的搜索和深度切片请求。

这套组合拳上线后,彻底治愈了系统中几个顽固的历史遗留慢接口。在组内代码

Review规范中,我随后明确添加了一条铁律:严禁在没有任何WHERE过滤条件和

排序主键依托的情况下,直接对过万行数据的大表使用裸露的LIMIT进行深度翻页跳

转。

Q19:讲一下你在项目中做过最成功的一次性能优化(比如接口响应慢或CPU

利用率异常),优化前后的数据对比是怎样的?

❌不好的回答示例:

有一次我们系统的导出功能很慢,大概要点一下等一分钟才能出结果。我接手之

后,看了一下代码,发现里面嵌套了很多层for循环。我就把那些循环都拆开了,然

后把能用多线程的地方都加上了多线程。最后优化完之后,大概十秒钟就能导出文

件了,速度提升了特别多。

为什么这么回答不好:

1、把性能优化仅仅等同于改写for循环,缺乏对I/O瓶颈和内存占用的全链路分析视

角。

2、滥用多线程却不提任何边界控制,在处理大批量数据导出时极其容易引发内存

崩溃。

3、缺乏严谨度量,只有模糊的“大概十秒”,没有专业的TPS、QPS或P99延迟等指

标对比。

高分回答示例:

我通常执行性能优化的铁律是:拒绝任何没有APM链路追踪数据支撑的盲目推测。

在解决近期某核心交易链路TPS上不去的瓶颈时,我没有急着去改代码,而是先调

取了SkyWalking的拓扑图,精准锁定到问题源于一个循环调用下游微服务的黑洞逻

辑。

1、我第一时间重构了这段引发N+1网络风暴的代码,将原先在for循环中单条单条

去发起RPC查询商品信息的逻辑全部铲除,转而与下游团队协调提供了一个批量获

取的接口,在内存中完成组装聚合,仅仅这一步改造就砍掉了90%以上的网络握手

开销。

2、顺着链路继续下钻,我发现在拼装最终返回DTO时使用了极其沉重的

BeanUtils.copyProperties进行深拷贝,在极高的并发下反射机制引发了CPU的疯

狂燃烧,我立即将其全部替换为基于预编译的MapStruct工具自动生成硬编码的

Get/Set方法。

3、在最底层的数据库写入环节,我将原先逐条Insert的操作合并为JDBC的Batch

批量提交,并在执行前加了一层极短的本地GuavaCache缓存拦截那些字典类的非

敏感只读数据,死死卡住无关紧要的磁盘I/O下沉。

经过这套自上而下的微操手术,压测报告显示该核心接口的P99响应时间从极其危

险的1.5秒断崖式骤降到了令人满意的60毫秒以内,单机可承载的QPS也从可怜的

200飙升到了将近1500。最重要的是,服务器的CPU利用率始终平稳地保持在60%

的健康水位线下,没有再引发任何报警。

Q20:分布式事务在你的项目中有落地吗?具体使用的是2PC、TCC还是最终一

致性方案,为什么这么选?

❌不好的回答示例:

我们用过分布式事务,当时因为微服务之间数据经常对不上。我选了2PC两阶段提

交,在代码里用了Seata框架去实现的。选它的原因就是用起来比较简单,网上的

教程也很多,加几个注解就行了。虽然感觉加了之后接口变慢了一点,但至少数据

是强一致性的了。

为什么这么回答不好:

1、对2PC(两阶段提交)的致命性能毒药属性缺乏认知,高并发下全局锁会瞬间瘫痪

整个系统。

2、选型理由依然是“用起来简单”、“教程多”这种缺乏极客精神的肤浅借口。

3、没有展示出如何在真实业务中权衡CAP定理,并在强一致性与高吞吐量之间做

出断臂求生的妥协。

高分回答示例:

在架构设计阶段讨论分布式事务落地方案时,我极其排斥为了追求所谓的强一致性

而在核心高频链路上引入诸如2PC这种极度笨重的全局阻塞锁。我通常的妥协逻辑

是依靠可靠消息模型来拥抱最终一致性,用短时间的延迟去换取系统无限的吞吐

量。

1、在主导重构整个微服务订单履约链路时,我坚决阻断了直接使用SeataAT模式

在订单和库存服务之间建立分布式大事务的想法,转而引入了RocketMQ并将其原

生的半消息(HalfMessage)机制作为系统解耦的骨干动脉。

2、在落地的核心步骤中,我要求订单中心在提交本地订单事务前,必须先向MQ成

功投递一条状态为未确认的事务预备消息,只有当本地数据库事务提交且成功落盘

后,才触发二次请求将该消息转为可用状态抛向给下游的库存服务,彻底消灭了消

息发送与本地写库之间的原子性分裂隐患。

3、在面对极少数因为下游服务假死或网络彻底瘫痪导致最终一致性遭到破坏的极

端脏数据时,我并没有继续在业务代码里死磕,而是在旁路部署了一套基于定时调

度的T+1对账审计平台,每天凌晨自动拉取双边流水进行金额轧差,针对不平账的

孤儿单直接报警并转交人工运营工单强制介入修复。

这套基于柔性事务思想的改造方案上线后,整个清结算接口的耗时被强制压缩到了

百毫秒级别,即使下游某一个非核心服务发生短暂宕机,也丝毫不会阻塞源头大量

订单的极速涌入,完美诠释了利用异步事件驱动来保护高可用底盘的架构初衷。

Q21:在你们的秒杀/抢购等高并发场景中,是如何防止超卖的?具体代码层面

是怎么实现的?

❌不好的回答示例:

防超卖最简单的方法就是加锁。我们一般是在数据库查询库存的时候加上for

update,利用数据库的行锁来排队执行。如果觉得数据库扛不住,就会在代码层用

Redis的setnx加一个分布式锁,谁拿到锁谁就能扣库存,没拿到就报错,这样绝对

不可能出现超卖。

为什么这么回答不好:

1、把高并发场景和沉重的悲观锁强行捆绑,这种方案在真实秒杀中会瞬间把数据

库连接池打满并导致吞吐量降至冰点。

2、用分布式锁包裹整个扣减逻辑会导致串行化执行,完全丧失了引入Redis作为高

并发缓冲的意义。

3、缺乏将“防超卖”这一个动作进行分层拦截(从内存到缓存再到DB)的纵深防御

思维。

高分回答示例:

面对大促秒杀这种极其极端的并发写突刺,我通常的逻辑是绝对不能让任何一丝争

抢流量直接穿透到关系型数据库,必须将抢库存的动作转化为Redis内部极其轻量

的原子操作。

1、我会第一时间在代码层引入Redis的Lua脚本功能,将查询剩余库存和扣减库存

这两个动作打包成一个绝对原子的指令下发,这彻底规避了高并发下“查-改”操作分

离导致的竞态条件和超卖漏洞。

2、在Redis扣减成功后,我不会让当前线程同步去等数据库落盘,而是直接将包含

用户ID和商品ID的扣减成功凭证作为消息打入RocketMQ,前端直接向用户返回“抢

购中”,利用MQ极高的吞吐量把洪峰流量削平。

3、在后端真正消费MQ去操作MySQL写盘时,我会使用库存版本号(Version)来

实现乐观锁兜底,在Update语句中强制比对版本号,即使极小概率发生了消息重

发,也能在底层物理存储上做最后一道硬拦截。

在历次大促复盘中,我会重点核对Redis的缓存库存量与MySQL真实库存量的最终

对账记录。为了防止个别恶意用户利用脚本疯狂取消订单导致Redis库存回滚异

常,我还会在旁路建立一个专门处理超时未支付订单的补偿轮询机制来保证数据的

最终闭环。

Q22:系统的核心接口有没有做限流和降级?使用的是什么算法,如何根据真实

业务去定限流的阈值?

❌不好的回答示例:

我们有用Sentinel做限流,一般选的是令牌桶或者漏桶算法。定阈值的话就是根据

平时经验,比如我觉得这个接口一秒钟能处理500个请求,那我就把阈值配成

500。如果超过了500,就直接抛出一个限流异常,或者返回一个默认的失败信息

给前端,让用户等会再试。

为什么这么回答不好:

1、限流阈值的设定极其草率,“拍脑袋定数字”是线上频繁发生误杀或压垮机器的直

接元凶。

2、没有明确区分令牌桶(应对突发流量)和漏桶(绝对平滑整形)在实际业务场

景中的应用差异。

3、降级策略极其粗暴单一,没有考虑提供柔性可用(如返回缓存旧值)的兜底方

案。

高分回答示例:

我通常的防御逻辑是:任何没有经过全链路压测就拍脑袋定下的限流阈值,都是埋

在线上的定时炸弹。限流不仅仅是配一个数字,而是一套基于资源容量底线的动态

防护体系。

1、我会联合测试团队在夜间低峰期打入真实比例的染色流量,不断逼近系统的性

能极限,直到找出单台实例CPU飙升到80%或者接口响应时间发生拐点时的真实

QPS数值,再打个八折作为最终的限流阈值。

2、在网关层处理瞬间涌入的秒杀突刺时我会首选令牌桶算法,利用其允许一定程

度突发流量的特性快速消化合规请求,而在处理极重度的定时跑批任务流控时,我

会改用漏桶算法强行将流量整形为绝对匀速的滴漏以保护底层数据库。

3、在微服务调用链内部发生限流阻断时我会提供极其平滑的降级手段,例如商品

推荐接口被限流后,代码会立刻切向本地的Caffeine缓存读取兜底的静态推荐列

表,而不是向C端用户抛出冰冷的报错弹窗。

这套防护网建立后,我强制要求所有核心业务线的流控规则必须接入配置中心实现

动态下发。在应对诸如双十一这种大促时,运维能根据机房的实际负载水位,在一

分钟内无需重启直接拔高或收紧全站的限流阀门。

Q23:你在项目中负责的模块,每天产生的数据量有多大?如果单表数据量达到

千万级,你会怎么做分库分表规划?

❌不好的回答示例:

我负责的订单模块每天大概几万条数据。如果以后数据量到了千万级,我就会引入

ShardingSphere中间件去分表。最简单的办法就是按时间分,比如一个月建一张

表。或者直接拿订单ID对10或者100取模,然后分散存到不同的表里去,这样就能

把单表数据量降下来了。

为什么这么回答不好:

1、按时间分表会导致极度严重的数据冷热不均,当月的热点表依然会面临巨大的

并发I/O压力。

2、简单取模法完全没有考虑系统未来爆发式增长带来的二次扩容难题,重新Hash

会导致全量数据迁移的灾难。

3、没有考虑到一旦以订单ID为分片键后,C端用户按自身ID查询订单列表将引发极

其低效的全路由广播。

高分回答示例:

在规划数据库的水平拆分时,我通常的逻辑是坚决抵制为了分库而分库,这是一种

不可逆的复杂架构改造。在动手之前必须基于业务增长模型进行极其严苛的容量推

演和查询维度分析。

1、在敲定分片键(ShardingKey)时我会直接拒绝按时间切分这种外行做法,而

是抓取业务中占比90%以上的查询入口,比如在买家视角的订单库中,我会毫不犹

豫地抽取买家用户ID作为分片键,确保该买家的所有操作都能被精准路由到单一物

理分片内完成闭环。

2、在处理二次扩容的数据迁移痛点时我会采用一致性哈希结合预分配虚拟节点的

方案,一开始哪怕只有两台物理库,我也会直接在逻辑层切出1024个虚拟表,未来

增加物理机时只需要进行整表级的数据搬迁,彻底消灭了重新计算Hash的恶梦。

3、在解决因为买家ID作为分片键导致卖家端无法高效查询的问题时,我会引入

Canal组件实时监听买家库的Binlog日志,在旁路毫秒级异构出一套以卖家ID为分

片键的只读卖家库,用磁盘空间的冗余去换取查询性能的绝对极致。

在分库分表成功落地后,为了避免开发人员写出全路由的危险SQL把所有底层节点

打满,我在中间件层强制配置了SQL拦截规则,任何不带分片键的查询或者跨节点

的Join语句会在执行前被直接熔断并抛出异常。

Q24:分库分表之后,跨库的Join查询和全局的分页查询你们是怎么解决的?

❌不好的回答示例:

跨库Join的话就不能直接在SQL里写Join了。我们的做法是先在代码里查出一个库

的数据拿到ID,然后再拿这些ID去循环查另一个库的数据,最后在Java内存里把它

们拼起来。全局分页就用ShardingSphere,它会自动把所有库的数据查出来,然

后再帮我们排序和分页。

为什么这么回答不好:

1、在代码里用for循环发起网络RPC查询数据库是极其典型的N+1风暴,性能极

差。

2、把所有库数据查到内存里做拼接,在数据量稍微一大时就会引发秒级的内存溢

出(OOM)。

3、迷信中间件的全局分页,没有意识到这会导致底层执行大量LIMIT丢弃动作,彻

底压垮数据库IO。

高分回答示例:

在应对分库分表后的分布式查询顽疾时,我通常的架构逻辑是绝不允许在在线核心

交易链路中进行任何形式的跨库Join,这种行为违背了数据分片的初衷,必须在业

务设计和数据同步层面进行降维打击。

1、在处理商品表和字典类表这种必须关联的跨库查询时我会采用全局广播表策

略,利用分库组件将那些极少修改但频繁被Join的小表实时同步到所有的分片物理

库中,让所有的Join动作强行在单一节点的内部完成。

2、对于复杂的业务关联查询我会坚决采用字段冗余的方式打造业务宽表,宁可在

订单表里多存几列商品名称和商户信息忍受一定程度的数据冗余,也绝不允许跨网

络发起二次数据库调用。

3、在面对运营后台那种必须进行全局多条件组合查询和深度翻页的变态需求时,

我会彻底斩断在MySQL内挣扎的幻想,通过监听Binlog将数据全量异构同步至

Elasticsearch,利用ES天然的分布式倒排索引和游标机制去承接所有的复杂聚合

和分页需求。

每次评审涉及分片表的新需求变更时,最核心的风险点就是开发擅自写出跨片扫描

的SQL。为此我们组内的研发红线规定,在线系统的微服务只允许通过单一

ShardingKey进行点查,任何违规的跨库聚合代码在CodeReview阶段会被无条

件打回。

Q25:在微服务架构中,你们的服务拆分粒度是根据什么划分的?有没有遇到过

服务边界划分不清导致的问题?

❌不好的回答示例:

我们拆分服务主要是看代码量和模块。比如把用户相关的代码抽成一个用户服务,

订单代码抽成订单服务,积分抽成积分服务。拆得越细越好,这样互不影响。遇到

过的问题就是有时候查一个页面要调十几个服务,很慢。还有就是改一个需求,经

常要前后端和几个服务的人一起联调很久。

为什么这么回答不好:

1、把“代码量”作为微服务拆分的依据,是完全不懂领域驱动设计(DDD)的外行

表现。

2、盲目追求“拆得越细越好”,导致分布式事务泛滥和链路无止境的拉长。

3、对边界不清引发的问题只停留在表面抱怨,没有给出利用聚合根或上下文重构

的深层解决方案。

高分回答示例:

在主导微服务架构演进时,我通常的逻辑是坚决反对以静态的数据表或者代码行数

去驱动拆分,那只会制造出一堆分布式的单体灾难。微服务的边界必须高度贴合业

务的真实运转生命周期。

1、我会深度引入DDD(领域驱动设计)的方法论,通过事件风暴与业务专家对

齐,把那些修改频率完全不一致、生命周期独立的对象严格切开,比如订单履约和

商品陈列绝对不能混在一个服务里,因为一个是极高并发的写核心,另一个是极高

并发的读核心。

2、在处理边界极其容易模糊的共享对象时,比如“库存”,我绝不允许出现跨服务直

接修改数据源的恶劣行为,我会将其提升为独立的聚合根服务,任何对库存的变更

只能通过明确暴露的领域接口来排队执行。

3、在评估服务颗粒度时我会死死盯住康威定律,一个微服务如果需要跨三个以上

的后端团队进行长达几周的频繁同步联调,这就意味着我们的边界彻底划错了,我

会果断将其回退合并为一个粗粒度服务以降低极其昂贵的组织沟通成本。

经历过早期盲目拆分导致的分布式调用雪崩后,我明确向团队下达了指导原则:微

服务演进必须是渐进式的绞杀者模式。只有当某个模块的吞吐量瓶颈或者发布频率

与主体应用产生了无法调和的矛盾时,才具备将其物理抽离的资格。

Q26:项目里怎么做日志收集和链路追踪的?如果一个请求经过了5个微服务最

后报错了,你怎么快速定位出问题的节点?

❌不好的回答示例:

日志收集我们是用ELK,在每台机器上装个Logstash把日志发到ES里,然后去

Kibana上搜

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论