Skip to content

高可用系统设计 ​

S6-017选修高级约 60 分钟JDK 21Java 高级与架构 · 架构设计

1. 本节定位 ​

《高并发系统设计》 解决「能扛住」;本节解决「坏一台、抖一下、重试一波」时还能不能活。

★ 四组一手实测 ✓✓:

text
9 的换算:99.9% = 年停机 8.8 小时;99.99% = 52.6 分钟(每多一个 9,成本陡增)
串并联  :5 个 99.9% 服务串联 → 99.501%(年停机 1.8 天)✗✗;双活 → 99.9999% ✓✓
重试风暴:基础负载 85% 时「裸奔重试 3 次」**4 轮迭代就雪崩**(ρ=1.79)✗✗✗
         加「重试预算」→ 收敛(ρ=0.935);加「熔断」→ 触发后回到 ρ=0.85 ✓✓

2. 学习目标 ​

  • 会算「几个 9」的停机时间与串并联可用性 ★★★;
  • 能说清「重试风暴」的正反馈机制,并解释为什么重试必须配预算/退避/熔断 ★★★;
  • 会用错误预算做发布决策 ★★★;
  • 能列出一份可执行的高可用设计清单 ★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

★ 高可用不是「买更多机器」,而是「对故障的数学与预案」 ✗✓:

常见幻觉 ✗现实 ✓
「我们多部署几台就很稳」串联链路越长越脆(5 跳 → 99.5%)✗✗
「失败就重试,总会成功」重试放大的是流量,不是可靠性 ✗✗✗
「SLA 写 99.99% 就行」得算得出来、测得到、有预算约束 ✓

5. 核心概念 ​

5.1 「几个 9」意味着多少停机(实测)★★★ ​

text
可用性   | 年停机    | 月停机    | 日停机
99%      | 3.7 天     | 7.3 小时   | 14.4 分钟
99.9%    | 8.8 小时   | 43.8 分钟  | 1.4 分钟
99.99%   | 52.6 分钟  | 4.4 分钟   | 0.1 分钟
99.999%  | 5.3 分钟   | 0.4 分钟   | 0.0 分钟

★ 用法 ✓✓:对外承诺前先换算成「停机预算」 —— 「99.99%」意味着全年只能停 53 分钟:一次大促事故就超了 ✓。

5.2 串并联:微服务为什么越拆越脆(实测)★★★ ​

text
串联 5 个 99.9% 的服务:0.999^5 = 99.501% → 年停机 1.8 天 ✗✗
并联(双活)2 个 99.9%:1 - 0.001² = 99.9999% → 年停机 0.5 分钟 ✓✓
3 层 × 每层双活:99.9997% → 年停机 1.6 分钟 ✓✓

★★ 两条设计原则 ✓✓✓:

原则落地手段
减少串联环节 ✓✓合并调用(BFF/聚合层)、本地缓存、异步化
关键环节冗余 + 自动切换 ✓✓多实例 + 健康检查 + 负载均衡(《Kubernetes 入门》 的容器自愈)

★ 别忘「切换时间也算停机」 ✗:冷备切 5 分钟 = 每次故障先赔 5 分钟 ✓。

5.3 重试风暴:一条正反馈的死亡螺旋(实测)★★★ ​

★ 场景:服务能力 1000 req/s、基础流量 850(ρ=0.85)、客户端超时 30ms、失败重试 3 次:

text
第 1 轮:λ= 850 → ρ=0.850 → 排队 5.7 ms → 超时率 0.94% → 重试 +  24
第 2 轮:λ= 874 → ρ=0.874 → 排队 6.9 ms → 超时率 2.00% → 重试 +  51
第 3 轮:λ= 901 → ρ=0.901 → 排队 9.1 ms → 超时率 4.62% → 重试 + 118
第 4 轮:λ= 968 → ρ=0.968 → 排队 30.1 ms → 超时率 36.87% → 重试 + 940
第 5 轮:λ=1790 → ρ=1.790 → **过载:队列无限增长,雪崩** ✗✗✗

★★★ 正反馈链条 ✓✓✓:流量涨 → 排队涨 → 超时涨 → 重试涨 → 流量再涨 —— 关键是第 4 轮:超时率从 4.6% 跳到 36.9%,重试瞬间放大到 940/s ✗✗✗。

★ 三种解法的实测对比 ✓✓✓:

策略结局用户体感
不重试稳定 ρ=0.85 ✓约 1% 失败(可接受)
重试 3 次(裸奔)雪崩,过载 ✗✗✗全员超时
重试 + 预算 10%稳定 ρ=0.935 ✓约 13% 超时(降级但不崩)
重试 + 熔断(10%)熔断后回到 ρ=0.85 ✓✓短暂失败后恢复

★★ 三条铁律 ✓✓✓:

  1. 重试必须配预算(重试流量不得超过基础流量的一个小比例,如 10%)✓✓✓;
  2. 重试必须配退避 + 抖动(把重试在时间上摊开,避免同时到达)✓✓;
  3. 熔断是最后一道闸(超时率异常时,停重试比继续打更负责)✓✓✓。

★ 呼应 《项目 B:性能测试与故障分析》:项目 B 的故障分析里「超时设置不合理 + 无退避重试」正是同类事故 ✓。

5.4 错误预算:用数字管理「稳」与「快」(实测)★★★ ​

text
SLO 99.9% → 月错误预算 = 43200 × 0.001 = **43.2 分钟**
本月已消耗 31.5 分钟(72.9%)→ 剩余 11.7 分钟
判定:**黄灯**:放缓发布节奏、优先修稳定性问题 ⚠️

★ 使用规则 ✓✓:

text
预算充足(< 50%)→ 放心发版、做演练、冒可控风险 ✓
预算过半(黄灯)  → 放慢节奏、修复优先 ✓
预算耗尽(红灯)  → **冻结功能发布**,全员转向稳定性 ✗✓

★★ 价值 ✓✓✓:把「研发要快」和「运维要稳」的争执,变成一条双方认可的线 —— 没有错误预算,SLA 就只是一句口号 ✗✓。

5.5 高可用设计清单 ★★ ​

维度必做项呼应
冗余多实例 + 多可用区;无状态化(有状态要谈复制)《CAP 与 BASE 理论》
隔离舱壁(线程池/连接池按依赖隔离)、租户隔离《线程池与 ThreadPoolExecutor》
超时每个远程调用都要有超时;超时 < 上游超时(层层收敛)本节 5.3 ✓
重试预算 + 退避 + 抖动 + 只重试幂等操作《分布式事务》
熔断/降级超时率/错误率阈值 + 兜底响应(缓存/默认值)《Sentinel 限流与熔断》
限流入口限流 + 依赖保护《高并发系统设计》
演练故障注入(杀实例/断依赖/压满 CPU)、定期做混沌工程
可观测RED 指标(Rate/Error/Duration)+ 告警 + 追踪T2-016

6. 递进示例 ​

6.1 超时的「层层收敛」 ​

text
网关 3s → 服务 A 2s → 服务 B 1s → DB 500ms
★ 上游超时必须大于下游(否则下游还在干活、上游已放弃 = 白干)✗✓
★ 反例:全都配 3s → 一次慢调用可以把三层线程全部占满 3 秒 ✗✗

6.2 熔断器的三态回顾 ​

text
关闭(正常)→ 错误率超阈值 → 打开(快速失败)→ 冷却期 → 半开(放少量探测)
★ 半开的意义:**恢复要有试探,不能一次性放全量** ✓✓

6.3 反例:在「写操作」上无脑重试 ​

text
支付超时 → 重试 → 可能扣两次 ✗✗(除非幂等,[S6-014](../distributed/S6-014))
★ 规则:**只重试幂等操作;非幂等必须先做幂等改造** ✓✓✓

7. 完整可运行代码与实测 ​

代码:labs/java-deep/src/main/java/cn/kb/learn/deep/architecture/AvailabilityMathLesson.java、labs/java-deep/src/main/java/cn/kb/learn/deep/architecture/RetryStormLesson.java

实测输出(节选):

text
===== 串并联计算:微服务为什么越拆越脆 =====
  串联 5 个 99.9% 的服务(一次请求穿过 5 个):
    0.999^5 = 0.99501(99.5010%)→ 年停机 1.8 天 ✗✗
  并联(双活)2 个 99.9% 的实例:
    1 - 0.001² = 0.999999(99.9999%)→ 年停机 0.5 分钟 ✓✓

===== 场景:失败就重试 3 次(无上限、无退避)=====
  第 4 轮:λ=   968 → ρ=0.968 → 排队  30.1 ms → 超时率 36.87% → 重试 +  940
  第 5 轮:λ=  1790 → ρ=1.790 → **过载:队列无限增长,雪崩** ✗✗✗

===== 场景:重试 3 次 + 重试预算 10% =====
  → 结论:**收敛于 λ=935(ρ=0.935)**(预算限制住了重试),超时率 13.3% ⚠️

8. Debug 观察点 ​

  1. 把基础流量从 850 调到 700:观察「同样的重试策略不再雪崩」✓✓(水位是关键变量);
  2. 把熔断阈值从 10% 调到 30%:观察熔断更晚触发、超时率更高 ✗✓;
  3. 把「不重试」的超时阈值调到 10ms:观察超时率飙升(超时太短同样致命)✗✗;
  4. 给重试加「抖动」的定性分析:为什么同步重试最危险 ✓✓。

9. 常见错误 ​

#错误说明
1失败就重试、无预算无退避 ✗✗✗本节实测:4 轮雪崩 ✓✓
2在非幂等操作上重试 ✗✗✗双扣 / 重复下单 ✓✓
3超时不收敛(层层都 3s) ✗✗慢调用会占满整条链的线程 ✓
4冷备切换不算停机预算 ✗每次故障先赔切换时间 ✓
5SLA 只写不测 ✗没有监控与错误预算 = 空话 ✓✓
6从不做故障演练 ✗✗切换/降级路径从未被验证过 ✓✓

10. 动手实验 ​

  1. 跑 AvailabilityMathLesson:把你项目的 SLA 换算成停机预算 ✓✓✓;
  2. 跑 RetryStormLesson:把基础流量改成「大促水位」,观察雪崩点前移 ✓✓✓;
  3. 给项目 B 的调用链补齐「超时收敛 + 重试预算」(参考 《Sentinel 限流与熔断》 的 Sentinel 配置)✓✓;
  4. 做一次故障演练:kill 一个实例 / 断开一个依赖,记录「发现时间 + 恢复时间」✓✓✓。

验收方式:给出一条调用链,能指出它的串联可用性、重试风险点、超时收敛顺序与降级方案。

扩展任务:写一份《高可用设计卡》:每个依赖的「超时/重试/熔断/降级」四要素 + 演练记录 ✓✓

11. 练习题 ​

  1. 99.9% 与 99.99% 的年度停机差多少?为什么说「每多一个 9 成本陡增」?
  2. 5 个 99.9% 的服务串联,可用性是多少?怎么改善?
  3. 重试风暴的正反馈链条是什么?三条铁律分别防什么?
  4. 错误预算怎么用?「预算耗尽」意味着什么?
  5. 什么情况下绝对不能重试?
参考答案(要点)
  1. 8.8 小时 vs 52.6 分钟;99.99% 需要冗余/演练/多活等成体系投入 ✓✓★
  2. 99.501%(年停机 1.8 天);减少调用跳数 + 关键环节双活 ✓✓★
  3. 流量→排队→超时→重试→更多流量;预算防放大、退避防同步、熔断防死循环 ✓✓✓★
  4. 按 SLO 折算允许的故障时长;耗尽 → 冻结发布、转向稳定性 ✓✓★
  5. 非幂等操作(未做幂等改造前);以及下游明确过载时 ✗✗★

12. 掌握标准 ​

  • [ ] 会做 9 的换算与串并联计算 ★★★;
  • [ ] 能说清重试风暴的机制与三条铁律 ★★★;
  • [ ] 会用错误预算做决策 ★★★;
  • [ ] 能背出高可用清单的八个维度 ★★;
  • [ ] 知道超时收敛与幂等重试的前提 ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

《DDD 领域驱动设计》 —— 从「系统怎么不坏」转向「系统怎么长得对」。

后续知识

暂无

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引