微服务面试题 - 2026-06-19 19:04
生成时间:2026-06-19 19:04 | 共 10 题 | 难度递进(基础→进阶→高阶)
【面试题】1. Nacos 2.x 相比 1.x 在通信协议上做了什么重大升级?gRPC 替代长轮询后带来了哪些实际收益?
【标准答案】Nacos 1.x 客户端和服务端走 HTTP 长轮询——客户端发请求,服务端 hold 住连接等变更或超时,然后客户端立刻再发下一个。问题三个:每个订阅的配置 / 服务会各建一个长轮询连接,N 个配置+N 个服务就是 2N 个 HTTP 连接,客户端和服务端连接数爆炸;HTTP 每次都要建连接和拆连接的开销大(即使是 Keep-Alive);定时任务的精准度差,最低 1 秒间隔。Nacos 2.x 把客户端和服务端的通信统一升级为 gRPC 双向流——客户端和服务端之间只维持一条 TCP 长连接,所有服务发现、配置变更、健康检查全走这条连接的多个 gRPC Stream。带来的收益很实际:连接数从 O(N) 降为 O(1),一万个节点在 1.x 下要维持几万条连接,2.x 下只要几千条;推送延迟从秒级降到毫秒级——服务端变更直接 push,不用等下一个轮询周期;支持服务端主动推送健康检查结果,客户端不用定时上报心跳。迁移成本很低,客户端升级 SDK 版本就行,服务端和 1.x 客户端兼容。面试一句:Nacos 2.x 是从"HTTP 短话轮番打"升级为"一条电话线一直开免提"——省资源、快响应、能双工。
【面试题】2. 缓存穿透、缓存击穿、缓存雪崩分别是什么?各自有哪些生产级别的解决方案?
【标准答案】三个概念都是在缓存层失效时 DB 被打爆的场景,但触发机制完全不同。缓存穿透:请求查一个 DB 里根本没有的数据(比如用不存在的 userId=-1 频繁请求),缓存没命中、每次都穿透到 DB 查 null,DB 空转被拖垮。解法两招:布隆过滤器——在缓存前加一层,把所有合法的 key 映射到 Bitmap 里,请求先过布隆,不存在的 key 直接返回空,不用打 DB;空值缓存——查 DB 返回 null 也在 Redis 里写一条短 TTL(30 秒)的空值标记,后续同样的非法请求直接命中空值返回。缓存击穿:一个热点 key 过期瞬间,大量请求同时打到 DB 查同一个数据。解法:互斥锁——第一个拿到锁的去查 DB 并回写缓存,后续的等锁释放后直接读缓存;逻辑过期——不设 Redis 的物理 TTL,在 value 里存一个逻辑过期时间,读的时候发现逻辑过期了先返回旧值,然后异步起一个线程去刷新缓存,保证任何时刻都有人兜底。缓存雪崩:大量 key 在同一时刻同时过期(比如批量导入时设了同一个 TTL),或 Redis 集群宕机,所有请求瞬间涌向 DB。解法:TTL 加随机值——TTL = baseTTL + random(0, 300),批量 key 的过期时间分散开;多级缓存——本地 Caffeine + Redis + DB 三级逐层兜底;Redis 高可用——哨兵或集群模式保证 Redis 本身不成为单点。面试一句:穿透是"没有这个人你偏要查",击穿是"热点明星被围观",雪崩是"全村房子同一秒塌"——对症下药,别搞混了。
【面试题】3. 什么叫全链路灰度发布?从网关到后端服务,一整套灰度流量染色的技术方案怎么设计?
【标准答案】全链路灰度不是"某个服务单独灰度",而是"一个请求从网关→A 服务→B 服务→C 服务,一路上全都走灰度环境"——否则 A 走灰度、B 走正式,数据不一致导致业务逻辑混乱(灰度订单写到了正式库存表)。技术方案分四步。第一步流量染色:网关根据请求头(cookie 里的 uid 取模、header 里带 x-gray: true)判断是否灰度流量,在请求头上打标记 x-traffic-tag: gray。第二步标记透传:全链路中间件(Dubbo 的 RpcContext、Spring Cloud 的 RequestHeader、SkyWalking 的 TraceContext)自动把流量标记从网关→A→B→C 透传到调用链尾部。第三步实例分组:注册中心(Nacos)里同一服务注册两组实例——一组打 tag env=normal,一组打 tag env=gray。消费方做负载均衡时根据流量标记路由到匹配的实例组:灰度流量只调灰度实例,正式流量只调正式实例。第四步兜底:如果灰度实例挂了,灰度流量可以配置"降级到正式实例"还是"直接报错"。核心细节:DB 和 MQ 也要隔离——灰度服务连接灰度 DB 和灰度 Topic,数据不能串。面试一句:全链路灰度的本质是"给流量打标签,让标签跟着请求跑完整个调用链"——光染色不够,透传和路由一个都不能掉链子。
【面试题】4. CAP 定理中,一个银行转账系统在发生网络分区时,你会选择 AP 还是 CP?为什么?你的选择在实际架构中怎么落地?
【标准答案】银行转账毫无疑问选 CP——宁可暂时不可用(返回"系统繁忙"),也绝不允许一笔转账在两个分区里各执行一次导致凭空多钱。但面试官想听的不是"选 CP"三个字,而是你怎么在工程上处理 CP 带来的代价。网络分区时选 CP 的核心做法是:确保发生分区时只有多数派能提供服务。以 ZooKeeper 为例——发生脑裂时只有 Leader 所在的分区能处理请求,少数派拒绝服务。这意味着被划分到少数派的机房整站挂掉,用户看到的是"无法服务"而不是"账乱了"。你必须配套两个东西让这种选择可接受:一是同城双活或三机房部署,让多数派挂掉的概率极低(概率已经比单个硬盘坏掉还低了);二是熔断器快速失败——用户请求超时后 30 秒之内返回明确错误,而不是挂起 30 秒转圈。相反,电商商品浏览场景选 AP——一个机房挂了,另一个机房继续展示商品(可能库存数据稍微旧一点),库存超卖的风险用下单时的实时校验来兜底,好过把整个商城全关掉。面试一句话:选 CP 还是 AP,取决于你在"让用户看到错误"和"让用户看到错误数据"之间更怕哪个——金融怕后者,电商怕前者。
【面试题】5. Seata 的 AT 模式和 TCC 模式分别是怎么工作的?什么业务场景该选 AT、什么必须上 TCC?
【标准答案】AT 模式是"自动挡"——Seata 接管了你的 SQL 解析:一阶段自动拦截业务 SQL,在执行 SQL 前后各拍一张快照(before image 和 after image),然后提交本地事务;二阶段如果全局事务提交,Seata 异步删掉快照;如果回滚,Seata 用 before image 反向生成回滚 SQL 自动执行。你写的是普通 @GlobalTransactional 和 CRUD,不需要写补偿代码。优点是零侵入,缺点是只在关系型数据库上工作、不能跨 Redis/MQ 等异构数据源。TCC 是"手动挡"——你得自己实现 Try(预留资源)、Confirm(提交资源)、Cancel(释放资源)三个方法。比如扣钱的 TCC:Try 阶段冻结账户余额(可用余额-100、冻结余额+100)、Confirm 把冻结的 100 真正划走(冻结余额-100)、Cancel 把冻结的 100 解冻回去(可用余额+100、冻结余额-100)。优点是灵活——可以跨 Redis 锁定库存、跨第三方支付做预授权冻结、跨任何异构系统;缺点是代码量大、每个接口都要写三个方法。选型一句话:纯关系型数据库的业务(订单、库存)→AT 模式,省钱省力;涉及 Redis 扣库存、第三方支付、跨系统资源的→TCC。面试一句:AT 是"全自动洗衣机"——扔进去就行,但只洗衣服(只支持 ACID 数据库);TCC 是"手洗"——什么面料都能洗,但你得自己搓每个步骤。
【面试题】6. 一致性哈希的原理是什么?虚拟节点解决了什么问题?为什么 Redis Cluster 不用一致性哈希而是用哈希槽?
【标准答案】一致性哈希把整个哈希空间围成一个环(0 到 2^32-1)。节点和 key 都映射到环上的位置——key 落到环上后,顺时针找最近的节点,由那个节点负责。节点增减时,只有受影响的那一段环上的 key 需要迁移,不是全量重分布——这是它和普通取模哈希的本质区别:普通哈希 hash(key) % N 加一个节点所有 key 几乎全要迁移,一致性哈希只迁移1/N的数据。但节点少时分布不均——三个节点可能一个占了 50% 的环。虚拟节点解决的就是这个问题:每个物理节点在环上映射 N 个虚拟节点(比如 150 个),虚拟节点均匀散布在环上,物理节点的负责区间被切成很多小段均匀分布——哪怕只有 3 个物理节点,负载也基本均分。Redis Cluster 不用一致性哈希:它用了 16384 个哈希槽——CRC16(key) % 16384 算槽号,槽是数据迁移和路由的基本单位。为什么?一致性哈希在节点增减时 key 迁移是"自动的但不可控的"——数据默默迁移到邻居节点,你要查某个 key 到哪去了得反推环。哈希槽的迁移是显式的——你手动指定"把 100~200 号槽从 A 节点迁到 B 节点",迁移过程和进度全透明可监控,运维可控性远高于一致性哈希。面试一句:一致性哈希是"民主自治村",节点增减数据自动重新分配;哈希槽是"行政划区",槽给谁、数据往哪搬由你明确下令。
【面试题】7. 美团 Leaf 的号段模式(Segment Mode)是怎么实现的?它相比雪花算法有什么优势和劣势?
【标准答案】Leaf 号段模式的核心思路是"一次从 DB 拿一批 ID 号段,在内存里慢慢发,发完再拿下一批"。具体实现:DB 里一张 id_alloc 表,字段 biz_tag + max_id + step。应用启动时执行 UPDATE id_alloc SET max_id = max_id + step WHERE biz_tag = 'order',step 默认 1000,拿到更新后的 max_id(比如 1000),那这一批号段就是 [1, 1000],在内存里用 AtomicLong 一个个发。号段快耗尽(比如用到 90%)时异步去 DB 拿下一批 [1001, 2000],无缝切换,发 ID 操作完全不走 DB 更不走网络,性能极高。对比雪花算法:优势一——纯数字,单调递增,DB 主键索引友好,B+Tree 插入直接追加不用分裂页;雪花算法 19 位 long 也是递增的,但没有号段模式"完全连续"。优势二——没有时钟回拨问题,不依赖机器时间。劣势一——依赖 DB,虽然 DB 挂了也能靠内存里还没发完的号段撑一会儿,但号段耗尽时 DB 还没恢复就断了;雪花算法完全去中心化。劣势二——号段用完重启服务没发完的部分就浪费了(号段空洞)。Leaf 的生产做法是号段模式和雪花模式同时提供,小公司直接用号段模式就够了。面试一句:号段模式是"批发 ID"——一次进货一箱,边卖边补货;雪花算法是"自产 ID"——每个工位自己生产,不依赖进货渠道。
【面试题】8. Spring Cloud Gateway 的 Filter 链是怎么组织的?GlobalFilter 和 GatewayFilter 的执行顺序谁先谁后?如果你想在网关层获取请求体做验签,应该在哪一层做?
【标准答案】Gateway 的 Filter 链分三层,按执行顺序:最外层是 GlobalFilter(实现 GlobalFilter 接口,自动生效),中间层是路由匹配后触发的 GatewayFilter(在 yaml 的 filters 下显式配置),最底层是 Netty 路由 Filter(真正把请求转发到下游的那个 NettyRoutingFilter)。同一个层内用 @Order 或 Ordered 接口控制顺序,数字越小越靠前。实际执行走的是责任链模式——请求进来先过 GlobalFilter 的前置逻辑(认证、限流、日志埋点)→GatewayFilter 的前置逻辑(加请求头、改路径)→路由转发→GatewayFilter 的后置逻辑(改响应头)→GlobalFilter 的后置逻辑(统一异常包装)。在网关获取请求体做验签的场景,关键陷阱:Gateway 默认走 Netty,请求体是 Flux<DataBuffer> 流,只能读一次——你读了之后下游就收不到了。解法:写一个高优先级的 GlobalFilter,用 ServerWebExchange.getRequest().getBody() 读请求体,验证签名,然后通过 ServerHttpRequestDecorator 把读过的 body 重新包装回去继续往下传。或者用 Spring Cloud Gateway 自带的 ModifyRequestBody GatewayFilter,本质上也是 Decorator 模式。面试一句:Gateway Filter 链像坐地铁安检——GlobalFilter 是进站闸机(每个人都过),GatewayFilter 是站台检票(指定路由才过),@Order 决定你排第几个,Decorator 模式让你看完身份证还能还给乘客。
【面试题】9. 链路追踪的采样策略是怎么设计的?固定采样、概率采样和自适应采样各自的适用场景是什么?全量采样的代价有多大?
【标准答案】固定采样最简单——每秒固定采 N 条(比如每秒最多 100 条 Trace),超过就丢弃。好处是控制住了写入量,坏处是低峰期(QPS 只有 50)可能采不满,高峰期(QPS 一万)丢掉了 99% 的链路,排查问题时看你丢的那条 Trace 正好就是出问题那条,就瞎了。概率采样——按固定比例(比如 10%)随机采,概率均匀,但同样有"出问题那条恰好没采到"的风险。自适应采样是 SkyWalking 和 Jaeger 比较推崇的方案——根据业务请求的响应结果动态调整采样率:正常请求低采样率(比如 1%),错误请求全量采样(100%),慢请求提升采样率(比如 50%)。逻辑是"99% 的正常请求 Trace 都是千篇一律的,存了也没人看;但错误和慢请求每条都是排查线索,一条不能丢"。全量采样的代价不容小视:假设你每天 1 亿次请求,按照一个 Trace 平均 10 个 Span、每个 Span 约 1KB 算,全量采样光存储就是 1TB/天,再加上采集 Agent 的 CPU 占用(序列化、压缩、上报)和 Collector 端的写入压力,整个系统的负载可能会增加 10%~20%。生产上合理的策略是"自适应采样 + 强制采样标记"——运维在网关层通过 Header 强制标记某类请求全量采(比如压测流量、灰度流量),其余走自适应采样。面试一句:全量采样是理想,自适应采样是现实——在"看到一切"和"不被 Trace 本身拖垮"之间,你得用"只采异常"来平衡。
【面试题】10. 从零搭建一个微服务项目的 CI/CD 流水线,开发提交代码到上线,中间要走哪几个关卡?每个关卡的设计要点是什么?
【标准答案】一条标准的微服务 CI/CD 流水线至少六个关卡,一关不过不能往下一關。第一关——代码规范检查:提交代码触发 Git Hook(或 CI 流水线第一步),跑 SonarQube 静态扫描(bug 数、代码异味、重复率)和 Checkstyle(代码风格)。硬性门禁:阻塞级别的 bug 必须为 0,增量代码覆盖率不低于 80%。第二关——单元测试:mvn test 全量跑,任何失败直接打回。第三关——构建镜像:docker build 构建带版本号的镜像,tag 用 git commit SHA 的前 7 位,保证镜像和代码一一对应可追溯。第四关——镜像安全扫描:用 Trivy 或 Clair 扫描镜像里的 CVE 漏洞,高危漏洞阻断流水线。第五关——部署到测试环境:Helm 或 Kustomize 把镜像更新到 K8s 测试集群,自动跑集成测试(pytest/Postman 脚本),验证核心链路通不通。第六关——生产上线:不是全量一把上,而是先用金丝雀发布——先更新 10% 的 Pod 跑新版本,观察 15 分钟(错误率、RT、CPU),指标正常逐步加到 50%→100%。中间任一指标异常,自动回滚到上一个镜像版本。关键设计要点三个:一是速度——流水线从头到尾要在 20 分钟以内跑完,否则开发等不及开始绕过流程;二是门禁要硬——任何卡点失败不能"跳过",跳过一次的流水线形同虚设;三是可观测——每个卡点的日志和结果全可视化,谁提交的、哪个卡点挂的、为什么挂的一眼能看。面试一句:CI/CD 不是工具链堆砌,而是一套"提交即可上线"的信任体系——信任不是口号,是靠每一个不可跳过的硬门禁建立起来的。
📋 考点覆盖:服务注册与发现(Nacos 2.x 架构升级)→ 分布式缓存设计(穿透/击穿/雪崩三连)→ 灰度发布与金丝雀部署(全链路灰度流量染色)→ 分布式核心理论(CAP 取舍实战案例)→ 分布式事务方案(Seata AT vs TCC 选型)→ 负载均衡策略(一致性哈希原理与虚拟节点)→ 分布式 ID 生成(Leaf 号段模式 vs 雪花算法)→ 服务网关(Gateway Filter 链执行顺序)→ 服务链路追踪(采样策略与自适应采样)→ 项目从 0 到 1 全流程实战(CI/CD 流水线六关卡)
题型分布:概念题(1,2) | 原理题(6,8) | 方案对比题(5,7) | 架构设计题(3,10) | 场景面试题(4,9)
难度递进:基础(1-3) → 进阶(4-7) → 高阶架构(8-10)