分布式系统常见故障模型有哪些?
简化版
分布式系统常见故障模型有哪些? 这类题在 分布式基础 面试中非常高频,考察点不是背概念,而是你能否把 故障模型 放进真实分布式链路里分析。
可以按四步回答:先说明业务目标,再说明分布式环境下的不确定性,然后给出核心方案,最后补上监控、降级、补偿和回滚。
典型场景是:服务调用经常跨进程、跨机器、跨机房,面试官会考你是否知道超时、丢包、重复、乱序、分区、时钟漂移这些故障不是异常小概率,而是日常约束。
回答时要强调:分布式系统没有单点真相,任何方案都要面对超时、重试、重复、乱序、部分失败、网络分区和人工恢复。
常见错误是:只讨论机器宕机,不讨论慢请求、网络分区和部分成功。
详细版
面试里可以这样组织答案:
- 先定义目标:要保护的是正确性、可用性、吞吐、延迟,还是恢复能力。
- 再定义失败模式:请求可能超时、结果可能未知、节点可能宕机、消息可能重复、配置可能短暂不一致。
- 然后给主流程:正常路径怎么走,异常路径怎么兜底,重试和补偿由谁触发。
- 最后给工程闭环:监控哪些指标,如何灰度,如何回滚,如何人工介入。
一个更贴近项目的表达是:先把 故障模型 拆成入口控制、核心状态、外部依赖、异步补偿、可观测性五层。入口控制决定请求能不能进来,核心状态决定是否允许重复执行,外部依赖决定超时和重试边界,异步补偿负责把未知结果拉回可确认状态,可观测性负责让问题能被发现和定位。
request
|
v
入口校验 -> 状态记录 -> 核心处理 -> 结果确认
| |
v v
重试控制 补偿任务
| |
v v
指标告警 <--- 对账巡检
| 层次 | 要回答的问题 | 常见手段 |
|---|---|---|
| 入口层 | 请求是否允许进入 | 限流、鉴权、幂等键、灰度标签 |
| 状态层 | 结果是否可确认 | 状态机、版本号、事务日志、操作记录 |
| 依赖层 | 下游失败怎么办 | 超时、重试、熔断、降级、隔离 |
| 补偿层 | 未知结果如何收敛 | 定时扫描、消息重投、对账、人工处理 |
| 观测层 | 问题如何定位 | TraceId、指标、日志、告警、看板 |
伪代码可以这样表达:
def handle_distributed_action(command):
record = load_or_create_record(command.biz_id)
if record.status in ("SUCCESS", "COMPENSATING"):
return record.result
if not allow_request(command.route_key):
return fallback_result(command)
try:
mark_processing(record)
result = call_core_dependency(command, timeout_ms=800)
mark_success(record, result)
emit_event(record)
return result
except TimeoutError:
mark_unknown(record)
schedule_compensation(record, delay_seconds=30)
raise
except Exception as exc:
mark_failed(record, str(exc))
schedule_retry(record, max_times=3)
raise
如果面试官继续追问,你要能给出数量级:例如单次调用超时 800ms,重试最多 3 次,补偿任务每 30s 扫描一次,单批处理 200 条,告警阈值是连续 5min 错误率超过 1% 或积压超过 10000 条。这些数字不一定固定,但要体现你会控制放大效应。
完整版教学
一、先看这个问题为什么高频
分布式基础 的题目经常出现在 Java 后端、基础架构、微服务和中高级开发面试里,因为它连接了理论和工程实践。
候选人如果只会讲概念,很容易在追问里暴露问题。真正能打动面试官的回答,要能说明方案在正常情况下怎么工作,在异常情况下怎么收敛,在压力变大时怎么保护系统。
分布式系统常见故障模型有哪些? 的核心不是某一个组件,而是 故障模型 背后的工程取舍。你要说明为什么这么设计,以及这个设计牺牲了什么。
二、先定义业务目标
回答前先问自己四个问题:
- 这个能力服务于核心链路还是非核心链路。
- 失败时应该拒绝、排队、降级,还是返回处理中。
- 数据要求强一致、读己之写、单调读,还是最终一致。
- 恢复目标是秒级、分钟级,还是允许人工介入。
例如支付链路更重视正确性和可确认结果,商品推荐更重视可用性和响应时间,离线报表更重视吞吐和可恢复。
面试时不要急着给技术名词,先把业务目标说清楚,后面的方案才有判断标准。
三、再定义分布式失败模式
分布式环境下最麻烦的不是明确失败,而是结果未知。
客户端看到超时,不代表服务端没有执行。MQ 投递成功,不代表消费者已经完成。配置推送到一部分机器,不代表全量生效。注册中心摘除实例,也不代表所有客户端立刻感知。
所以设计 故障模型 时,要至少覆盖这些失败模式:
- 请求超时,但服务端可能已成功。
- 请求重试,导致同一业务动作重复进入。
- 节点宕机,内存状态全部丢失。
- 网络分区,多个节点看到的世界不一样。
- 消息乱序或重复,旧事件覆盖新状态。
- 下游变慢,上游资源被持续占满。
四、主流程要有状态记录
分布式方案如果没有状态记录,就很难恢复。
状态记录可以是数据库表、事务日志、消息表、任务表、版本号、租约令牌,也可以是注册中心里的实例元数据。关键是让系统知道某个业务动作处在什么阶段。
一个常见状态机如下:
INIT
|
v
PROCESSING ----超时----> UNKNOWN
| |
|成功 |补偿确认
v v
SUCCESS <------------- COMPENSATING
|
v
DONE
PROCESSING --明确失败--> FAILED --重试/人工--> PROCESSING
状态机的价值是把重试、补偿、查询和人工处理统一起来。否则每个分支都靠 if 判断,很容易出现重复执行、漏补偿和状态覆盖。
五、重试必须有边界
重试能提升成功率,也会放大故障。
如果一个请求原本 QPS 是 1000,每次失败后立即重试 3 次,故障期间下游可能看到 4000 QPS。再叠加多个服务层级,放大效应会更明显。
更稳的策略是:
- 设置单次超时,例如
500ms到1000ms。 - 使用指数退避,例如
1s、2s、4s。 - 加随机抖动,避免同一时间集中重试。
- 设置最大次数,例如最多
3次。 - 对不可重试错误直接失败,例如参数错误、权限错误。
- 对结果未知的场景进入补偿,而不是无限同步重试。
六、幂等是分布式系统的底线
只要有超时和重试,就必须考虑幂等。
幂等不是简单地说接口重复调用结果一样,而是同一个业务动作重复进入时,不会重复扣款、重复发货、重复创建资源或重复释放锁。
常见做法包括:
- 客户端传业务唯一键,例如
order_id + action。 - 服务端保存请求处理记录。
- 使用唯一索引阻止重复写入。
- 用状态机判断是否允许从当前状态迁移。
- 对 MQ 消费保存消费记录或业务结果。
- 对外部调用保存请求流水和返回结果。
七、可观测性要和方案一起设计
没有可观测性,分布式方案就只是一套理想流程。
至少要监控这些指标:
| 指标 | 含义 | 异常信号 |
|---|---|---|
| 成功率 | 主流程是否稳定 | 成功率持续低于 99.9% |
| P95/P99 延迟 | 用户体验和下游压力 | P99 突然从 200ms 升到 2s |
| 重试次数 | 故障是否被放大 | 重试量超过原请求量 30% |
| 补偿积压 | 最终一致是否收敛 | 积压持续增长超过 10min |
| 状态卡点 | 哪个阶段无法推进 | UNKNOWN 状态占比异常 |
日志里要带 trace_id、biz_id、route_key、version、attempt、status。这样排查时才能从入口请求追到下游依赖、消息消费和补偿任务。
八、上线前要准备灰度和回滚
故障模型 相关改动通常影响链路稳定性,不能直接全量发布。
更稳妥的上线顺序是:
- 先加只读监控,不改变业务行为。
- 再对内部租户或小流量用户灰度。
- 观察错误率、延迟、重试量、补偿积压。
- 扩大灰度到
5%、20%、50%。 - 全量前确认回滚开关和数据兼容。
- 全量后保留一段时间双写校验或影子校验。
回滚不只是代码回滚,还要考虑数据状态能否兼容。如果新版本写了旧版本不认识的状态,回滚后可能造成更大的故障。
九、常见误区与追问
- 误区:只讲组件名,不讲业务目标。 面试官更关心你为什么选这个方案,以及它保护什么。
- 误区:只考虑正常流程,不考虑结果未知。 分布式系统里超时和部分成功非常常见,必须有补偿和查询。
- 误区:无限重试。 重试要有最大次数、退避、抖动和熔断,否则会放大故障。
- 误区:没有幂等。 一旦出现重复请求、重复消息或重复任务,业务副作用会失控。
- 追问:如果下游慢而不是直接失败怎么办? 要结合超时、隔离、背压、限流和熔断一起处理。
- 追问:如果补偿任务也失败怎么办? 要有重试次数、死信记录、告警、人工处理和对账兜底。
- 追问:如果新旧版本同时运行怎么办? 接口字段、消息 Schema、状态枚举都要保持向前兼容。
十、加强记忆
- 先记目标:正确性、可用性、吞吐、延迟、恢复能力。
- 再记失败:超时、重试、重复、乱序、宕机、分区。
- 再记状态:没有状态记录,就没有可靠恢复。
- 再记边界:重试有次数,排队有上限,补偿有告警。
- 再记幂等:重复进入不能造成重复副作用。
- 再记观测:TraceId、指标、日志、状态表要能串起来。
- 最后记取舍:分布式没有免费方案,每个设计都在一致性、可用性、性能和复杂度之间交换。