Skip to content

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

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


【面试题】1. 微服务为什么要做服务无状态化?登录态、WebSocket 长连接这种"有状态"场景怎么处理? ​

【标准答案】无状态意味着每个请求都是独立的,服务实例不存储和上下文绑定的数据,任意实例都能处理请求。核心好处三个:弹性扩缩容——新实例起来就能干活,不需要"找回之前的状态";负载均衡简单——请求随便分发,不用担心打到错误实例;故障恢复快——实例挂了直接换一个,用户无感。登录态处理:JWT Token 把用户信息编码在 Token 里,客户端每次带上来,服务端不用 Session 存储,实例间无状态;或者把 Session 外部化到 Redis 统一存储。WebSocket:建立连接时在网关层根据 userId 一致性哈希路由到固定实例,实例维护连接状态,挂了自动重连到新实例。一句话:状态能推到客户端就推客户端,推不了的推到中间件,把服务本身做薄。


【面试题】2. Nacos 的临时实例和持久化实例有什么区别?底层健康检查机制分别是怎样的? ​

【标准答案】临时实例(ephemeral=true):基于心跳续约,客户端每 5s 发心跳,Nacos 15s 收不到就踢掉实例。走的是 Nacos 自研的 Distro 协议(AP 模式),数据在集群节点间异步同步,强调可用性。持久化实例(ephemeral=false):不走心跳续约机制,由 Nacos 服务端主动健康检查——定期 TCP/HTTP/MySQL ping 探测。走的是 Raft 协议(CP 模式),实例上下线要经过半数节点确认,强调一致性。选型原则:Spring Cloud 微服务用临时实例——Pod 挂了心跳自然断,自动踢出;DNS 方式的非微服务基础设施(如 Nginx 反向代理的 MySQL)用持久化实例——没有 Nacos SDK 发心跳,只能靠服务端主动探测。面试容易忽略的点:临时实例的健康是"客户端主动上报",持久化是"服务端主动探测",本质区别决定了适用场景。


【面试题】3. Spring Cloud Gateway 的自定义全局过滤器怎么做?一个请求认证+日志记录的 GlobalFilter 怎么写? ​

【标准答案】Gateway 的过滤器分两种:GatewayFilter(作用于特定路由)和 GlobalFilter(作用于所有路由)。自定义全局过滤器实现 GlobalFilter + Ordered 接口,核心是在 filter() 方法里处理请求链。实战示例:认证+日志过滤器,拿到 exchange 后先检查请求头是否有 Token,没有直接返回 401,exchange.getResponse().setComplete() 拦截掉;校验通过就把 userId 塞到请求头透传给下游。注意三个坑:① 过滤器顺序用 getOrder() 控制,数字越小优先级越高,Netty 路由过滤器默认 Ordered.LOWEST_PRECEDENCE,你的认证过滤器必须比它先执行(比如设 -1);② 跨服务透传数据用 ServerWebExchange.mutate().request() 添加 Header,不要用 ThreadLocal——响应式编程线程会切换,ThreadLocal 会丢数据;③ 下游响应的日志记录用 then(Mono.fromRunnable(...)) 在响应返回时执行,链式写法不出错。GlobalFilter 是 Gateway 的核心扩展点,认证、鉴权、日志、限流都在这一层统一做。


【面试题】4. SAGA 分布式事务的编排型和协调型有什么区别?长事务场景下 SAGA 和 TCC 怎么选? ​

【标准答案】SAGA 把一个长事务拆成多个本地短事务,每个本地事务有对应的补偿事务(反向操作)。编排型(Choreography):没有中央协调器,A 做完发事件,B 监听到事件自己做,B 做完发事件,C 再监听——事件驱动,松耦合但流程散落在各服务里,出了错很难知道"现在 SAGA 执行到哪一步了"。协调型(Orchestration):引入一个 SAGA 协调器,它按顺序调 A→B→C,任何一步失败按逆序调补偿 C→B→A——流程集中,易追踪但协调器成了单点。SAGA vs TCC 的选型:SAGA 适合长流程但一致性要求不那么严的场景(订单履约:创建→支付→发货→收货,总共可能跨几天);TCC 适合短流程但强一致性的场景(秒杀扣库存+扣余额,毫秒级完成)。SAGA 的补偿是"做错了撤销",TCC 的 Cancel 是"预留了释放"——前者是事后补救,后者是事前预留,一致性强度天然不同。


【面试题】5. Apollo 和 Nacos 配置中心全面对比:功能、架构、生态上各有什么优劣势? ​

【标准答案】功能层面:Apollo 的亮点是配置版本发布审核+灰度+回滚完整闭环——改配置要审批,灰度给部分实例生效验证,出问题一键回滚,适合合规要求高的企业;Nacos 配置的亮点是和注册中心一体,一套中间件干两件事,运维成本低。架构层面:Apollo 是 Config Service + Admin Service + Portal 三层,客户端长轮询拉配置,依赖 Eureka 做服务发现;Nacos 是单体二进制内置所有能力,客户端也是长轮询,自举不需要额外注册中心。生态层面:Nacos 背靠阿里 Spring Cloud Alibaba 全家桶,和 Sentinel、Dubbo、Seata 无缝集成;Apollo 由携程开源,携程内部大规模验证很多年,但和 Spring Cloud 生态的集成没有 Nacos 深。选型一句话:如果已经用了 Spring Cloud Alibaba 生态(Nacos/Dubbo/Sentinel)就用 Nacos 省事;如果配置管理是独立命题、对审批回滚有强需求的金融/合规团队就用 Apollo。


【面试题】6. Spring Cloud LoadBalancer 的底层是如何做服务实例选择的?和 Ribbon 有什么区别? ​

【标准答案】Spring Cloud LoadBalancer 的核心接口是 ReactorLoadBalancer,工作流程:先从 DiscoveryClient 拿到服务实例列表,过 ServiceInstanceListSupplier 提供给负载均衡器,然后根据 LoadBalancer 策略选出一个实例。默认策略是 RoundRobinLoadBalancer(轮询),基于 AtomicInteger 自增取模实现的无状态轮询。LoadBalancer 比 Ribbon 的改进有三点:① 响应式非阻塞——原生支持 WebFlux 和 Reactive 流,不需要 Ribbon 请求时阻塞等结果;② 更轻量——没有 Ribbon 的复杂统计和健康检查模块,专注做负载均衡这一件事;③ Ribbon 已停更,Spring Cloud 2020.x 后官方主推 LoadBalancer。自定义负载均衡只需实现 ReactorServiceInstanceLoadBalancer 接口,在 choose() 方法里写你的逻辑——比如按实例元数据的 region 就近路由、一致性哈希按 userId 选实例,代码量十来行就够了。


【面试题】7. Zookeeper 分布式锁是怎么通过临时顺序节点 + Watcher 实现的?为什么它是"公平锁"? ​

【标准答案】ZK 分布式锁的核心流程:所有线程在同一个锁路径下创建临时顺序节点,节点名形如 /lock/guid-0000000001、0002...,ZK 保证节点序号全局递增唯一。每个线程获取 /lock 下的子节点列表,如果自己创建的节点序号是最小的,说明拿到锁;如果不是最小的,则对自己前一个序号的节点注册 Watcher 监听。前一个节点删除(线程释放锁或断连)时,ZK 通知下一个线程"轮到你了",它再去检查是否最小序号。为什么是公平锁:竞争锁的顺序严格按照节点创建的时间顺序——先来的先拿到锁,后来的排队,队列结构就是序号顺序。相比 Redis:ZK 是公平锁(顺序排队),Redis 是自旋非公平锁(谁手快谁拿);ZK 的临时节点在客户端断连时自动删除,避免死锁,可靠性更高;但 ZK 的吞吐量受限于 CP 协议的写性能,高并发下比不上 Redisson。一句话选型:对锁的公平性和可靠性要求高选 ZK,追求吞吐和低延迟选 Redisson。


【面试题】8. 多级缓存(Caffeine + Redis)的数据一致性怎么保证?缓存更新时先删缓存还是先写数据库? ​

【标准答案】多级缓存的两层:L1 本地 Caffeine(进程内纳秒级),L2 Redis(跨实例毫秒级)。一致性挑战是"双写"——数据改了,L1 和 L2 都要更新。先说最经典的问题:先删缓存还是先写 DB?推荐 Cache Aside 模式的"先写 DB 再删缓存"——因为写 DB 是事务保证的,删缓存失败了最坏情况是缓存脏数据等 TTL 过期,比"先删缓存再写 DB 时另一个请求读到老数据回填缓存"的安全窗口小得多。多级缓存的更新策略:① 设置合理 TTL——L1 设短(如 30s),L2 设长(如 5min),容忍短暂不一致;② 用 Redis Pub/Sub 或广播 MQ 通知所有实例清除 L1 缓存——改了数据后发一条消息,所有实例监听并 cache.invalidate(key);③ 关键数据用 Canal 监听 binlog 驱动缓存更新,完全解耦业务代码。一句话:缓存一致性没有银弹,本质是"用短暂的脏数据换性能",TTL 是你的最后安全网。


【面试题】9. 事件溯源(Event Sourcing)的核心原理是什么?在电商订单系统里怎么落地?和传统 CRUD 有什么本质区别? ​

【标准答案】事件溯源的核心一句话:不存当前状态,存导致状态变化的所有事件。传统 CRUD 订单表:status 字段从"待支付" update 成"已支付"再 update 成"已发货",你只知道当前是"已发货",不知道什么时候支付的、有没有回过款。事件溯源:存 OrderCreated、OrderPaid、OrderShipped 三条不可变事件,当前状态是通过"重放事件"计算出来的——依次 apply 这三个事件得到最新状态。电商落地:订单服务写事件到 EventStore(可以是 MySQL 事件表或 Kafka Topic),订单查询服务订阅事件流构建物化视图。核心好处:天然审计——每一笔变更是哪个事件驱动的,因果链完整可追溯;Bug 修复可以重放事件——修复业务逻辑后 Replay 全部事件生成修正状态;天然支持 CQRS——事件流同时喂给多个读模型各取所需。代价:写操作和查操作是异步的——刚下单立马查可能还是旧状态;事件版本演进(事件 schema 变了老事件怎么重放)要规划好。不是所有业务都值得上,审计、金融、复杂业务流是它的主场。


【面试题】10. 生产环境某接口 P99 延迟突然从 50ms 飙到 3s,CPU、内存、GC 都正常,你怎么排查根因? ​

【标准答案】CPU 和内存正常却 P99 飙升,基本排除代码 CPU 密集计算和内存问题,线索指向两点:等锁或等 IO。排查路径:第一步——Arthas trace 命令追踪接口内部调用链,看具体耗时卡在哪个方法,是卡在查 DB 还是调下游。第二步——如果是 DB 慢查询,Druid 监控或 show processlist 看有没有锁等待(Waiting for table metadata lock)、有没有大量行锁竞争、慢 SQL 是不是索引失效。第三步——如果是调下游变慢,看 SkyWalking 链路里下游 P99 是否同步飙升,可能是下游服务打满了线程池你在排队等响应。第四步——如果是间歇性卡顿,看 TCP 重传率(netstat -s),网络丢包导致超时重传也会让 P99 飙高而平均耗时看起来正常。第五步——jstack 抓线程栈看大量线程是不是 BLOCKED 或 WAITING,常见元凶:连接池耗尽等连接(HTTP 连接池/DB 连接池)、synchronized 锁竞争、ThreadLocal 用了没清理导致内存碎片化。经验之谈:CPU 不高的慢,90% 是等——等锁、等 IO、等网络,从"等什么"切入十拿九稳。