微服务与分布式系统面试题
生成时间:2026-06-19 16:49 | 共 10 题 | 难度递进:基础 → 进阶 → 高阶
【面试题】1. DDD 中的防腐层(Anti-Corruption Layer)是什么?不用防腐层直接调用外部服务的后果是什么?
【标准答案】防腐层就是你的限界上下文和外部系统之间的"翻译官"。你的订单上下文需要查用户信息,不直接调用户服务的 API 返回对象,而是在订单上下文里定义自己的 User Value Object,通过一个 Adapter 把外部用户 DTO 转成你内部的领域模型。核心思想一句话:你自己的领域模型不能因为外部系统的变化而崩。不用防腐层最直接的坑:上游用户服务改了字段名或数据类型,你订单服务所有调用的地方全炸,系统之间耦合成一坨。防腐层不是过度设计——只要你的服务和外部系统有数据交互,就意味着两个上下文有自己的语言和模型,不加翻译迟早出事。落地上每集成一个外部系统就写一个 Adapter + Translator,代码量不大但隔离效果立竿见影。
【面试题】2. 服务发现有两种模式:客户端发现和服务端发现,核心区别在哪?Nacos 是哪种?
【标准答案】客户端发现模式:客户端自己从注册中心拉取服务实例列表,自己在本地做负载均衡选一个实例直连。典型代表是 Spring Cloud + Nacos/Eureka——Nacos 只负责存"有哪些实例",选谁干活是你应用代码里的 LoadBalancer 决定。服务端发现模式:客户端只调一个固定的中间代理(比如 Nginx/K8s Service),代理负责从注册中心拿到实例列表并转发。核心区别就一条——负载均衡的决策权在哪。客户端发现的优点是中间少一跳、延迟低,缺点是多语言多框架都要维护一套发现逻辑。服务端发现反之,适合多语言异构系统统一入口,但代理本身成了瓶颈。Nacos 默认支持客户端发现模式,但 Spring Cloud Gateway 这类网关也可以充当服务端发现的代理角色,两者不互斥。
【面试题】3. 配置中心怎么做多环境配置隔离?为什么不能所有环境共用一个 Namespace?
【标准答案】多环境隔离三层设计:第一层 Namespace 隔离——dev/test/staging/prod 各一个独立 Namespace,物理上数据不共存,防止开发手滑改到生产配置。第二层 Group 分组——同一个 Namespace 内按功能模块再分,比如 ORDER_GROUP、PAYMENT_GROUP,让配置归属清晰。第三层 DataId 命名规范——项目名-环境-profile.yaml,比如 order-service-dev.yaml。不能共用一个 Namespace 的原因很简单:生产环境的数据库密码、API Key 是最高级别的保密信息,和开发环境放一起等于把生产秘密暴露给了所有开发环境。而且操作权限不好控制——用同一个 Namespace 你没法限制开发只能改 dev 配置而不能碰 prod。实战里就一条原则:不同环境隔离到不同 Namespace,不同业务线隔离到不同 Group。
【面试题】4. 服务雪崩是怎么触发连锁反应的?从代码层面到架构层面有哪些防护手段?
【标准答案】雪崩是一条链:A 调 B,B 因为 DB 慢查询响应变慢→A 调用 B 的线程被 hang 住→A 的线程池逐渐占满→A 对外也变慢→上游 C 调 A 也开始 hang→全链路崩溃。关键不是 B 挂了,是 B 慢了导致调用方资源耗尽。防护分四层:代码层——设置调用超时(连接超时+读超时,不是默认无穷等),避免一个慢服务拖死所有线程;框架层——熔断器(连续失败 N 次直接快速失败,给下游恢复时间)和线程池隔离(给不同下游不同线程池,一个下游炸了不占其他下游的资源);中间件层——限流(限制进入调用链的请求总量,超了就排队或拒绝);架构层——服务降级(核心链路保证,非核心功能熔断时返回兜底数据)。一句话:雪崩防的是"慢"不是"死",超时设好是最后一道防线。
【面试题】5. CAP 定理说分布式系统不能同时满足一致性和可用性,微服务场景下到底怎么选?选 CP 还是选 AP?
【标准答案】CAP 不是非黑即白的选择,是"在发生网络分区(P)的情况下你必须二选一"。选 CP 就是发生分区时优先保证数据一致——少数派分区拒绝写请求直到恢复,强一致但可用性受损。选 AP 就是优先保证服务可用——所有分区都接受写请求,但数据可能不一致,事后靠异步同步补齐。微服务场景下的决策逻辑:资金交易类核心链路(扣款、转账)必须 CP,宁可拒绝请求也不能出错,选 Seata AT/TCC + DB 强一致;用户行为日志、浏览量统计选 AP,丢几条问题不大,MQ 异步最终一致就行。大部分业务其实是"用户没察觉到的短暂不一致"就够了,不需要强一致。面试官想确认:你知道 CAP 是用来思考取舍的框架,不是选择题。另外注意:没有网络分区时 C 和 A 可以同时满足,别把 CAP 当"只能二选一"。
【面试题】6. 可靠消息最终一致性方案具体怎么落地?RocketMQ 事务消息和本地消息表各有什么适用场景?
【标准答案】两种方案本质相同:先确保业务执行成功,再确保消息一定投递成功。本地消息表方案:业务操作和消息记录同写一个 DB 事务——扣库存时顺便往 message_log 表塞一条待发送消息,然后定时任务扫 message_log 表投递到 MQ,投递成功更新状态为已发送。优劣势:不依赖 MQ 的事务能力,普通 MQ 就能搞,但需要额外维护扫表任务且 DB 读写压力增加。RocketMQ 事务消息方案:发半消息→执行本地事务→Commit/Rollback→Broker 回查兜底。优势是不需要额外扫表,缺点是要求 MQ 必须是 RocketMQ。选型简单:用了 RocketMQ 直接用事务消息省事;用了 Kafka/RabbitMQ 就老老实实走本地消息表。两种方案的共同前提:消费端必须幂等——消息可能被重复投递,消费前先查去重表。
【面试题】7. 一致性哈希是怎么解决传统哈希"加节点大量迁移"问题的?虚拟节点又解决了什么?
【标准答案】传统哈希是 hash(key) % N,加一台机器 N 变了,几乎全部 key 的映射都变了,缓存全部失效——这就是"雪崩"。一致性哈希把节点和 key 都映射到一个 0 到 2^32-1 的哈希环上,顺时针找最近的节点。加一台新节点只影响它和逆时针上一个节点之间的数据,迁移量是 1/N。但这里有个坑:节点太少时数据分布可能极度不均——三个节点可能一个承 50% 数据。虚拟节点就是给每个物理节点映射一堆虚拟节点均匀散落在环上,比如 node-A 映射 150 个虚拟节点,key 实际被虚拟节点承接然后路由到物理节点。虚拟节点越多分布越均匀,但路由计算开销也越大,一般每个物理节点 100-150 个虚拟节点是个经验值。Dubbo 的一致性哈希负载均衡就是基于 TreeMap 实现哈希环 + 虚拟节点。
【面试题】8. Sentinel 热点参数限流是怎么做到"针对某个用户 ID 精准限流"的?秒杀场景里能防黄牛刷单吗?
【标准答案】热点参数限流的核心:不统计全局 QPS,而是统计参数值维度的 QPS。比如 /buy?goodsId=123456,Sentinel 会单独统计 goodsId=123456 这个参数的调用频率,而不是全接口混在一起统计。具体实现用 LRU 缓存存每个热点参数的滑动窗口计数器,不是每个参数都长期追踪——只追踪 Top N 的热点参数,避免统计成本过高。秒杀防黄牛就需要这招:对所有用户限流 100 QPS 没用,黄牛一个账号就能刷到 100。你可以精确配置 goodsId=123456 参数值级别的 QPS 上限为 5,超过的直接拒绝,正常用户每人买一件不会触发限流,脚本刷单就撞墙了。还可以配合参数值白名单——VIP 用户不高限,黑名单用户直接拒绝。这个功能是 Sentinel 相对 Hystrix 的王牌差异点。
【面试题】9. 金丝雀发布的全链路灰度路由是怎么实现的?流量染色和透传机制怎么设计?
【标准答案】全链路灰度不是网关一层的事,是"一格请求从头走到尾都走灰度"。三步实现:第一步流量染色——网关层根据规则(Header、Cookie、用户白名单)给请求打标,比如在 Header 里塞 x-version-tag: canary,这就是"染色"。第二步服务路由——RPC 框架(Dubbo/Spring Cloud)在调用下游时读取染色标记,路由到同样标记的灰度实例,而不是随机选。第三步全链路透传——OpenTelemetry/SkyWalking 在 Trace Context 里携带这个标记,中间每一跳自动透传,不需要业务代码手动加参数。关键的工程细节:如果下游没有灰度实例怎么办?必须兜底降级到稳定版本,不能因灰度找不到实例就失败。流量标 Key 要统一命名,所有服务约定好一个 Header 名,否则中间某一跳丢了标就走回了稳定版本。网关 + 服务网格 + 一致标名是落地的铁三角。
【面试题】10. 雪花算法(Snowflake)时钟回拨了怎么办?Leaf 和美团的方案分别是怎么解决的?
【标准答案】雪花算法的核心结构:41 位时间戳 + 10 位机器 ID + 12 位序列号,时间戳自增是保证 ID 全局递增的关键。时钟回拨意味着时间戳比上次发的小了,可能产生重复 ID。五种解法:最粗暴——检测到回拨直接抛异常,适合容忍度低的场景;第二种——回拨幅度小(<5ms)就自旋 sleep 等到时间追上再发;第三种——记录上次生成的最大时间戳,回拨时用"最大时间戳 + 序列号递增"继续发,直到时钟恢复,不依赖系统时钟;第四种——Leaf 的方案,号段模式从 DB 取号段后纯内存发号,根本不用机器时钟,天然免疫时钟回拨,雪花算法模式则额外用 ZK 持久化时间戳做校验;第五种——美团方案,把 10 位机器 ID 拆成 5 位 WorkerID + 5 位 ClockBackRoll,回拨发生时递增 ClockBackRoll 字段,等于手动给时间戳"加了 1ms",这样即使时间倒流 ID 也递增。选哪种看你的容忍度:号段模式最稳但依赖 DB;美团方案最优雅但需要改造算法。