Skip to content

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

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


【面试题】1. 微服务接口的幂等性设计:为什么 GET 天然幂等、POST 不是?让 POST 接口也幂等的关键手段有哪些? ​

【标准答案】幂等就是同一个操作执行一次和执行一百次结果一样。GET/PUT/DELETE 天然幂等——GET 查一百次数据不变,PUT 设一百次还是那个值,DELETE 删一百次第一次删了后面都是空操作。POST 不幂等——每次 POST 都新建资源,发一百次就是一百条记录。让 POST 幂等的关键手段:一、唯一 ID 去重表——客户端生成唯一 requestId,服务端拿 requestId 查去重表,已处理就直接返回上次结果。二、乐观锁——UPDATE SET status='paid', version=version+1 WHERE id=123 AND version=5,同一个 version 的 UPDATE 只会生效一次。三、数据库唯一约束——订单号设唯一索引,重复插入直接抛 DuplicateKeyException,catch 住返回"订单已存在"。四、状态机——订单状态只能从待支付→已支付,不能从已支付→已支付,天然防重。幂等设计在支付、扣库存场景是必选项,漏一次就是生产事故。面试一句话:幂等的本质是"给每个操作一个指纹,同一指纹只执行一次"——requestId 就是那个指纹。


【面试题】2. 缓存预热是什么?一个新服务启动后 Redis 还是空的,怎么避免冷启动把 DB 打挂? ​

【标准答案】缓存冷启动就是服务刚重启、Redis 里空的,所有请求穿透到 DB——相当于缓存在线但缓存里没数据,DB 瞬间被打成筛子。预热本质是"在流量进来之前先把热数据塞进缓存"。三种做法:一、启动预热任务——服务启动后监听 ApplicationReadyEvent,异步线程去 DB 把热点数据批量加载到 Redis。比如查最近 7 天最热的商品 TOP1000,分页分批灌进 Redis(绝不能一次 SELECT * 全表拉,那预热本身就打挂了 DB)。二、双缓存架构——Caffeine 本地缓存做 L1,启动后先从 DB 预热到本地,Caffeine 挡第一波流量,Redis 在后面慢慢热起来。三、发布前预热——灰度发布时新实例先注册但先不分流量,后台跑预热任务塞好缓存再开闸引流。暖的坑:预热数据的 TTL 要比正常短(比如 10 分钟),等正常流量进来后自然会替换成最新的。面试一句话:缓存预热就是用"主动拉取"替代"被动穿透"——与其让第一个用户吃 10 秒超时,不如让机器提前热好锅。


【面试题】3. API 网关的跨域 CORS 问题怎么解决?为什么 Spring Cloud Gateway 的 CORS 方案比 Nginx 更灵活? ​

【标准答案】CORS(跨域资源共享)是浏览器安全策略——浏览器禁止前端 JS 调不同域名的后端 API。微服务网关是解决 CORS 的最佳位置——所有外部请求都经过网关,在网关统一配置 CORS,下游微服务完全不用管。Spring Cloud Gateway 比 Nginx 灵活在哪:Gateway 用 Java 代码写 CorsWebFilter,可以动态从配置中心读取允许的 Origin 白名单——新增域名不用重启网关,Nginx 配 CORS 要改 nginx.conf 并 reload。关键配置:allowedOrigins(哪些域名能跨域)、allowedMethods(GET/POST 等)、allowedHeaders(Authorization 等)、allowCredentials(是否允许带 Cookie,此时 Origin 不能是 *)、maxAge(OPTIONS 预检缓存时间)。流程:浏览器先发 OPTIONS 预检→网关 CorsFilter 返回允许的 Header→浏览器确认通过→发真实 GET/POST。常见坑:搞了 * Origin 又开了 allowCredentials,浏览器直接拒绝;OPTIONS 请求没被全局鉴权 Filter 豁免,401 了 CORS 就失败。面试一句:CORS 不是安全机制而是"浏览器替服务器做的限制"——从后端直接调别人的 API 根本不受 CORS 约束,只有浏览器会拦。


【面试题】4. Zipkin、Jaeger、SkyWalking 三者核心架构差异?为什么国内 Spring Cloud 体系首选 SkyWalking? ​

【标准答案】三者架构差异明显。Zipkin(Twitter 开源):组件最拆——Collector 收数据、Storage 存(MySQL/ES)、Web UI 展示。客户端用 Brave 库埋点,数据上报走 HTTP 或 Kafka。Jaeger(Uber 开源):架构精简——Agent 采集 + Collector 汇聚 + Query 查询,默认存 Cassandra。Jaeger 兼容 Zipkin 的 Thrift 协议,Brave 埋点的数据也能进 Jaeger。SkyWalking(国内 Apache):最"一把梭"——Java Agent 字节码增强零侵入 + OAP Server 做分析和存储 + RocketBot UI。核心差异三个:接入方式——SkyWalking 靠字节码增强零代码侵入,Zipkin/Jaeger 也可以字节码增强但更多走 SDK 手动埋点;功能范围——SkyWalking 的 OAP 天然支持服务拓扑图和 Metrics,Zipkin/Jaeger 更偏纯 Tracing;生态——SkyWalking 拥抱 Spring Cloud Alibaba,自带 Nacos 集成。为什么国内选 SkyWalking:中文社区活跃 + 几十个微服务装上 Agent 就生效 vs 每个服务配一遍 Bravely SDK。Jaeger 强在 K8s + Istio 的 Sidecar 注入式集成,Zipkin 强在协议标准化。面试金句:SkyWalking 靠零侵入把接入成本打到零,这对微服务规模越大的团队越值钱。


【面试题】5. 百度 UidGenerator 的 RingBuffer 和美团 Leaf 的双 Buffer 有什么本质区别?各自性能上限和可靠性如何? ​

【标准答案】两者都是"预分配 + 双缓冲"思想,但方向不同。UidGenerator 核心是 RingBuffer 预填充——预先在内存分配一个环形数组(默认 8192 个槽位),异步线程往数组里用雪花算法批量生成 ID 填好,取 ID 时直接 CAS 无锁从 RingBuffer 读取。两个 RingBuffer 交替使用,一个快用完自动切到另一个。Leaf 双 Buffer——从 DB 批量拉一个号段(如 1~1000)放到 Buffer,发号时 AtomicLong 无锁自增,发到 90% 异步去 DB 拉下一段填充备用 Buffer,current 发完指针交换。本质区别:UidGenerator 是"雪花算法的加速器"——还是每毫秒算 ID,RingBuffer 解决了雪花算法同一毫秒上限 4096 的瓶颈,但时钟回拨问题还在;Leaf 号段模式是"绕开雪花算法"——ID 来自 DB 号段,完全不受时钟影响。可靠性差异:Leaf 号段依赖 DB,DB 挂了已缓存的号段能撑一段时间但最终会耗尽;UidGenerator 纯内存 + 本地时钟,DB 全挂也能发号,但要自己处理时钟回拨。性能都是单机百万 QPS——发号走纯内存 AtomicLong/CAS,网络和 IO 被预加载吃掉了。选型:需要全局趋势递增 + 怕时钟回拨 → Leaf 号段;完全不想依赖 DB → UidGenerator。面试一句:两个 Buffer 的哲学一样——"提前把弹药箱搬到你手边",你只管开枪不用等弹药。


【面试题】6. 事件通知、事件溯源、事件协作三种消息模式有什么区别?各自适合什么微服务场景? ​

【标准答案】三种模式都是"通过事件解耦服务",但意图完全不同。事件通知:A 发生某事,广播出去"A 发生了 X",B 和 C 收到后各干各的,A 不关心它们成没成功。典型:订单支付成功后发"OrderPaid"→积分服务加积分、物流服务创建发货单。事件协作(编排):A 发事件"OrderPlaced"→B 收到后执行→B 完成后发"InventoryReserved"→C 收到后执行→链式接力。每个服务既消费事件也产生事件,像接力棒传递。适合 SAGA 长事务的正向编排。事件溯源:不存当前状态只存事件流——"UserRenamed"→"UserPhoneChanged"→"UserAddressChanged",当前状态从事件流重放计算出来。核心区别一句话:通知是"我告诉你发生了什么",编排是"你做完之后告诉你下一个人",溯源是"我不记结果只记过程"。选型:通知最常用,90% 的场景够了;编排适合多步骤流程但链断了排查头疼,必须配合补偿机制;溯源适合需要完整审计和任意时间点回溯的场景,代价是查询复杂需要 CQRS 配合。面试结论:三种模式是按"耦合程度"渐弱的——协作紧、通知松、溯源完全解耦了"状态"和"历史"。


【面试题】7. 微服务的"舱壁隔离"除了线程池隔离,还有哪些形式?信号量隔离、连接池隔离、进程隔离各自有什么优劣势? ​

【标准答案】舱壁隔离来自造船业——船体分多个隔舱,一个舱进水不会淹没整艘船。微服务里就是"一个下游挂了不能拖死整个上游"。四种隔离层级递进:一、信号量隔离——在主线程内用 Semaphore 控制并发数,同一时刻最多 50 个请求调下游,第 51 个直接拒。优点是零线程切换、延迟极低;致命缺点是调下游超时了不能中断——线程已经卡在等待下游响应里,信号量只管"能不能开始",管不了"开始后的事"。二、线程池隔离——每个下游独立线程池,满了直接拒绝。优点是隔离最彻底,超时能中断;代价是线程切换开销 + 内存。三、连接池隔离——每个下游服务分配独立的 HTTP 连接池,下游慢把连接池占满了新请求拿不到连接快速失败。Gateway 的 Route 级别连接池隔离就是这种。四、进程/容器隔离——核心链路和非核心链路部署在不同 JVM 进程上,非核心服务就算 OOM 了也影响不到核心服务。极限是不同 K8s 集群物理网络隔离。选型:日常线程池隔离够用,信号量适合"下游不超时"的本地操作,进程隔离是重型武器——只在服务里非核心逻辑比重太大且经常出事时才拆。面试核心:隔离的强度 = 付出的代价。线程池隔离性价比最高,进程隔离最重但也最安全。


【面试题】8. 微服务什么时候该合并?服务粒度拆太细的反模式和评估标准是什么? ​

【标准答案】微服务不是越拆越好——拆太细了运维成本吃掉所有开发收益。四个合并信号:一、一个人维护超过 5 个服务,每天 oncall 在不同服务间跳来跳去,修个 bug 要开 5 个 IDE 窗口——服务比人还多的团队一定拆过头了。二、两个服务总是同时发布——A 改了个字段 B 必须跟着改、B 改了接口 A 必须跟着升级,说明它们在物理上是两个服务但在逻辑上是一次部署,该合并。三、跨服务调用链过长——A→B→C→D→E,一个用户操作穿透 5 个服务,每个跨服务调用多 2ms 延迟 + 序列化开销,排查一个 bug 要翻 5 个服务的日志。这种"深层调用链"说明拆得太碎。四、数据一致性问题反而因为拆了服务变复杂——本来一个本地事务能搞定的事现在要上分布式事务、MQ、对账系统,架构复杂度指数级上升但业务收益为零。评估用"三问":这个服务有独立数据库吗?有独立团队在维护吗?有独立发布节奏吗?三个都是 NO 就该合并。反模式:按技术层拆——Controller/Service/DAO 各一个服务——这是分布式单体,必须按业务领域拆或合。面试一句:拆的初衷是解耦,如果拆之后耦合更深了(每次都要联动上线),那就不是微服务是"拆散的一家人"。合并不丢人——Netflix 都在合并微服务。


【面试题】9. 微服务 GC 频繁导致定时抖动,怎么排查?G1 和 ZGC 在微服务场景下怎么选? ​

【标准答案】微服务 GC 频繁导致定时抖动的根因通常是"内存配小了被 GC 拖死"或"配太大了 GC 停太长时间"。排查三步:一、看 GC 日志——-Xlog:gc*:file=/tmp/gc.log,找两次 Full GC 之间间隔多长,间隔小于 5 分钟就是频繁。二、对比 P99 延迟和 Full GC 时间——延迟峰值和 GC 暂停时间完全重合就是 GC 导致的抖动。三、dump 分析——jmap -histo:live <pid> 看哪些对象占内存最多,定位是业务对象泄漏还是框架对象正常占用。G1 vs ZGC 选型:G1 是 JDK 8/11 默认选择,-XX:MaxGCPauseMillis=200 设目标暂停时间 200ms,适合堆 4G~32G、可接受百毫秒级暂停。ZGC 从 JDK 17 开始成熟——最大特点是暂停时间不随堆大小增长,16G 堆照样 <1ms 暂停,但 CPU 开销比 G1 高 10%~20%。选型法则:堆 ≤ 8G → G1,堆 > 8G 且对延迟敏感 → ZGC。微服务常见三个 GC 坑:System.gc() 触发显式 GC(加 -XX:+DisableExplicitGC)、接口返回大对象直接进老年代(必须加分页)、ThreadLocal 没 remove 导致线程复用对象泄漏。面试结论:GC 问题不是换个收集器就能解决的——先确认是泄漏还是内存配小了,泄漏换 ZGC 也救不了。


【面试题】10. 给一个日均千万级订单的电商做服务限流和降级方案,你会怎么分层设计? ​

【标准答案】千万级订单的限流降级不是"一个 Sentinel 规则"能解决的,要四层接力打防守。第一层,接入层(Nginx):WAF + IP 黑名单先过滤恶意流量,Nginx 用 limit_req_module 限制单 IP QPS,超过的直接 444 断开——这是最外层粗粒度,不跟恶意请求浪费资源。第二层,网关层(Gateway + Sentinel 集群限流):全集群订单创建接口 QPS 上限 5000(根据下游容量测算)。网关层的限流是"保护下游服务不被流量冲死",而不是"保证每一个请求都能下单"——超过就快速返回"活动太火爆"。第三层,服务层(每个微服务入口):订单服务、库存服务、支付服务各自配熔断和线程池隔离。库存服务可能只能抗 1000 QPS——超过就排队或快速失败。这层是"各自保命",一个服务崩不影响其他服务。第四层,业务降级(最高级):不是"拒请求"而是"请求处理了但体验降级"。大促时非核心链路——推荐列表切静态热门、评论关掉、物流实时追踪改成"发货后短信通知"——核心下单链路不受任何非核心逻辑牵连。降级开关放 Nacos 动态控制,一键切大促模式。最关键的:降级方案不是代码写完就完事,要定期演练——行业血泪教训是降级开关半年没碰,触发时发现逻辑已经跑不通。面试一句:分层限流降级的核心哲学是"离用户越近越要快拒,离核心链路越近越要精细"——Nginx 粗暴拒绝→网关精准限流→服务熔断自保→业务优雅降级,四层接力,层层兜底。