微服务常见面试知识点
围绕「下单 → 扣库存 → 支付 → 发通知」理解微服务。回答设计题时,先说明业务约束、流量、可用性和一致性要求,再给出方案及故障处理。
1. 为什么拆微服务?什么时候不拆?
微服务可让业务能力独立演进、部署和扩缩容,但引入网络调用、分布式数据一致性、部署治理和排障成本。
按业务能力、数据所有权和团队边界划分通常比按数据库表拆分更有意义。需求边界不清、团队较小时,模块化单体可能更合适。
追问:拆了服务却共用数据库有什么问题?跨服务直接改表容易形成隐式依赖,独立升级和数据约束更难保证。可以渐进迁移,但应明确数据归属。
2. 同步调用和消息通信怎么选?
| 方式 | 适用场景 | 必须考虑 |
|---|---|---|
| HTTP / RPC | 当前请求需要立即获得结果 | 超时、依赖链长度、错误传播 |
| 消息 | 异步通知、削峰、解耦 | 重复、乱序、积压、确认和补偿 |
消息队列能隔离处理速度,不能消除容量问题。库存扣减与发通知的正确性要求不同,不应全部套用一种通信方式。
3. 注册发现、负载均衡和网关
服务发现解决「实例在哪里」,负载均衡决定「请求给谁」,网关承担入口路由以及部分认证、限流等横切能力。
实例仍在注册列表中,不代表已经可以接流量。发布和下线要配合就绪检查、摘流、连接排空以及超时策略。网关验证身份后,具体资源的权限检查仍应由业务服务落实。
4. 超时后可以直接重试吗?
超时只表示调用方未及时获得结果,服务端可能已经成功。因此先判断操作是否幂等,再决定是否重试。
设置端到端时间预算、单次调用超时、重试次数和退避抖动;避免多个调用层同时重试放大流量。例如三层各尝试三次,最下游最多可能承受 27 次尝试。
工作场景:支付请求超时,先按业务请求号查询状态,再进入明确的重试或对账流程,不能直接生成新订单再扣一次款。
5. 限流、熔断、降级和隔离
| 手段 | 主要目标 | 例子 |
|---|---|---|
| 限流 | 控制进入系统的负载 | 按租户或接口限制请求速率 |
| 熔断 | 下游持续异常时减少无效调用 | 打开后快速失败,半开时有限探测 |
| 降级 | 在约束内提供较弱服务 | 推荐失败时返回默认内容 |
| 隔离 | 限制故障影响范围 | 为依赖设置独立连接池和并发预算 |
熔断与重试需要配合,不能用重试绕过熔断。降级也不能伪造关键操作成功,例如库存扣减失败不能返回「下单成功」。参见 熔断器模式。
6. 幂等如何实现?
目标是同一个业务操作被重复请求时,不产生重复业务效果。可以使用请求幂等键、数据库唯一约束和状态机条件更新。
关键是把幂等记录与业务修改放进同一可靠事务边界,并定义进行中、成功和失败的处理方式。相同键但不同请求内容应拒绝或明确报冲突;不能只在处理前查一下「是否存在」,因为并发请求可能同时查不到。
追问:Redis 先标记成功再写数据库安全吗?标记与业务写入之间的崩溃可能留下不一致,需要补偿或更可靠的事务边界。
7. CAP 与最终一致性如何回答?
CAP 讨论网络分区发生时,无法同时保证线性一致性与每个非故障节点都能响应的可用性。这里的可用性不是日常监控中的可用率,也不是任何时候随意「三选二」。
最终一致性需要在更新停止、通信恢复且同步机制持续运行等条件下收敛。它不是「等待一会儿就自然一致」,需要重试、对账、冲突处理和明确的业务状态。
8. Saga、TCC 和两阶段提交
| 方案 | 基本思路 | 主要代价 |
|---|---|---|
| 两阶段提交 | 协调参与者准备,再统一提交或回滚 | 故障期间可能等待并占用资源 |
| TCC | Try 预留资源,Confirm 确认,Cancel 释放 | 业务侵入较强,需处理幂等、空回滚和悬挂 |
| Saga | 多个本地事务串联,失败时补偿 | 中间状态可见,补偿也可能失败 |
Saga 补偿是业务动作,例如退款,不是撤销历史上所有外部影响。已发送的邮件就无法真正「回滚」。参见 Saga 模式。
9. 数据库写入成功,但发消息失败怎么办?
Transactional Outbox 把业务记录与待发布事件放进同一个本地事务,后台发布器再把事件发到消息系统。
发布器可能在发送成功后、标记已发送前崩溃,因此仍可能重复投递。消费者需要使用事件 ID 去重,并把去重记录和本地业务效果一起提交;外部调用还需要外部系统支持幂等或通过对账补偿。
「至少一次投递 + 幂等处理」是常用设计。某组件提供的 exactly-once 保证有范围边界,不能直接扩展成整个业务链路恰好执行一次。参见 AWS Outbox 模式。
10. 消息积压和乱序怎么处理?
先定位生产速率、消费耗时、失败重试和分区分布,再判断是扩容、修复慢依赖还是隔离毒消息。顺序要求通常按订单等业务键限定,不必追求所有消息全局有序。
消费者用状态机或版本号拒绝过期事件;重试队列、死信队列要配合告警和人工处理流程。无限重试会让坏消息长期消耗容量。
11. 一个接口变慢,如何定位到具体服务?
结合指标、日志和分布式追踪:指标看延迟分位数、错误率、吞吐及资源饱和度;追踪定位慢 span 和调用关系;日志解释该请求的具体错误。
Trace ID 应跨同步调用和异步消息传播。没有看到错误日志并不能证明没有问题,可能是采样、埋点缺失或请求卡在连接池。
排查顺序:确认影响范围和变更时间 → 找到慢链路 → 检查连接池、线程池、数据库与缓存 → 验证限流、回滚或修复的效果。
12. 如何讲清一个自己的项目?
按「背景与约束 → 方案与取舍 → 故障场景 → 验证结果」组织。记录真实的请求量、延迟、错误率和改动前后数据;没有测量过的结果不要编造。
可用下单链路自测:重复请求是否重复扣库存?超时能否查询结果?消息重复如何处理?服务发布时如何摘流?补偿失败由谁发现和处理?