微服务面试题 - 2026-06-19 18:44
生成时间:2026-06-19 18:44 | 共 10 题 | 难度递进(基础→进阶→高阶)
【面试题】1. 微服务、单体和模块化单体怎么选?什么情况下"暂不拆分"是更好的决策?
【标准答案】三个选项的决策看三条线。第一条线——团队规模:3人以下团队搞微服务是自杀,部署、联调、排障成本会吃掉所有开发时间,模块化单体(按包拆分、接口隔离、独立模块)完全够用。第二条线——业务成熟度:需求一天三变的创业项目,过早拆微服务的边界一定是错的,三个月后你拆出的服务边界全成了技术债;等业务模型稳定了再拆。第三条线——数据耦合度:如果你的表之间Join满天飞、事务到处跨表,说明数据层面还是"一个整体",硬拆只会把数据库Join变成RPC网络Join,性能雪崩。判断标准:能不能把一个功能团队+它的数据库完整剥离出来独立上线——能,就拆;不能,就暂不拆。面试一句:微服务不是技术演进的目的地,是组织规模和业务复杂度到达临界点后的自然结果——你不到那个临界点硬拆,得到的是分布式的复杂度、单体的交付速度。
【面试题】2. Eureka的自我保护机制是什么?触发后会发生什么?为什么说它是"宁可放过不可错杀"?
【标准答案】Eureka的自我保护机制是这样触发的:当Eureka Server在15分钟内,心跳续约失败的比例超过85%(默认)时,它认为"不是我挂了,是网络出了故障",于是进入自我保护模式——不再剔除任何服务实例,哪怕实例的心跳已经超时。为什么这么设计?Eureka诞生于AWS——云环境里网络分区是家常便饭。如果因为短暂网络抖动就把大量健康实例踢掉,消费者拿到的实例列表会大面积缺失,大量调用直接失败,问题被放大。保护模式下的取舍是:允许消费者可能调用到一个已经挂掉的实例(需要客户端重试兜底),但绝不允许健康的实例被误杀导致大面积不可用。和Nacos临时实例的心跳剔除逻辑的区别:Eureka是"乐观存活假设"——我没收到心跳不代表你死了,可能是网络问题;Nacos是"悲观存活假设"——超时没心跳就当你死了踢掉。面试一句:Eureka的自我保护是"疑罪从无",Nacos的临时实例剔除是"疑罪从有"——两种哲学没有对错,看你更怕"误杀好人"还是"放过坏人"。
【面试题】3. Apollo和Nacos作为配置中心,在功能定位和适用场景上有什么根本区别?小团队怎么选?
【标准答案】Apollo是携程开源的纯配置中心,Nacos是阿里的"注册+配置"二合一。本质区别:Apollo定位是"配置治理平台"——提供环境隔离、灰度发布、权限审计、版本追踪、配置继承、审批流等全套配置管理功能,是给"配置需要管起来"的大团队用的。Nacos定位是"基础设施底座"——注册和配置一把梭,轻量上手、部署一条命令。选型口诀:10人以下团队→Nacos,一个服务同时搞定注册和配置,运维成本低;50人以上多团队协作、需要配置审批、需要看"谁在什么时候改了哪个配置"的完整审计→Apollo。但有个现实:大多数10~50人的团队用Nacos就够了——Nacos也有配置历史版本、命名空间隔离、监听回调,Apollo的审批流很多团队最后也没真正用起来。面试一句:Apollo是"给100个人管1000个配置"用的,Nacos是"给10个人管100个配置"用的——东西好不好,看你的规模匹配不匹配。
【面试题】4. 网关层怎么实现动态路由?路由规则改了不重启网关怎么生效?
【标准答案】Gateway的动态路由靠RouteDefinitionRepository实现。Spring Cloud Gateway默认的InMemoryRouteDefinitionRepository是内存存储——改路由必须重启。动态路由的做法是自定义一个RouteDefinitionRepository,从外部数据源(Nacos/MySQL/Redis)读取路由定义。流程:启动时从数据源加载路由→存到RouteDefinition→Gateway根据定义构建路由。路由变更时——通过Nacos监听配置变化或Redis的Pub/Sub通知→触发RouteDefinitionWriter的delete()和save()→Gateway内部发布RefreshRoutesEvent→CachingRouteLocator重建路由缓存→新请求走新路由。一个关键细节:路由变更期间已有请求不受影响(Gateway不会中断正在处理的连接),只有新请求开始按新路由匹配。另外注意路由顺序——Gateway用一个order字段排序,动态添加的路由如果order比已有路由小,会被优先匹配,小心别把/**的兜底路由顶到前面。面试一句:动态路由的本质是把路由表从"编译期常量"变成"运行时数据"——数据源一变,Gateway立即消费,核心三个字:监听、刷新、重建。
【面试题】5. Paxos和Raft两种共识算法的核心区别是什么?为什么工业界大多用Raft而不是Paxos?
【标准答案】两者都是解决分布式一致性的共识算法,都能在少于半数节点挂掉时保证数据一致。区别不在能力而在"可理解性"。Paxos是理论完美但极其难懂——Leslie Lamport的原始论文发表后学术界用了十年才完全接受。工程实现坑太多:单Paxos只对一个值达成共识,Multi-Paxos要叠加Leader选举、日志压缩、成员变更,而这些在原始论文里没定义完整——导致每个实现都有自己的"解读"。Raft的核心设计目标就是"易懂"——把共识拆成Leader选举、日志复制、安全性三个独立子问题,每个都有清晰的状态机。工业界选Raft的原因很实际:开源社区维护得起——etcd、Consul、TiKV都是Raft实现,出bug了至少能看懂代码;Paxos实现(比如Chubby)全世界只有Google等极少数公司能维护。面试一句:Paxos是"图灵奖级的学术杰作",Raft是"给普通工程师看的工程手册"——分布式系统最怕的不是选错算法而是选了一个没人能排查bug的算法。
【面试题】6. SAGA的"编排模式"和"协同模式"各是怎么实现的?两种模式分别适用什么业务场景?
【标准答案】SAGA是长事务的补偿方案,拆成多个本地事务,每个本地事务有对应的补偿操作。编排模式(Orchestration):有一个SAGA编排器(中央协调者)按顺序调用各服务——先调服务A、成功调B、成功调C,任何一步失败就反向调用补偿:C的补偿→B的补偿→A的补偿。像一个指挥家拿着乐谱指挥乐队。优点是流程集中、图看得到全貌,缺点是编排器是单点和瓶颈。协同模式(Choreography):没有中心协调者,每个服务干完自己的活发事件,由事件驱动下一个服务——A完成发"订单已支付"事件→B监听到执行→B完成发"库存已扣减"事件→C监听到执行。任何一步失败就发补偿事件,各服务自己监听补偿。像舞者之间靠眼神和动作交流,没有指挥。协同模式优点是松耦合、无单点,缺点是流程散落各服务——你没法一眼看到整个SAGA走完了没有、卡在哪一步。选型简单:流程频繁变化(电商营销规则一月三变)→编排模式,改编排器就行;流程稳定、团队自治要求高→协同模式。面试一句:编排是"宪法"——权力集中但好管理,协同是"市场"——各自为政但抗风险。
【面试题】7. 雪花算法的时钟回拨有哪几种常见的解法?美团Leaf和百度UidGenerator分别是怎么处理的?
【标准答案】雪花算法依赖时间戳保证递增,时钟回拨(NTP校时导致当前时间小于上次生成ID的时间)会导致ID重复。五种解法逐级递进。方案一:抛异常——最简单粗暴,回拨就直接抛RuntimeException,等时钟追上再恢复。适合允许短暂不可用的业务。方案二:等待追上——循环休眠等时钟追上,但回拨太多等太久就投降了。方案三:用回拨前的最大时间戳继续生成——回拨期间用上次记录的时间戳继续,只要回拨窗口内没有把序列号用完就安全。方案四:扩展位——预留几个bit专门做时钟回拨版本号,每次回拨版本号+1塞到ID里,保证唯一。方案五:不从系统时钟取时间——这也是Leaf的"雪花+号段混合"方案:WorkerID用ZK分配,但时间戳不是System.currentTimeMillis(),而是Leaf启动时记一个基准时间,ID的时间戳部分是"距基准时间过了多少毫秒"——这个值只会往前不回退。百度UidGenerator的CachedUidGenerator用的是方案三+RingBuffer预生成——预先生成一批ID放RingBuffer里,回拨时RingBuffer里还有存量ID可以继续消费,给时钟追上争取时间。面试一句:时钟回拨是物理问题,纯软件解法都是"拖延+兜底"——最好的解法是把NTP配置成slew模式(微调而不是跳拨)从源头减少回拨。
【面试题】8. 事件溯源(Event Sourcing)的核心设计是什么?Event Store有什么特殊要求?和传统CRUD比起来最大的代价在哪?
【标准答案】事件溯源的核心思想是"不存当前状态,存状态变更的历史事件"——账户余额不存一个100的数字,而存"充值+200、消费-50、退款+30……"一系列事件。当前余额是所有事件replay重放的结果。Event Store的特殊要求:它是只追加不可修改不可删除的永久日志——技术上选EventStoreDB(专为此设计)或Kafka(天然只追加+支持replay)。和CRUD的根本区别:CRUD是"你问我余额,我告诉你一个数字";事件溯源是"你问我余额,我把自开户以来所有交易重算一遍告诉你"。最大代价两个:一是查询复杂度爆炸——CRUD一个SQL查出余额,事件溯源要重放全量事件,必须用快照(Snapshot)每隔N个事件存一次状态快照,查询时加载最近快照+重放后续事件,不然性能崩。二是事件Schema演进——事件一旦写入就不可改,但业务需求会变——当初"充值事件"就一个amount字段,后来加了个"支付渠道"字段。旧事件没这个字段,replay时消费旧事件需要兼容默认值。面试一句:事件溯源是"用空间和计算换审计和溯源能力"——不是所有业务都需要,但金融、审计、库存追溯这些场景下,事件溯源不只是一种技术选择,而是一种合规要求。
【面试题】9. 跨服务数据一致性怎么在"不做分布式事务"的前提下保证?对账系统的设计思路是什么?
【标准答案】不做分布式事务靠"最终一致性+对账兜底"两条腿。最终一致性这条腿:本地事务+异步消息——订单服务在同一个本地事务里写完订单+发消息到MQ,库存服务消费消息扣库存。关键点:本地事务和发消息必须原子——用RocketMQ的事务消息(半消息→执行本地事务→提交/回滚)或"本地事务表+定时轮询补发"。这就是"尽可能实时达成一致"。对账兜底这条腿:假设上面的事可能还会失败(MQ丢消息、消费端挂了没ACK、补偿逻辑有bug),就需要T+1对账作为兜底防线。对账系统设计四个步骤:第一步数据抽取——凌晨低峰期从订单库和库存库各拉一份变动明细;第二步数据标准化——两边数据格式不一样(订单侧的order_id=库存侧的source_order_id),统一成标准模型;第三步差异比对——按订单号双loop比对,标记出"订单已支付但库存未扣"、"订单已取消但库存未还"等差异类型;第四步差异处理——自动修复(重放消息)+人工修复(差异超过阈值比如金额>1000的走人工审核)。核心设计细节:对账一定要用"离线批处理"而不是实时——实时对账本身就是分布式事务变种;差异表要保留完整历史,每周复盘看哪些差异高频出现,反推修复产生差异的根因。面试一句:对账系统是分布式系统最后的底线——承认完美的分布式一致性不存在,用"事后发现+事后修正"补上。
【面试题】10. 线上CPU突然飙到100%,你作为值班工程师怎么在5分钟内定位到是哪段代码导致的?排查思路和工具链是什么?
【标准答案】5分钟排查黄金流程。第1分钟——top确认:top -H -p <pid>看是Java进程飙升还是其他进程,按线程CPU使用率排序,拿到CPU最高的线程ID(十进制)。第2分钟——线程转十六进制:printf '%x\n' <tid>把线程ID转十六进制,因为Java线程栈里展示的是十六进制nid。第3分钟——jstack取线程栈:jstack <pid> | grep -A 30 <hex_tid>直接定位到具体线程在跑什么代码。如果发现是GC线程飙高→切内存排查路线;如果是业务线程→看栈帧里卡在哪个方法。第4分钟——arthas加持(如果装了就快10倍):thread -n 3直接列出CPU最高的3个线程+完整栈帧,一键到底;dashboard看整体JVM状态;怀疑是某个方法热点→monitor -c 5 <全限定类名> <方法名>看调用次数和耗时。第5分钟——根因判断:常见三类——死循环/HashMap死链(CPU一直100%不降)→栈帧里有业务循环代码;频繁Full GC(CPU锯齿状100%~50%波动)→dump堆 jmap -dump:live,format=b,file=heap.hprof <pid> 拿MAT分析;正则回溯/JSON大对象解析(CPU在某个方法上持续高但没死循环)→优化正则或用流式解析。工具链:轻量→top+jstack;重量→arthas+jprofiler;事后→堆dump+MAT分析。面试一句:CPU飙高不可怕,怕的是没有日志抓手——上线前至少配好arthas的启动脚本,线上出问题ssh上去10秒内进诊断,5分钟定不到根因就是技术债。
📋 考点覆盖:微服务架构设计原则(微服务/单体/模块化单体决策)→ 服务注册与发现(Eureka自我保护机制)→ 配置中心(Apollo vs Nacos选型对比)→ 服务网关(动态路由+热更新)→ 分布式核心理论(Paxos vs Raft共识算法)→ 分布式事务方案(SAGA编排vs协同模式)→ 分布式ID生成(雪花算法时钟回拨解法)→ 消息驱动架构(事件溯源Event Sourcing核心设计)→ 跨服务数据一致性(对账系统设计)→ 线上故障排查与调优(CPU飙高5分钟排查流程)
题型分布:概念题(2) | 原理题(5,7) | 方案对比题(1,3,6) | 架构设计题(4,8,9) | 故障排查题(10)
难度递进:基础(1-3) → 进阶(4-7) → 高阶架构(8-10)