Skip to content

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

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


【面试题】1. 微服务之间通信什么时候该用同步 RPC、什么时候该用异步消息?怎么判断一个接口到底走同步还是异步? ​

【标准答案】三看原则。一看"调用方要不要等结果"——用户下单页必须同步拿到"扣库存成功"才能跳支付,这种要结果的走 Feign/Dubbo 同步 RPC;发短信、送积分这些旁路操作不需要等结果,走异步消息广播。二看"是不是同一条核心链路"——下单→扣库存→生成订单是紧耦合链路,失败要整体回滚,走同步配合分布式事务;用户注册后"送新人礼包+发欢迎短信+上报大数据"三个操作互相独立,最适合异步事件广播。三看"下游响应时延"——下游慢会传染上游的场景必须异步解耦,比如支付回调后通知 WMS 发货,WMS 可能 30 秒才回,同步调的话支付接口 P99 直接拉到 30 秒。一个经常被忽略的坑:全链路异步后 trace 断得到处都是,排查成本翻倍——事件风暴看着高级,但出问题时没人能一眼说出"这笔订单到底经历了什么"。面试一句话:要结果+核心链路+响应快 → 同步;可延迟+旁路链路+响应慢 → 异步。异步好,但别滥。


【面试题】2. Nacos、Eureka、Consul 的健康检查机制有什么区别?为什么"心跳上报"和"服务端探测"是两种哲学? ​

【标准答案】三种注册中心代表了两种健康检查哲学。心跳上报派(Nacos 临时实例 / Eureka):服务自己定期给注册中心报"我还活着",注册中心只管收心跳——挂了就等心跳超时窗口过去再剔除。好处是注册中心压力小、不需要知道你的应用细节;坏处是端口通≠服务可用——线程池满了照样能发心跳。服务端探测派(Consul):Consul Agent 主动定期调你的 /health 接口(HTTP/TCP/Script),返回码不对就标不健康。好处是能感知 DB 挂了、死锁了、线程池溢出了——你写的健康检查自己会查这些;坏处是 1000 个实例每 10 秒探测一次,Server 网络 IO 成为瓶颈。Nacos CP 模式走了中间路线——持久实例用 TCP 探测只做基本连通检查。选型结论:实例量小且对健康精准度要求极高 → Consul;实例量大追求简单 → Nacos 心跳模式。面试金句:健康检查的代价和精准度成正比,没有完美方案——心跳是"我相信你说的话",探测是"我亲自去查",哪个更合适取决于你的规模和对故障敏感的底线。


【面试题】3. 一致性哈希和普通哈希取模有什么本质区别?它在微服务负载均衡和分布式缓存中各有什么应用? ​

【标准答案】普通哈希取模 hash(key) % N——N 从 3 变 4 时几乎所有 key 的映射位置全变,缓存大面积失效→DB 瞬间雪崩。一致性哈希把哈希空间变成一个环(0~2^32-1),节点和 key 都映射到环上,key 顺时针找最近的节点。扩容加一台新节点时,只影响新节点和它逆时针上一个节点之间的 key——约 1/N 的缓存失效,不会全军覆没。虚拟节点补刀:节点少时数据可能歪(节点 A 管半个环),给每个真实节点创建一堆虚拟节点散布到环上,负载就均匀了。微服务中的应用:Dubbo 一致性哈希负载均衡——同一个 userId 总是路由到同一台服务实例,天然亲和,适合 WebSocket 等有状态服务;Kafka 消息分区——hash(key) % partition 保证同一 key 进同一分区,有序消费;网关做粘性会话——IP Hash 的一致性哈希版本,会话保持不丢。面试关键词:"哈希环""顺时针找最近节点""虚拟节点""只有 1/N 受影响"——四个词说清楚就能让面试官点头。


【面试题】4. Spring Cloud Gateway 怎么设计一套生产级统一认证授权体系?JWT 校验、RBAC 权限检查、用户信息透传的完整链路是什么? ​

【标准答案】三步走:认证→授权→透传。第一步认证(GlobalFilter,order=-1):网关拦截请求,从 Header 取出 Authorization→解析 JWT→验证签名和过期时间。失败直接 401 返回,绝不往下游走。把解析结果(userId/roles)存到 ServerWebExchange.attributes,后面 Filter 复用不再解析。第二步授权(紧接认证后):从 JWT 取角色,和当前 URL 匹配——/api/admin/** 要 ROLE_ADMIN,不够格直接 403。如果权限规则复杂(RBAC),网关内存缓存权限字典 + 配置中心动态刷新,避免每次查 DB。第三步透传:认证通过后把 X-User-Id、X-User-Name 塞进下游请求头,下游用 @RequestHeader 直接拿,不再解析 JWT。三个硬核设计点:一,白名单 URL(登录/注册/health)在认证 Filter 里豁免,配成 excludePathPatterns;二,JWT 公钥验签放网关本地,别每次调认证中心——否则认证中心成了新的单点;三,权限颗粒度分两层:网关只做"有没有权限进入这个服务"(粗粒度),下游自己做"能不能操作这条订单"(细粒度业务授权)。面试总结:网关是安全边界,做统一认证拦截;下游信任透传的用户信息,聚焦业务规则。


【面试题】5. 配置中心底层怎么把变更推给客户端?长轮询、长连接推送、定时拉取三种方案的延迟和选型逻辑? ​

【标准答案】延迟递减、复杂度递增。定时拉取(最原始):客户端每 N 秒调 Server 拉一次对比版本号——N=5 时最大延迟 5 秒,N 越小 Server 越扛不住,配置中心基本没人用。长轮询(Apollo / Nacos 1.x 默认):客户端发请求,Server 不立刻返回,hold 连接 60 秒——60 秒内有变更立刻返回,没变更 60 秒后返回空→客户端立刻发起下一个长轮询。延迟近实时(Server 收到变更马上通知正在 hold 的连接),且 HTTP 协议防火墙全放行。长连接推送(Nacos 2.x gRPC):客户端和 Server 建一条持久 gRPC 双向流,变更时 Server 直接 push——延迟最低,毫秒级。代价是 gRPC 需 HTTP/2,中间代理和防火墙可能拦。Apollo 没走长连接因为启动早(2016),HTTP 长轮询是当时最稳妥选择。选型:内网秒级延迟够用 → 长轮询;需要毫秒级推送(比如风控规则秒级生效)→ Nacos 2.x gRPC;小规模内网甚至定时拉都够。面试金句:配置推送的本质是用复杂度换延迟——长轮询是"假装 push 的 pull",长连接是真正的 push,多的 gRPC 组件不是每个团队都能接住。选你团队运维得动的方案。


【面试题】6. 可靠消息最终一致性和最大努力通知都是"异步保证最终一致",核心区别在哪?各自用在什么场景? ​

【标准答案】区别一句话:发送方是主动兜底还是"我尽力"。可靠消息最终一致性(本地消息表 / RocketMQ 事务消息):上游执行业务成功了一定要把消息送到下游——手段是本地事务原子写入消息记录、定时任务重试、下游幂等消费,100% 保证送达到并处理成功。典型场景:支付成功→积分服务必须加积分,丢了消息就是生产事故。最大努力通知:上游只做"我尽力调你几次",调不通就算了——比如支付宝扣款成功后会调你的回调 URL,调不通按指数退避重试最多 N 次,还是通不了就停了,等你主动调查询接口来问结果。所以叫"最大努力"——不保证 100%。适用场景:外部系统回调、SMS 送达通知——上游不关心你收没收到,你收不到可以自己查。二者本质区别是对"未送达到"的兜底责任归属——可靠消息是发送方闭环兜底(我保证送到),最大努力通知是把兜底转移给接收方(我通知你了,没收到你来问我)。面试一句话:本地消息表解决"我死活要让你知道",最大努力解决"我跟你说了你没听清自己来问"。


【面试题】7. Resilience4j 熔断器的 CLOSED→OPEN→HALF_OPEN 三种状态是怎么转换的?和 Sentinel 的熔断有什么关键差异? ​

【标准答案】Resilience4j 是最经典的三态熔断器。CLOSED(正常):所有请求通过,后台滑动窗口统计失败率。失败率 ≥ 阈值(如 50% 异常或 50% 慢调用)→ 切换到 OPEN。OPEN(熔断打开):所有请求直接拒绝抛异常,不碰下游。等待一个固定时间窗口(如 60 秒)→ 切换到 HALF_OPEN。HALF_OPEN(半开探测):放少量请求试探下游恢复了没有(默认环形缓冲 10 个位置)——成功了 ≥ 阈值 → CLOSED 恢复;失败了 → 回到 OPEN 继续等。核心精妙:HALF_OPEN 是全量恢复前的"侦察兵阶段",避免恢复瞬间大量流量把刚缓过来的下游又打挂。和 Sentinel 的差异:Sentinel 没有显式的 HALF_OPEN 状态——OPEN 后按"熔断时长"自动进入探测,探测通过就关闭。语义等价但模型不同——Resilience4j 建模更标准教科书式,Sentinel 把探测逻辑内嵌在熔断状态内部。另一个差异:Resilience4j 支持慢调用比例熔断;Sentinel 额外支持异常比例+异常数+慢调用比例的组合——更灵活。选型:Spring WebFlux 栈 → Resilience4j(原生 Reactive 支持);Spring Cloud Alibaba 栈 → Sentinel(无感集成)。面试一句定音:名不同本质同——"失败率高就闭嘴→试探→恢复或继续闭嘴"这个循环是所有熔断器的通解,差异在参数和接口形态。


【面试题】8. 分布式锁的公平锁和非公平锁有什么区别?Redisson 用 Redis 怎么实现公平锁?性能差距多少? ​

【标准答案】公平锁:按请求时间排队,先到先拿。非公平锁:谁手快谁拿,不管顺序。JUC 的 ReentrantLock 里差异可忽略——排队就是内存操作。分布式锁里差异巨大——排队就是网络 IO。Redisson 用 Redis 实现公平锁:不是简单的 SETNX 竞争,而是用 Redis List 当等待队列 + Pub/Sub 当通知。加锁时 RPUSH queue clientId 把自己塞队列尾,然后检查 LINDEX queue 0 == 自己——是队头就拿锁,不是就订阅一个 Pub/Sub 频道阻塞等通知。释放时 LPOP 移除自己,Pub 事件唤醒队列头部的下一个等待者。这样避免了所有竞争者疯狂 SETNX 空转。Redisson 非公平锁就是标准 SETNX + Lua + 自旋重试,本地自旋直到抢到或超时。性能差距:公平锁至少多 3 次 Redis 交互(入队+检查+订阅),而非公平锁就一次 SETNX。微服务场景下 90% 不需要公平锁——如果 10 个请求同时在抢同一把锁,说明你得缩小锁粒度或异步化。公平锁只在"执行顺序有严格业务要求"时用——比如流水编号必须严格递增。面试核心:分布式公平锁是用性能买顺序,排队本身是一次分布式协调——只在"必须有序"时开这个成本,白嫖党不要用。


【面试题】9. 美团 Leaf 的号段模式为什么一次取一批 ID 而不是每次取一个?双 Buffer 是怎么做到发号期间零阻塞的? ​

【标准答案】每次取一个 ID:一次 SQL → 一次网络 IO → QPS 上限就是 DB 单次查询 QPS(几千)。双 Buffer + 异步预加载后:发号变纯内存操作,单机 QPS 百万级。流程:Leaf 启动时从 DB 拿号段(如 1~1000),存到 SegmentBuffer 里——current 存当前号段,next 存备用号段。发号时只做 AtomicLong.incrementAndGet() 从 current 取号。当 current 发到 90% 时触发异步线程去 DB 加载下一段(1001~2000)到 next。current 发完后指针交换——next 变 current,新的 next 再由异步线程后台加载。整个过程发号线程永远不等 IO,无锁、无阻塞。核心设计是"预加载的触发点"——不是 current 发完了才去加载,而是发到 90% 就去加载。等到发完再加载就卡住了。号段大小是关键参数——设 1000,TPS=1000 时一秒查一次 DB;设 10000,十秒查一次。号段越大 DB 压力越小,但 Leaf 挂了重启会浪费一段号(没发完但内存没了)。经验值:设 60 秒的量,高峰期号段大抗流量,低峰期号段小减少浪费。面试亮点:你能说出"双 Buffer 交换指针 + 90% 触发异步预加载 + AtomicLong 无锁发号"这三个机制,面试官就知道你不是背概念是真看过源码。


【面试题】10. 事件溯源(Event Sourcing)怎么做"从事件流重建聚合状态"?快照机制如何避免每次都重放全量历史事件?它的生产落地最大挑战是什么? ​

【标准答案】事件溯源的核心:不存当前状态,只存"发生过什么事件"——改名不是 UPDATE SET name='新名' 而是存 UserRenamed(old='旧', new='新', time=...)。当前状态从事件流重建:从第一个事件开始重放,UserCreated→UserRenamed×5→UserChangedPhone×3→聚合到最新状态。问题:一个用户三年几千条事件,每次查询都重放几千条——不可能。解法:快照机制——每 N 个事件拍一张状态快照存起来(Redis 或快照表),重建时只加载最近快照(版本 200)+ 从 201 号事件起增量重放,最多重放几十条就到最新。生产三个大坑。一是事件版本升级:v1 的 UserCreated 只有 name 字段,v2 加了 phone,历史事件没有 phone——重建时怎么处理?解法是事件 upcaster——先把 v1 事件转成 v2 再重放。二是事件不可变:事件一旦写入不可修改,业务补偿不能"改事件"只能"追加新事件"——UserRenamed 写错了不能改,只能追加一条 UserNameCorrected。这要求业务从一开始就设计好补偿事件类型。三是查询复杂:没有"当前用户表"你怎么查"所有手机号 138 的用户"?要整 CQRS——事件落库后异步更新一份读模型(ES 或视图表)。一句话定音:事件溯源是"时间机器的数据库"——你存变化而不存结果,换来了不可逆审计和任意时间点回溯,代价是查询和存储膨胀。国内极少全套事件溯源,更多是在关键实体上存关键事件做审计,状态还是正常存。