Skip to content

微服务面试题 - 2026-06-19 17:44 ​

生成时间:2026-06-19 17:44 | 共 10 题 | 难度递进(基础→进阶→高阶)


【面试题】1. 雪花算法生成的分布式 ID 长什么样?为什么能做到全局唯一?时钟回拨是什么问题,怎么解决? ​

【标准答案】雪花算法生成的 ID 是一个 64 位 Long,结构:1 位不用 + 41 位毫秒时间戳 + 10 位机器 ID(5 位机房 + 5 位机器)+ 12 位序列号。全局唯一的保证靠两条:时间戳保证不同毫秒的 ID 不会重复,序列号保证同一毫秒内最多 4096 个 ID 不重复,加上机器 ID 保证不同机器的 ID 前缀不同。三者组合在一起,只要同一台机器在同一毫秒内的序列号不溢出,全局绝不重复。时钟回拨是雪花算法的致命问题——NTP 时间同步让机器时间倒退几秒,可能导致这期间生成的 ID 和之前重复。三种解法:一是直接抛异常拒绝服务(最安全但不优雅),二是等——循环等到时间追上之前的最大时间戳再继续生成(适合短暂回拨),三是借用未来时间——回拨时把"已用过的毫秒"跳过,下次时间追上来后重新开始(Meituan Leaf 的做法)。生产上最稳妥的方案:Leaf 的号段模式——不每毫秒算 ID,而是每次从 DB 批量拉一段号(如 1000 个),本地缓存着发,发完再拉一段,彻底不受时钟影响。


【面试题】2. Eureka 的"自我保护模式"是什么?为什么很多面试官爱问这个?触发条件是什么、后果是什么? ​

【标准答案】自我保护模式是 Eureka 最经典的反直觉设计:当 Eureka Server 发现大量客户端心跳超时(比如 15 分钟内 85% 的实例都没心跳了),它不是去剔除这些"失联"实例,而是暂停剔除——保护注册表的完整性。为什么这么设计?因为大概率不是客户端挂了,是 Eureka 和客户端之间的网络出了问题(网络分区)。如果此时 Eureka 把"没心跳"的实例全踢掉——整个注册表空了,消费者就服务发现不到任何实例,整个系统反而可以正常工作?不对,是整个系统全挂。触发公式:实际续约数 / 预期续约数 < 0.85。后果:Eureka 不再剔除"过期"实例,注册表里会保留已经下线的旧实例信息。消费者调用时可能拿到死实例地址导致个别请求失败,但比"注册表全空"好得多。Eureka 的设计哲学一句话:宁可让你拿到一个可能不能用的地址,也别让你什么都拿不到。这和 Nacos AP 模式的设计目标一致,但 Eureka 用了一个更粗暴的实现——"全部不踢"。面试官问这个,其实在考你 CAP 在注册中心里的真实落地:Eureka 选了 AP,自我保护模式就是网络分区时的保活策略。


【面试题】3. Spring Cloud Gateway 和 Zuul 1.x 的核心区别?为什么 Zuul 退出历史舞台了?Gateway 的过滤器链是什么设计模式? ​

【标准答案】Zuul 1.x 是 Servlet 容器(Tomcat)之上的同步阻塞网关——每个请求占一个 Tomcat 线程,线程被 IO 阻塞时只能干等着。处理 1000 个长连接需要 1000 个线程,线程上下文切换吃掉大量 CPU。Spring Cloud Gateway 基于 WebFlux + Netty,异步非阻塞——一个线程处理多个连接,IO 就绪时被事件循环回调,不需要大量线程。这是网关的"代际差异":同步 Servlet vs 异步 Reactive。Gateway 的过滤器链是责任链模式——请求进来按 order 值从小到大依次经过一批 GlobalFilter 和 GatewayFilter,每个 Filter 处理后交给下一个。区别:GlobalFilter 对所有路由生效(比如全局鉴权),GatewayFilter 只对特定路由生效(比如给 /api/user 加个限流)。过滤器的 pre 逻辑在请求路由到下游之前执行,post 逻辑在下游返回响应后执行,通过 chain.filter(exchange) 分开——这句之前是 pre,之后是 post。面试金句:Zuul 1.x 死在线程模型上,Gateway 赢在异步非阻塞上。Zuul 2.x 也改成了 Netty 但社区生态已经没了。


【面试题】4. 2PC 和 TCC 的核心区别在哪?为什么说 TCC 是"业务层面帮你做了 2PC 协调器的事"?空回滚和悬挂是什么? ​

【标准答案】2PC(两阶段提交)是资源层面的——协调器(Seata AT 的 TC)锁住数据库资源,Phase 1 问你"能提交吗",Phase 2 说"全提交"或"全回滚"。缺点:Phase 1 锁住资源后协调器挂了,锁永不释放——单点故障直接卡死全局。TCC(Try-Confirm-Cancel)把资源锁定从数据库层提到了业务层——Try 阶段你做业务预留(冻结库存),Confirm 阶段实际扣减,Cancel 阶段释放预留。协调器只负责调度 TCC 的三个阶段,不锁任何数据库资源——资源是你在业务代码里自己预留的。一个本质区别:2PC 锁的是数据库行(比如 select for update),TCC 锁的是"业务含义"(比如冻结库存字段 = 100)。两个经典问题:空回滚——Try 还没执行就收到了 Cancel,因为 Try 阶段网络超时协调器以为异常直接调了 Cancel。防法:Cancel 里先检查 Try 有没有执行过,没执行过就跳过去(空回滚幂等)。悬挂——Cancel 先执行了(因为 Try 网络延迟),Try 后执行。Cancel 释放了预留资源,Try 又来预留——资源被错误冻结。防法:Try 执行前检查 Cancel 有没有执行过。一句话:TCC 的复杂度全在幂等和防悬挂上,代码量是 AT 模式的 3~5 倍,但性能是 AT 的 10 倍——因为没有全局锁。


【面试题】5. 客户端负载均衡(Ribbon/LoadBalancer)和服务端负载均衡(Nginx)有什么区别?为什么微服务内部用客户端、边界用服务端? ​

【标准答案】服务端负载均衡(Nginx/F5):请求打到负载均衡器,它帮你选一台后端机器转发。客户端不知道后端有几台机器、哪些活着——负载均衡器是"代理人"。客户端负载均衡(Ribbon/Spring Cloud LoadBalancer):客户端从注册中心拿到服务实例列表(Nacos 里 /service-api 下面 3 台机器 IP),自己在本地做负载均衡算法选一台。区别三句话:一是"谁做选择"——服务端是均衡器选、客户端是自己选;二是"性能瓶颈"——服务端均衡器本身是单点瓶颈,所有流量都经过它,极端流量下均衡器先扛不住;客户端均衡是各消费者分布式决策,没有中心瓶颈;三是"感知能力"——客户端可以从注册中心实时感知实例上下线,服务端均衡器需要额外健康检查。这就是为什么微服务内部用客户端负载均衡:服务间调用多,如果都经过 Nginx,Nginx 成了巨型瓶颈。但边界处(用户请求进系统)必须用 Nginx/Gateway:用户不可能知道你内部有一百台用户服务,你只能给一个入口 IP。一句话:入口收敛用服务端,内部调用用客户端——各司其职。


【面试题】6. Apollo 和 Nacos 配置中心的架构差异?Apollo 没有"临时实例"这个概念,它的高可用靠什么? ​

【标准答案】架构差异四个维度。一是部署结构:Apollo 四层——Portal(管理界面)、Admin Service(修改发布)、Config Service(客户端读取)、Meta Server(路由)。Nacos 就一个单体进程同时干注册和配置两件事,轻量得多。二是配置下发:Apollo 客户端长轮询 Config Service,Config Service 查到有变更后推通知→客户端重新拉取。Nacos 也一样是长轮询,但 Nacos 支持客户端和 Server 之间建 gRPC 长连接,变更实时推送延迟更低。三是高可用方案:Apollo 没有注册中心那种"临时实例",它的 Config Service 全是对等无状态节点,随便挂——只要有一台活着就能提供配置读取。数据库 MySQL 挂了呢?Config Service 会把配置缓存在内存里继续提供服务,只不过不能发布新配置了(降级模式)。Nacos 内置 Derby 或外接 MySQL,配置数据本身也缓存在内存中——同样是"只要有一台活着就能读"。四是多环境管理:Apollo 天然支持 DEV/FAT/UAT/PRO 多环境,Portal 上切换环境就能管理。Nacos 靠 namespace 隔离,搭建时要手动建立不同 namespace。选型一句话:小团队求简单一把梭 → Nacos(注册+配置同进程);大公司需要多环境、权限审批、配置审计 → Apollo(天生企业级)。


【面试题】7. 事务消息(RocketMQ)处理的是一对一的服务间数据一致,SAGA 长事务处理的是什么场景?SAGA 的前向恢复和逆向补偿有什么区别? ​

【标准答案】SAGA 和事务消息解决的不是同一类问题。事务消息(RocketMQ)处理的是一次短事务跨两个服务的一致性——下单→扣库存,两步之间用半消息+回查保证原子性,时间跨度通常秒级。SAGA 处理的是长流程多步骤事务——比如跨境转账:A 银行扣款→外汇兑换→B 银行入账,三步可能跨三个系统、耗时几分钟甚至几小时。SAGA 把一个大事务拆成 N 个本地事务,每一步成功就发事件触发下一步。出问题了两种恢复策略:逆向补偿——哪步失败了,从失败那步往回逐条执行补偿操作。比如 B 银行入账失败→调用外汇兑换的 Cancel 把汇率回滚→调用 A 银行的 Cancel 把扣款恢复。每一步的 Cancel 必须幂等,因为补偿本身也可能失败要重试。前向恢复——失败后不往回退,而是重试当前步直到成功(假设最终一定能成功)。适合可重试无副作用的操作——比如发短信通知,失败了重试 10 次不行就告警人工处理。SAGA 最核心的权衡:逆向补偿要求每一步都有 Cancel 逻辑,开发量大且 Cancel 逻辑本身比正向逻辑还复杂(钱扣了怎么退?汇率变了怎么办?);前向恢复要求操作本身幂等且最终一定能成功。生产上大多数是混合:有补偿能力的步骤做逆向,有重试保证的步骤做前向。面试关键:SAGA 又叫"长事务的最终一致性",和 2PC 比——SAGA 没有全局锁、吞吐高、但业务补偿逻辑重;和事务消息比——事务消息处理短流程,SAGA 处理长流程。


【面试题】8. 缓存击穿、缓存穿透、缓存雪崩,三者到底有什么区别?各自的防御手段是什么? ​

【标准答案】面试官把这三个放一起问,就是看你能不能分清场景。缓存穿透:查一个根本不存在的 key——比如查 userId=-1。缓存里没有,每次穿透到 DB 查也是 null。量大时 DB 被这种无效请求打死。防御:布隆过滤器——把所有存在的 key 提前加载到布隆过滤器中(bitmap),请求来先过布隆,"不在"就直接拒(100% 准确),"可能在"再放行查缓存(有误判概率但可控)。另一种是缓存空值——key 不存在也往缓存写一个 null,设短 TTL 60 秒。缓存击穿:某个热点 key 过期瞬间,大量请求同时砸向 DB——比如秒杀商品的详情缓存刚好过期。防御:互斥锁——第一个请求去查 DB 更新缓存时加锁,其他请求等锁释放后直接读缓存(或者用 SETNX)。另一种是"永不过期"——热点 key 不设 TTL,靠异步线程在快过期时主动刷新。缓存雪崩:大量 key 同时过期或者 Redis 集群挂了——大面积请求穿透到 DB。防御两招:一是 TTL 加随机值——缓存失效时间别设一模一样的 3600 秒,设 3600 + random(600) 秒,让过期时间分散;二是 Redis 高可用——集群/哨兵 + 多级缓存(本地 Caffeine→远程 Redis→DB),Redis 挂了你还有 Caffeine 挡一道。面试一句总结:穿透是查不存在的东西,击穿是一个热点过期,雪崩是一大片过期或 Redis 挂了。三者防御策略可叠加,但别过度设计——小项目布隆过滤器就是杀鸡用牛刀。


【面试题】9. CQRS 什么场景该用、什么场景不该用?读写分离和 CQRS 是一回事吗?CQRS 事件溯源模式下,读模型延迟怎么办? ​

【标准答案】读写分离 ≠ CQRS。读写分离是数据库层面的——主库写从库读,读和写的还是同一套数据模型。CQRS 是应用架构层面的——命令模型(Command)和查询模型(Query)用完全不同的数据结构,甚至不同的数据库。比如订单"写模型"是 MySQL 里的规范化的 order+order_item 表,"读模型"是 Elasticsearch 里的一张展平了所有字段的大宽表 order_view(包含用户姓名、商品图片、物流状态),查订单列表一条 ES 查询就出来,不用 JOIN。什么场景该用 CQRS:读和写的需求差异巨大——写需要事务一致性,读需要多维度筛选和聚合。什么场景不该用:CRUD 型业务(后台管理系统的增删改查),读和写就是同一套字段,CQRS 纯粹是加复杂度。读模型延迟是 CQRS 的核心问题——写模型改完了,读模型还没来得及更新,用户查到的还是旧数据。解法取决于一致性要求:能接受秒级延迟 → 用 CDC(binlog 监听)异步同步到 ES,延迟通常 <1 秒;不能接受延迟 → 命令执行完后同步刷新读模型(但这样擦掉了 CQRS 的解耦优势);或者关键字段用"版本号检查"——前端发请求带上上次读到的 version,后端对比当前版本,版本变了就提示"数据已更新请刷新"。面试金句:CQRS 不是万能药——90% 的业务模块不需要它。只有当你的"查询端"复杂度让你在业务代码里写了几百行 JOIN+聚合+重组 时,才开始考虑 CQRS。架构的复杂度要配得上业务的复杂度。


【面试题】10. Seata 的 AT、TCC、SAGA、XA 四种模式分别适用于什么场景?如果让你给一个电商核心交易系统选型,你会用什么模式覆盖哪些链路? ​

【标准答案】AT(自动补偿):修改前拍快照(before image),提交后删快照,回滚时用 before image 反向生成 UPDATE 还原数据。零业务侵入——加个 @GlobalTransactional 就行。适用于大部分标准 CRUD 的微服务链路——比如下单减库存,事务都走数据库。限制:不支持非关系型数据库,对 SQL 有要求(必须有 WHERE 条件定位到具体行)。TCC(手动两阶段):需要自己写 Try/Confirm/Cancel 三个方法的代码,侵入性最高。适用于需要"业务资源预留"的场景——比如冻结红包余额而不是直接用数据库行锁。优势是性能高(无全局锁),劣势是代码量大、幂等和防悬挂逻辑容易出 bug。SAGA(长事务补偿):没有全局锁,通过状态机编排正向事务链和逆向补偿链。适合跨系统、跨部门的长流程——比如电商下单后要调 WMS 发货、调 FMS 记账,每个系统独立自治,不可能锁住别家系统的数据库。XA(数据库协议层):依赖数据库的 XA 事务协议,性能最差但一致性最强——所有分支事务的 COMMIT 由 TM 统一协调。适合对一致性要求极高、并发不大的场景,比如金融对账。电商核心交易系统选型:下单链路(订单+库存+优惠券)→ AT 模式,开发成本最低且业务够用;红包/余额扣减 → TCC,资源需要冻结与释放;订单后履约(WMS+物流+FMS)→ SAGA,跨多个独立系统长流程。分层混用:AT 管核心交易的一致性,TCC 管需要业务预留的场景,SAGA 管长流程跨系统的最终一致。面试一句总结:别纠结用哪个,全链路不能只靠一种——AT 打底、TCC 精准打击、SAGA 收尾长链路。