微服务与分布式系统面试题
生成时间:2026-06-19 16:34 | 共 10 题 | 难度递进:基础 → 进阶 → 高阶
【面试题】1. DDD 里的聚合、实体、值对象怎么区分?一个好的聚合根应该满足什么原则?
【标准答案】实体是有唯一标识的对象——比如"用户",ID 不变属性可变,需要生命周期追踪。值对象是没有标识的不可变对象——比如"地址"、"金额",一旦创建就不改了,替换就是整个换。聚合是把一组关联的实体和值对象封装在一起,对外只通过聚合根访问。比如"订单"是个聚合根,订单明细(OrderItem)是被聚合的实体,外部不能绕过订单直接改明细。好的聚合根四条原则:一是边界小——一个聚合最好只有一个根实体,别搞大聚合(一个聚合包含十几张表);二是通过 ID 引用别的聚合,不要直接持有对象引用;三是最终一致性——聚合内强一致,聚合间最终一致;四是一个事务只改一个聚合。面试官喜欢听:聚合设计的好坏直接决定微服务拆分的质量。
【面试题】2. Nacos 的临时实例和持久实例有什么区别?注册中心的健康检查机制是怎么演进的?
【标准答案】临时实例和持久实例的核心区别在于谁来维护生命周期。临时实例是靠心跳续约——客户端每隔 5 秒发心跳,Nacos 15 秒没收到就踢掉,适用于弹性扩缩容的微服务。持久实例是 Nacos 主动探测——即使客户端挂了,Nacos 也不会立即剔除,而是标记不健康等待人为处理,适用于 DNS 场景或非 Java 的遗留系统。健康检查的演进:最早是 TCP 端口检测(只判断进程死活不判断服务死活),后来加了 HTTP 探活(调 /health 端点确认业务可用),再到现在 Nacos 2.x 用 gRPC 长连接,连接断开即感知,比心跳轮询更实时。面试亮点:临时实例心跳模式轻量但有延迟,长连接模式实时但连接数开销大,选什么看你的实例规模和网络环境。
【面试题】3. 网关层的限流算法有哪些?令牌桶和漏桶分别适用于什么场景?
【标准答案】网关限流本质是"控制流量进入的速度"。三种主流算法:计数器——简单的固定窗口,但边界突刺问题严重(第 59 秒和第 61 秒各 100 请求实际 1 秒内冲了 200);滑动窗口——把窗口切更细避免边界突刺,Sentinel 默认就用这个;漏桶——请求先进桶,固定速率流出,强行平滑流量,不管下游能不能处理都按节奏来;令牌桶——固定速率放令牌,请求拿到令牌才放行,没拿到拒绝,允许一定程度的突发(桶里积攒的令牌)。网关层实际落地:秒杀场景用令牌桶——预热期积攒令牌,抢购瞬间允许突发;保护下游脆弱服务用漏桶——不管请求多猛,出去的节奏恒定。Spring Cloud Gateway 集成 Redis + Lua 脚本实现分布式令牌桶是经典方案。
【面试题】4. Nacos 配置热更新是怎么做到的?@RefreshScope 底层实现机制是什么?
【标准答案】配置热更新分两层实现。第一层 Nacos Server 推送变更:2.x 版本用 gRPC 长连接,配置变更后 Server 主动推送 ChangeEvent 给客户端,不用客户端轮询;1.x 是长轮询(Long Polling),客户端发请求,服务端hold 住 30 秒,有变更就返回,没变更超时后再发下一次。第二层 Spring 框架生效变更:@RefreshScope 注解的 Bean 不是单例而是"懒加载代理对象",第一次调用时从 Scope 缓存里取实际对象;当收到 RefreshScopeRefreshedEvent 事件时,清空缓存,下次调用重新创建 Bean 从而读到新配置值。所以本质不是"魔法修改"正在用的对象,而是"缓存放飞,下次重建"。注意限制:@RefreshScope 只对属性注入生效,构造器注入的 Bean 不会重建。配置变更后加个 @EventListener 监听 RefreshScopeRefreshedEvent 做业务兜底逻辑是工程实践。
【面试题】5. BASE 理论如何指导微服务落地?幂等性设计有哪些实战手段?
【标准答案】BASE 理论拆开说:Basically Available(基本可用)——不是 100% 可用,允许部分降级,比如推荐服务挂了推荐位显示默认商品;Soft State(软状态)——允许系统存在中间态,比如订单状态"支付中"不是最终态,查的时候可能看到短暂不一致;Eventually Consistent(最终一致)——不追求实时一致,但保证最终会一致,比如积分延迟到账但不会丢。幂等性设计是 BASE 落地的关键——同样的请求无论执行多少次结果一样。实战手段四种:一是数据库唯一索引——用业务单号做唯一键,insert 冲突就是重复请求;二是状态机——已支付的不允许再支付,状态校验代替幂等键;三是 Token 机制——提交前先拿 Token,提交时带上 Token,服务端判重后删除;四是 MQ 消费端用消息 ID 做去重表。面试官最在意的是:你能说出"消息传了两次但业务只处理一次"的具体实现。
【面试题】6. Seata AT 模式的落地方案是什么?和 TCC 相比分别适合什么场景?
【标准答案】Seata AT 模式的核心是"自动回滚"——对业务代码零侵入。原理:一阶段各服务正常执行 SQL,Seata 通过代理数据源自动提取执行前后数据镜像(undolog),本地事务提交;二阶段 TC(事务协调者)判断全部成功就异步删除 undolog,有失败就用 undolog 反查 SQL 还原数据。代价是全局锁加锁时间长、写隔离级别要求高。TCC 模式是手动定义 Try/Confirm/Cancel 三个方法——Try 预留资源(冻结库存),Confirm 提交(真正扣库存),Cancel 释放冻结(解冻库存)。选型关键:AT 适合对数据一致性要求高但冲突少的场景(后台管理系统),TCC 适合高并发扣减类场景(秒杀、红包),因为 AT 的全局行锁在热点数据上会成为瓶颈。面试亮点:AT 是"给我回滚"的方式,TCC 是"我知道怎么回滚"的方式,TCC 对开发要求高但性能上限高。
【面试题】7. Spring Cloud LoadBalancer 相比 Ribbon 有哪些改进?你如何在项目里自定义负载均衡策略?
【标准答案】LoadBalancer 是 Spring Cloud 对 Ribbon(已停止维护)的替代。四个关键改进:一是响应式——基于 WebFlux 非阻塞,Ribbon 是阻塞式 RestTemplate 时代的产物;二是更轻量——砍掉了 Ribbon 繁重的配置抽象和 Rule 继承体系,代码量减少一半;三是和 Spring Cloud 服务发现原生集成——不用再额外配置 ribbon.listOfServers;四是支持 ServiceInstanceListSupplier 缓存 + 健康过滤链式组合。自定义策略两步走:实现 ReactorServiceInstanceLoadBalancer 接口,在 choose 方法里写你的选择逻辑,比如按实例 metadata 里的 weight 做加权随机;然后用 @LoadBalancerClient 注解指定配置类。实际项目里最常用的自定义场景是一致性哈希(某个用户固定走某台机器)和基于权重的动态调整(晚上降配实例权重降低)。
【面试题】8. Sentinel、Hystrix、Resilience4j 三者架构上有什么核心差异?为什么国内大厂更倾向 Sentinel?
【标准答案】架构差异三个维度:一是统计模型——Hystrix 用滑动窗口 + 线程池,但统计粒度粗(秒级);Sentinel 用 LeapArray 滑动窗口,把 1 秒切成多个桶滑动统计,精度到毫秒级;Resilience4j 也是滑动窗口但更轻量无外部依赖。二是隔离策略——Hystrix 主打线程池隔离(强隔离但线程切换开销大),Sentinel 默认信号量隔离 + 可选线程池,Resilience4j 只用信号量。三是规则管理——Hystrix 规则写死后必须改代码重启,Sentinel 有控制台实时推送规则秒级生效。国内选 Sentinel 三个理由:控制台可视化操作运营可自助改规则不依赖开发;支持热点参数限流——比如同一个商品 ID 被刷能精确限流,Hystrix 做不到;支持集群流控——多实例共享总 QPS 限额,不像 Hystrix 单机各自为战。补充一句:Resilience4j 在 Java 函数式风格和模块化上最优雅,Spring Cloud Circuit Breaker 统一抽象后换底层也不难。
【面试题】9. ZooKeeper 分布式锁是怎么实现的?和 Redis 分布式锁比,优缺点是什么?
【标准答案】ZK 分布式锁核心机制是"临时顺序节点 + Watch"。流程:所有竞争线程都在一个锁路径下创建临时顺序节点(EPHEMERAL_SEQUENTIAL),ZK 保证节点序号严格递增。序号最小的获得锁,其他线程 Watch 前一个序号节点,前一个节点释放后 ZK 通知 Watch 它的线程去尝试拿锁。这样就形成了一条公平的等待链。优点三个:一是强一致——基于 ZAB 协议,Leader 节点故障切换不会丢锁;二是自动释放——客户端会话断开,临时节点自动删除,不怕死锁;三是公平锁——按序号排队,不会饥饿。缺点两个:一是性能——基于磁盘和事务日志,QPS 远低于 Redis(Redis 万级,ZK 百级);二是依赖 ZK 集群——ZK 本身运维复杂,如果只是用来做锁有点重。选型规则:资金交易、对一致性极敏感的用 ZK 锁;高性能抢购秒杀用 Redis/Redisson 锁,配合看门狗和红锁就够。一句到位:Redis 锁是"快但可能错",ZK 锁是"慢但一定对"。
【面试题】10. Leaf 号段模式的 DB 高可用怎么设计?双 buffer 优化解决了什么问题?
【标准答案】Leaf 号段模式核心思路:不每次生成 ID 都去调 DB,而是先从 DB 批量拿一段号(比如 step=1000)缓存在本地,本地用完再去拿下一段。DB 高可用设计:两个号段发号——一张表两行记录,用不同 step 错开发号,这样做的好处是即使一个号段所在的 DB 主库挂了,另一个号段还能继续发。具体实现是不同服务实例取不同 tag 的号段,tag 间互不影响。双 buffer 优化的痛点:号段耗尽时去 DB 取新号的瞬间,请求会短暂阻塞——就像你在高速上开到一半突然断油要去加油。双 buffer 方案是:当前号段用到 10% 时异步去 DB 取下一个号段,放到备用 buffer 里。当前号段用完时直接切到备用 buffer,完全无缝。美团 Leaf 的实际实现是 SegmentBuffer 结构里有 current 和 next 两个 Segment,current 消费到阈值触发 next 段的异步加载,消费者感知不到切换。一句话:号段模式是"空间换时间",双 buffer 让它更丝滑。