微服务面试题 - 2026-06-19 17:19
生成时间:2026-06-19 17:19 | 共 10 题 | 难度递进(基础→进阶→高阶)
【面试题】1. CAP 理论的三个字母分别代表什么?为什么说分布式系统只能三选二?
【标准答案】C(Consistency)一致性:所有节点在同一时刻看到的数据都相同——你刚写入的数据立刻在所有副本上能读到。A(Availability)可用性:每个请求都能收到一个非错误的响应,即使系统内部部分节点挂了——但不保证数据是最新的。P(Partition Tolerance)分区容错:系统在网络分区(节点间通信断了)时仍能继续运作。三选二的关键在于"网络分区一定会发生"——这是分布式系统的物理事实,不是可选项。当 P 发生时,你必须在 C 和 A 之间选:要么要一致性,让挂了的那半边节点拒绝写请求(CP);要么要可用性,让所有节点继续处理请求但可能读到旧数据(AP)。注意:不是"永远放弃某一个",而是"分区发生时优先选谁"。Nacos 的持久化实例走 Raft(CP),临时实例走 Distro(AP),就是在不同场景做不同取舍。面试官想听到你说出"P 不是可选项——它一定会发生"这句话。
【面试题】2. 配置中心怎么让客户端实时感知到配置变更?长轮询比定时拉取好在哪?
【标准答案】三种配置刷新方式。定时拉取:客户端每 N 秒向配置中心发请求"有变化没",大量请求 99% 返回"没有",白白烧服务端和网络。长轮询(Long Polling):客户端发请求,服务端不立刻返回,hold 住连接,直到有配置变化或超过超时时间(如 60s)才返回。Nacos 和 Apollo 默认都用长轮询——客户端拿完配置后立刻发起下一次长轮询,服务端有变更时秒级响应,没变更时 60s 后返回空让客户端再发起。好处:在没有变更时几乎零网络开销,变更时又能秒级推送,是"实时性"和"服务端压力"的最佳折中。WebSocket 推送最实时但连接数多了服务端扛不住。还有一个机制漏了会翻车:客户端拿到的配置如果用 @Value 注入,不改代码的话不会自动刷新,得用 @RefreshScope 或 @NacosValue(autoRefreshed=true),不然配置变了 Bean 还是老值——这是初级工程师最常掉的坑。
【面试题】3. 什么是服务雪崩?生产环境里最常见触发雪崩的三种场景是什么?
【标准答案】服务雪崩:A 调 B,B 调 C,C 挂了导致 B 的线程堆积,B 的线程池耗尽后 A 调 B 也开始超时,故障像雪球一样从底层滚到上层,最终整个链路瘫痪。三种最常见触发场景:第一,慢 SQL 或慢接口——C 服务某个接口从 10ms 变 5s,B 调 C 的线程全阻塞在等响应,新请求进不来,B 的可用线程归零;第二,连接池耗尽——HTTP 连接池/数据库连接池配小了,流量上来后调用方拿不到连接超时报错,错误触发重试→更多请求打进来→更拿不到连接,正反馈毁灭;第三,热点数据缓存失效——Redis 里的大 key 过期瞬间,所有请求穿透到 DB,DB 打满后慢查询拖死所有依赖它的服务。雪崩的致命特性是"正反馈"——越出问题越放大问题。防雪崩三板斧:熔断(及时切断故障链路)、限流(入口控制不让流量压垮)、降级(有预案的优雅妥协)。
【面试题】4. Hystrix、Sentinel、Resilience4j 三者全面对比,各自的最佳战场在哪?
【标准答案】先看共同点:三者都提供熔断、限流、降级,都基于"断路器模式"。区别在于实现理念和扩展性。Hystrix:Netflix 出品、已停更维护,线程池隔离 + 信号量隔离双模式,功能完整但线程开销大,20 个服务各配独立线程池 JVM 线程数能上千。最佳战场:存量老项目维护,新项目不建议。Sentinel:阿里出品、规则动态生效是杀手锏——配了 QPS=100 的限流规则秒级生效不需要重启,控制台可视化做得好,和 Spring Cloud Alibaba、Dubbo 无缝集成。但 Sentinel 是 Java 生态专供,Go/Python 另起炉灶。最佳战场:国内 Spring Cloud Alibaba 体系项目。Resilience4j:轻量级函数式设计,没有 Sentienl 的控制台/可视化那么重,但对函数式编程友好(Decorators.ofCallable),信号量隔离为主,内存占用小。而且 Golang、Kotlin 都有官方模块,跨语言优势明显。最佳战场:函数式编程风格、跨语言需求、不想引入额外基础设施的团队。一句话选型:老项目 Hystrix 缝缝补补,国内阿里生态 Sentinel,多语言轻量派 Resilience4j。
【面试题】5. 美团 Leaf 的号段模式和雪花模式各自怎么工作?什么业务场景选哪种?
【标准答案】Leaf 的两个模式解决不同问题。号段模式(Segment):Leaf 服务预分大号段——先从 DB 里拿一段(比如 1~1000),在内存里以 AtomicLong 顺序发号,快发完了异步去 DB 加载下一段(1001~2000)。号段用完了还没拿到新号段才阻塞。结果是严格递增的整数 ID。适合对 ID 顺序有强要求的场景——数据按时间排序分页、需要 ID 反映插入顺序的内部系统。雪花模式(Snowflake):标准 64 位长整型,1 位不用 + 41 位时间戳 + 10 位机器 ID + 12 位序列号。Leaf 的改进是机器 ID 用 ZK 持久顺序节点自动分配,不用手动给每台机器配 workerId。趋势递增但不严格。适合海量分布式场景——订单号、用户 ID、物流单号等对外暴露的 ID。Leaf 还有一个精妙设计弱依赖 ZK:雪花模式启动时从 ZK 拿 workerId,拿到后缓存在本地文件,ZK 挂了也能用本地缓存的 workerId 继续发号。选型清晰:内部业务且要严格递增 → 号段;对外暴露且海量分布式 → 雪花。
【面试题】6. CQRS 的读写分离后,查询端的"读模型"怎么构建才能保证数据准实时?延迟和一致性怎么权衡?
【标准答案】CQRS 的读模型构建有三种路径,按延迟从高到低:第一种,同步更新——写操作在同一个事务里更新写库和读库。一致性最强但破坏了 CQRS 的初衷,写操作变重,不推荐。第二种,异步事件流——写端发领域事件到 MQ,读端消费事件更新读模型。延迟在毫秒到秒级(取决于 MQ 吞吐和消费者处理速度),这是最主流的方案。第三种,定时批量重建——用 Canal 监听 binlog 或定时全量扫描,适合对实时性要求极低的报表场景。核心矛盾处理:前端刚下单立马跳转订单详情页,订单可能还没同步到读库。解决方案不是"让读模型变实时"而是"前端先读写库"——下单成功后前端优先从写库查这笔订单,等几秒后切换到读模型。或者写端返回时直接把订单快照给前端,前端先展示快照再后台拉读库。还有一个进阶设计叫"读模型的版本号":写端 event 带版本号,读端消费后更新版本,查询时带上期望版本号,版本不够就 fallback 到写库。一句话:不要追求读模型完美的实时性,用前端体验弥补异步的延迟窗口。
【面试题】7. BASE 理论中的"软状态"和"最终一致性"怎么指导微服务架构设计?举个实际场景说明。
【标准答案】BASE = Basically Available(基本可用)+ Soft State(软状态)+ Eventually Consistent(最终一致性),是 CAP 的工程妥协方案。软状态的核心是"允许系统中的数据存在中间态,且这个中间态不影响系统可用性"。指导设计的一条原则:不要把中间态当错误来处理。实际场景——秒杀扣库存:用户看到"抢购中"的按钮转了 3 秒才显示"抢到了"或"没抢到",这 3 秒里 Redis 预扣的库存和 MySQL 的最终库存就是软状态——可能不一致但系统没报错。如果你非要强一致让每一步都加锁确认,吞吐量直接跪。最终一致性指导的设计原则:用"给个期限"替代"立刻一致"。比如订单 30 分钟后未支付自动取消——在这个窗口内订单服务和支付服务的数据可以不一致(订单显示待支付但支付回调还没到),30 分钟一到系统自动兜底。落地检查点:你的系统里有没有明确写"数据不一致的最大容忍时间是多少"?5 秒还是 5 分钟?定义了这个值,你的降级和补偿策略就有了时间锚点。BASE 不是"放弃一致性"而是"把一致性从强约束变成时间约束"。
【面试题】8. 一个下单操作涉及订单、库存、优惠券三个服务,不想引入 Seata 这类重型框架,怎么靠业务设计保证最终一致性?
【标准答案】用"本地事务 + 消息表 + 定时补偿"的轻量方案。第一步:订单服务在同一个本地事务里做两件事——创建订单(状态=待支付) + insert 一条扣库存消息到 outbox 表。如果插入失败,事务回滚订单也不会有。第二步:定时任务扫 outbox 表待发送消息投递到 MQ,投递成功改状态为已发送。第三步:库存服务消费消息,扣库存。扣库存先查"扣库存记录表"看这条消息处理过没有——处理过直接 ACK,没处理过就扣库存 + insert 扣库存记录(同一事务),保证幂等。第四步:同样的 outbox 模式把"使用优惠券"的消息从库存服务发出去,优惠券服务幂等消费锁定优惠券。第五步关键兜底——订单服务的定时任务每隔 5 分钟扫"创建超过 30 分钟还待支付"的订单自动取消,取消时发"释放库存"和"释放优惠券"的补偿消息。整个方案零外部依赖,就是 MySQL + MQ。面试官质疑"失败了怎么办"——第四步的每条消息都有定时扫描兜底,MQ 本身也有重试机制,消费端幂等意味着重试安全。这其实就是"轻量级 SAGA"——不引入框架,用业务代码实现 SAGA 的编排 + 补偿,适合分布式事务场景不复杂的团队。
【面试题】9. 100 个微服务的 CI/CD 管道怎么设计才能做到灰度验证、一键回滚、变更可追溯?
【标准答案】设计一条标准流水线分六阶段。第一阶段:代码提交触发——GitLab/GitHub push 触发 Jenkins/GitLab CI,跑单元测试 + 代码扫描(SonarQube),30 秒出结果,不通过直接挡。第二阶段:构建制品——编译打包成 Docker 镜像,打上版本 Tag(git commit hash + 时间戳),推到 Harbor 镜像仓库。这个镜像是"不可变制品",dev → test → staging → prod 是同一个镜像推过去,不是每个环境重新编译——杜绝"我本地能跑"的问题。第三阶段:部署测试环境——K8s 的 Deployment 滚动更新测试环境,跑集成测试和契约测试。第四阶段:金丝雀发布——生产环境先起 1 个金丝雀 Pod,引入 5% 真实流量跑 10 分钟,对比金丝雀和旧版本的错误率、延迟。有异常自动触发回滚:kubectl rollout undo。第五阶段:分批全量——金丝雀验证通过后,分 3 批每批间隔 3 分钟滚动更新全量,监控每一批的指标。第六阶段:一键回滚——每次发布在 GitLab 的 Release 里打 Tag,回滚时不是"还原代码再走一遍流水线",而是直接用上一个版本的镜像 Tag 走第五阶段的分批发布。变更追溯:每次发版 GitLab Tag + 镜像 Tag + K8s Deployment Revision 三元对应,线上 Pod 的 kubectl describe 能看到镜像 Tag,反向查到是哪个 git commit 谁发的。一句话总结:不可变制品 + 金丝雀验证 + 按批次渐进全量 = 安全发布的标准公式。
【面试题】10. 线上突然大量 502 Bad Gateway,一看是网关到下游 HTTP 连接池满了,你怎么在不停服的情况下快速止血?根治方案是什么?
【标准答案】止血三步,一分钟内完成。第一步:切流降级——Gateway 控制台或 Nacos 配置里立刻打开降级开关,把非核心接口(推荐列表、Banner 图)返回降级兜底 JSON,释放连接池给核心接口(下单、支付)。第二步:扩容或限流——如果 K8s 环境立马 kubectl scale 扩下游服务 Pod 数,同时网关层给核心接口配 QPS 限流,兜住最大处理量。第三步:熔断慢调用——把持续超时的下游单独熔断,切到备用服务或降级响应,别让一个慢服务把整个连接池占满。根治方案分四块。连接池配置优化:根据压测结果配池大小,公式= QPS × P99 延迟 + 30% 冗余,别用默认值。超时三板斧:connectTimeout(建连超时,设 500ms)、readTimeout(读超时,设 2s)、从重试里去掉幂等保护——只对 GET 类接口重试,POST 禁止自动重试,防止双写。加入熔断机制:下游服务错误率 > 50% 自动熔断 30s,熔断期间请求直接走降级不占连接池。监控告警:连接池使用率超过 80% 发告警,别等满了才发现。面试关键一句话:挺住当下靠"切流+限流",治好病根靠"合理配池+超时+熔断",二者缺一不可。