Skip to content

微服务面试题 - 2026-06-19 18:49 ​

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


【面试题】1. DDD的限界上下文和微服务的服务边界是一一对应的吗?怎么用DDD的方法论来指导微服务边界划分? ​

【标准答案】不是一一对应,但方向一致。限界上下文是DDD里"一个领域模型明确有效的边界"——同一个词在不同上下文里含义不同,比如"订单"在交易上下文里是交易单,在物流上下文里是运单。微服务边界通常在限界上下文基础上再聚合——一个微服务可以包含1~3个关系紧密的限界上下文(交易+结算在一个服务里),但不要让一个限界上下文跨两个微服务(交易上下文一半在A服务一半在B服务,联调灾难)。实战划分三个步骤:第一步事件风暴——把业务专家、产品经理、开发拉到一起,用橙色便签贴业务事件("订单已提交""支付已完成""库存已扣减"),梳理出完整的业务流程。第二步识别聚合——把事件按操作同一个实体(订单、库存、用户)聚类,找出聚合根(Aggregate Root),聚合是数据一致性的最小单元。第三步划定上下文边界——看聚合之间的耦合类型:强数据耦合(总是join查对方表)→大概率同一个上下文;弱事件耦合(你变了我只需要知道事件)→天然边界,可以拆。最终原则:聚合内的数据必须强一致(用同一个数据库事务),跨上下文只允许最终一致(用消息事件)。面试一句话:DDD限界上下文是"语言统一"的工具,微服务是"部署单元"的决策——前者帮你找到边界在哪,后者决定谁能独立上线。


【面试题】2. 什么是服务雪崩?级联故障有哪几种典型的传播路径?怎么从架构层面兜底? ​

【标准答案】服务雪崩就是"一个服务挂了,拖死全家"。比如服务A依赖服务B,B依赖C。C的DB慢查询导致C响应变慢→B调C的线程全堵在等C返回→B的线程池打满→A调B也跟着超时→A的线程池也打满→整个调用链崩溃。三种传播路径。路径一:线程池饥饿传播——下游变慢导致上游线程全卡在等待,最经典也最常见。路径二:重试放大——上游超时重试3次,B原本每秒100个请求,C变慢后B的超时重试把请求量放大到每秒300,C直接被打死。路径三:共享资源耗尽——多个服务共享一个连接池/线程池/Redis实例,一个服务突然疯狂打Redis把连接池占满,其他服务用不了Redis也跟着挂。架构层兜底三板斧:第一板斧——熔断,下游错误率超过阈值直接快速失败不走真实调用,防止线程阻塞;第二板斧——限流,上游给自己加QPS上限,超过就拒绝,宁可拒掉一部分请求也不能放进去把线程池打满;第三板斧——降级,熔断打开后走兜底逻辑(返回缓存数据、默认值、静态页面),保证核心链路至少还有一口饭吃。面试一句话:雪崩的本质是"故障的级联放大效应"——没有隔离机制的系统,任何一个服务故障都会变成全局故障。


【面试题】3. 分布式系统里的BASE理论到底是什么?它的"软状态"和"最终一致性"和CAP怎么对应?工程上怎么落地? ​

【标准答案】BASE是Basically Available(基本可用)、Soft State(软状态)、Eventually Consistent(最终一致性)的缩写,是CAP定理的工程折中方案。和CAP的对应关系:CAP告诉你"网络分区发生时C和A只能二选一",BASE告诉你"选A之后怎么补救C"——选了AP(高可用+分区容忍),牺牲了强一致性,用最终一致性补上。软状态(Soft State)就是"允许系统存在中间态"——比如订单状态从"待支付"到"已支付"中间可能短暂存在几分钟"支付中"数据还没同步完的状态,这和ACID里的"事务未提交外界不可见"截然相反。最终一致性的含义是"不保证你下一秒看到最新数据,但保证你没有新数据进来后最终会追平"。工程落地三步:第一步消息驱动,写操作完成发事件,各服务异步消费更新自己数据,允许短暂不一致;第二步对账兜底,T+1批处理扫描差异自动修复,作为最终一致性的最后防线;第三步"读自己的写"问题,用户写完立刻查可能看到旧数据,用写操作直接返回结果或短期缓存来规避。面试一句话:ACID是银行柜台的铁栅栏,BASE是菜市场的信任交易——一个靠刚性规则,一个靠有人担保最后对得上账。


【面试题】4. Consul的健康检查机制有什么独特设计?和Nacos的临时实例心跳机制相比,各自的优劣势是什么? ​

【标准答案】Consul的健康检查不走"客户端主动上报"这一路,而是让Consul Agent主动探测。架构:每个服务节点上跑一个Consul Agent,注册服务时定义健康检查方式——HTTP探测(GET /health返回200)、TCP探测(端口通)、脚本探测(执行脚本返回0)、TTL探测(服务自己定期调/agent/check/update上报存活)。Agent在本地执行检查,结果上报给Server,避免了网络抖动导致"心跳没到Server=误判宕机"。Nacos临时实例是客户端主动发心跳到Server,Server等心跳超时就踢。Consul的优劣:优势是"被动探测"更准确——可以检查/health端点真实返回,不光看程序活着不活,还能检查DB连接、缓存状态等深度指标;Agent部署在本地,探测结果不受跨数据中心网络抖动干扰。劣势是运维复杂度高一截——每个节点都要装Agent,服务要提供健康检查端点;Nacos一个sdk引用就完事了。选型:多数据中心、对节点健康状态准确度要求高→Consul;中小规模、快速上手、和服务发现注册一体化→Nacos。面试一句话:Consul用的是"我来看你死没死"的主动体检模式,Nacos用的是"你定期向我报平安"的被动心跳模式——前者更准但代价更大。


【面试题】5. 线程池隔离和信号量隔离分别是怎么做的?Sentinel和Hystrix在这两种隔离策略上有什么根本不同? ​

【标准答案】线程池隔离:给每个依赖服务分配一个独立的线程池。服务A调服务B走B专属线程池,调服务C走C专属线程池。好处是B线程池打满不影响C——物理隔离。代价是每个线程池占用内存(默认10个线程×多个依赖),线程池多了线程上下文切换开销大。信号量隔离:不分配独立线程,而是在调用路径上放一个计数器(信号量),请求来先获取许可,许可数超过阈值就拒绝。好处是零线程开销、延迟低,坏处是信号量满了会导致调用线程阻塞——如果下游超时时间设太长,阻塞会扩散到上游。Sentinel和Hystrix的根本不同:Hystrix默认线程池隔离,优点隔离彻底、支持异步超时中断,缺点线程开销大。Sentinel默认信号量隔离,因为Sentinel的核心是"统计+决策"而非线程资源隔离——它认为线程池隔离太重,靠熔断+限流规则就能防雪崩,不需要线程物理隔离。但Sentinel也支持线程池隔离(通过ThreadPoolFlowRule),只是默认不走。选型建议:依赖多且响应时间差异大(有的毫秒返回有的几秒)→线程池隔离;依赖少且都是低延迟调用→信号量隔离就够了。面试一句话:线程池是"高风险人群隔离包间",信号量是"排号限流绳"——前者隔离彻底代价高,后者轻量但依赖超时控制。


【面试题】6. 平滑加权轮询算法(Smooth Weighted Round Robin)的底层是怎么实现的?Nginx的平滑加权和Dubbo的加权随机有什么本质区别? ​

【标准答案】平滑加权轮询的核心要求是"权重越高的节点被选中的次数越多,但分布要尽可能均匀,不能连续选中同一个节点"。实现用每个节点的"当前权重"动态排序:初始时每个节点有个静态权重(configWeight)和动态权重(currentWeight,初始=0)。每次选择:所有节点currentWeight += configWeight→选currentWeight最大的节点→该节点的currentWeight -= 所有节点configWeight之和。举例:A权重5、B权重1、C权重1,总和7。第1轮:A=5,B=1,C=1→选A→A=-2。第2轮:A=3,B=2,C=2→选A→A=-4。第3轮:A=1,B=3,C=3→选C→C=-4。走7轮正好A出现5次、B和C各1次,且A不会连续出现超过2次。Nginx的Smooth Weighted Round Robin用的就是这个算法,Dubbo也有一个RoundRobinLoadBalance实现类似的。而Dubbo的RandomLoadBalance(加权随机)的本质完全不同——不是排队轮询而是每次独立随机,权重只是作为被选中的概率。比如权重5:1:1,随机模式下可能前3次全是A(概率很大),而平滑加权轮询一定7次里A只出现5次。选型:请求量小、需要即时均匀→平滑加权轮询;请求量巨大、统计意义均匀→加权随机也够用且并发更高(无状态)。面试一句话:平滑加权轮询是"你欠我一个公平",加权随机是"大数定律下的公平"——量小了前者准,量大了没区别。


【面试题】7. 服务网关怎么设计统一的异常处理?下游微服务返回的各种异常(业务异常、参数校验、系统异常、超时)怎么统一包装成对前端友好的规范响应? ​

【标准答案】网关统一异常处理分三层,对应四种异常类型。第一层预过滤:请求还没到下游就出错——参数校验失败、Token过期、IP黑名单。这由Gateway的全局Filter拦截,直接构造响应返回,response.setStatusCode(400/401/403)然后writeWith把JSON错误体写进去。第二层下游异常捕获:请求已经转发到下游,下游返回了4xx/5xx。Gateway默认不处理直接透传给前端(原始Tomcat异常页面甚至Java堆栈,前端没法解析)。解决:实现ErrorWebExceptionHandler来接管全局异常,从ServerWebExchange里拿到下游响应体,解析出下游统一返回的错误码和错误信息,重新包装成规范格式{"code":"xxx","message":"xxx","requestId":"xxx"}。关键是要区分四种下游异常:业务异常(code=BIZ_001,库存不足等,正常返回前端)、参数校验(code=VALIDATE_FAIL,返回字段级错误提示)、系统异常(code=INTERNAL_ERROR,不返回内部细节前端显示"系统繁忙"但日志记详情)、超时/连接拒绝(网关自己生成code=GATEWAY_TIMEOUT)。第三层兜底:网关自身异常(连接超时、线程打满),同样走ErrorWebExceptionHandler兜底。规范响应体至少包含code、message、data、traceId、timestamp五个字段,TraceId串联日志排查。面试一句话:网关是前端的最后一道防线——不能让前端的同事对着Java堆栈一行一行猜哪里出错了。


【面试题】8. 2PC两阶段提交在微服务场景下为什么几乎不可用?有哪些致命缺陷?可靠消息最终一致性是怎么替代它的? ​

【标准答案】2PC在微服务场景有四个致命缺陷。缺陷一:同步阻塞——一阶段Prepare时所有参与者锁资源(数据库行锁),等协调者二阶段决定Commit还是Rollback。如果协调者挂了,参与者的锁一直不释放,其他事务全堵死,这就是"协调者单点故障导致全局资源死锁"。缺陷二:网络不确定性放大——一阶段超时,协调者不知道参与者是没收到请求还是执行完没返回,二阶段到底发Commit还是Rollback?这就是2PC的"超时决策困境"。缺陷三:参与者在微服务里是不同数据库、不同系统——Prepare在MySQL里成功、在Redis里失败、在第三方支付系统里根本没法做Prepare。异构系统不支持统一的Prepare语义。缺陷四:数据不一致窗口——协调者发Commit,部分参与者收到Commit提交成功,部分因为网络丢包没收到Commit,协调者挂了没法重发,数据永久不一致。可靠消息最终一致性替代方案:本地事务+事务消息。生产者在本DB事务里写完业务数据+在本地消息表写入一条记录(同一事务保证原子),一个定时任务扫消息表把"待发送"消息发到MQ,消费者消费。宕机恢复依赖消息表重试——没发出去的重发,消费失败的ACK重试。和2PC本质区别:2PC是"大家一起同步等最终裁决",可靠消息是"各自先干了再说,事后靠消息保证最终一致"。面试一句话:2PC在微服务时代是"CP思维在AP世界的强求"——你没办法让MySQL和Redis一起做Prepare,但你可以让MySQL写完后发条消息给Redis的消费者。


【面试题】9. 配置中心怎么实现配置的灰度发布?一个新配置(比如某个限流阈值从100改成50)怎么先在小范围机器上验证再全量推? ​

【标准答案】灰度配置发布的核心是"配置和机器的标识绑定"。以Nacos为例,实现分三步。第一步打标签:在部署时为灰度机器打上特定标签——K8s里打gray: true的Label,或Nacos服务注册时给实例加metadata: {env: gray}。第二步配置命名空间+标签路由:创建一个灰度命名空间namespace=gray,灰度配置先在这个命名空间发布。业务应用配置spring.cloud.nacos.config.namespace指向正式命名空间,灰度机器指向灰度命名空间——不同命名空间隔离,灰度配置不影响正式环境。Apollo走的是"配置灰度发布"功能——在已有正式配置上新建灰度版本,指定灰度规则(比如按IP列表、按标签选中灰度机器),灰度机器会拉取到灰度版本的配置。第三步验证→全量:观察灰度机器上的指标——限流阈值改成50后,灰度机器上的QPS、错误率、RT是否异常?确认没问题后在Apollo/Nacos里点"全量发布",灰度配置覆盖正式配置。回滚机制:灰度配置出问题了,直接删除灰度版本或回退配置,业务服务30秒内watch到变更自动恢复。关键细节:灰度配置的生效延迟取决于客户端轮询间隔和刷新机制——默认Nacos客户端每10秒拉一次最新配置,@RefreshScope踩过的坑这里又要重温一次。面试一句话:配置灰度发布就是给配置发"内测邀请"——先给少数人试用,没炸再推给所有人,配置中心的命名空间和标签路由就是分发渠道。


【面试题】10. 线上服务突然OOM(OutOfMemory),你作为值班工程师怎么在5分钟内定位到是哪个对象把内存吃光了?排查思路和工具链是什么? ​

【标准答案】OOM排查五步黄金流程,每一步都在争取时间。第1秒——保现场:jcmd <pid> GC.heap_dump -all /tmp/heap.hprof在OOM发生前就dump堆,或者JVM启动加-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/让OOM自动dump。有了hprof文件,服务可以立刻重启止损——排查可以离线干,不用挂着OOM等分析。第2步——MAT分析:把hprof拉到本地用Eclipse MAT打开,点击"Leak Suspects"——MAT自动给出"什么对象占内存最大"和"谁持有了它"的可疑泄漏报告。最常见凶手:一个大HashMap里塞了几百万条没清理的缓存、一个无限增长的ThreadLocal变量、一个从未关闭的InputStream。第3步——看引用链:在MAT里选Dominator Tree(支配树),找Retained Heap最大的对象,右键"Path to GC Roots"→"exclude weak references"——得到一条清晰的引用链:谁→谁的成员变量→谁的Map→谁的List→具体哪个对象把内存干爆了。第4步——代码定位:根据引用链里的类名和方法栈定位到代码,查找"这个Map/List为什么没设上限"或者"为什么这个对象没被GC"。第5步——Arthas在线证实:memory命令看堆内存分布,vmtool --action getInstances --className xxx.xxx.xxx --limit 10直接查看可疑对象的数量和内容。如果在OOM发生前线上的Arthas用heapdump也能在线dump。面试一句话:OOM排查的本质是"找谁占内存最多+找谁的不释放引用了它"——MAT的Dominator Tree给你答案,代码引用链告诉你怎么改,两个加一起5分钟足够定到具体代码行。


📋 考点覆盖:微服务架构设计原则(DDD限界上下文与微服务边界)→ 服务雪崩与级联故障防护(传播路径+兜底三板斧)→ 分布式核心理论(BASE理论工程落地)→ 服务注册与发现(Consul健康检查vs Nacos)→ 服务熔断降级限流(线程池隔离vs信号量隔离)→ 负载均衡策略(平滑加权轮询算法实现)→ 服务网关(统一异常处理设计)→ 分布式事务方案(2PC致命缺陷+可靠消息替代)→ 配置中心(配置灰度发布全流程)→ 线上故障排查与调优(OOM排查5步流程)

题型分布:概念题(1,3) | 原理题(2,4,6) | 方案对比题(5,8) | 架构设计题(7,9) | 故障排查题(10)

难度递进:基础(1-3) → 进阶(4-7) → 高阶架构(8-10)