高可用系统设计
高可用不是“机器尽量不宕机”,而是系统在故障、维护和流量变化中,仍能持续满足用户成功率、延迟、正确性和数据目标。
设计高可用系统,需要把抽象口号转换成可测量目标、明确故障边界,并验证恢复路径。
可用性指标
SLI、SLO 与 SLA
| 概念 | 作用 | 示例 |
|---|---|---|
| SLI | 实际测量的服务指标 | 成功请求数 / 有效请求数 |
| SLO | 团队希望达到的目标 | 30 天窗口内成功率 ≥ 99.9% |
| SLA | 对客户的正式承诺及后果 | 低于承诺后提供服务赔偿 |
内部 SLO 通常应比外部 SLA 更严格,为检测和处理事故保留空间。
面向请求的可用性可以表示为:
“Good” 必须包含用户真正关心的条件。HTTP 200 但返回错误业务结果、响应慢到超过截止时间,可能仍应计为失败。
参考:Google SRE Workbook:Implementing SLOs 与 Google SRE:Embracing Risk
可用性目标
下面按连续 30 天和 365 天估算最大不可用时间:
| 可用性目标 | 30 天错误预算 | 365 天错误预算 |
|---|---|---|
| 99% | 7 小时 12 分 | 3 天 15 小时 36 分 |
| 99.9% | 43 分 12 秒 | 8 小时 45 分 36 秒 |
| 99.99% | 4 分 19 秒 | 52 分 34 秒 |
| 99.999% | 约 26 秒 | 5 分 15 秒 |
每增加一个“九”,容许失败量缩小一个数量级,成本却往往非线性增长。应根据用户影响和业务损失选择目标,而不是默认追求 100%。
对于全球或部分可用的服务,按成功请求比例计算通常比简单的停机分钟数更有意义。
故障域
故障域是可能一起失效的一组资源。副本只有跨越独立故障域,才能真正降低共同失效概率。
需要逐层询问:
- 多个实例是否在同一节点?
- 多个节点是否在同一机架或可用区?
- 多个区域是否依赖同一控制面、身份系统或数据库?
- 配置错误和软件缺陷是否会同时传播到全部副本?
- 故障转移依赖的组件在事故中是否仍可使用?
基础设施独立并不能消除软件、配置和人为操作造成的相关性故障。
冗余、隔离与容量余量
冗余模式
| 模式 | 特点 | 主要风险 |
|---|---|---|
| Active-Active | 多个副本或站点同时承载流量 | 状态一致性、双写、故障域相关性 |
| Active-Passive | 备用容量平时不承载或少量承载 | 备用漂移、切换慢、长期未验证 |
| N+1 / N+2 | 为单个或多个实例故障预留余量 | 只覆盖预期数量的故障 |
| Quorum | 多数副本可用时继续读写 | 网络分区、延迟和成员变更 |
冗余必须配合隔离。若一个租户能耗尽共享线程池,或一个区域故障把全部流量压向容量不足的区域,副本会把局部故障扩散成全局故障。
故障后的容量
假设三个可用区平均分担流量,每个区域正常运行在 60% 容量。一个区域退出后,剩余两个区域各自要承接约 50% 的额外流量:
如果正常水位已经达到 80%,故障转移后理论需求会超过单区容量。高可用容量规划必须同时计算:
- 正常峰值;
- 最大计划内维护;
- 目标故障域退出后的峰值;
- 扩容完成前的短时余量;
- 恢复时数据同步和缓存预热的额外开销。
健康检查与故障转移
启动、存活与就绪探针
- 启动探针:应用是否已经完成初始化,避免启动阶段被误杀。
- 存活探针:进程是否陷入不可恢复状态,需要重启。
- 就绪探针:实例当前是否应该接收新流量。
高负载时把“响应稍慢”误判成“不存活”,可能导致实例被批量重启,进一步减少容量。依赖变慢时,通常应先取消就绪、停止接收新流量,再判断进程是否真的需要重启。
故障转移时序
每一步都要有明确时间预算和失败处理。故障检测太敏感会产生误切换,太迟钝则延长用户错误;自动切换如果没有自动回退或人工停止条件,也可能在两个站点间震荡。
数据高可用
计算副本可以快速重建,数据错误却可能永久传播。数据层需要同时讨论:
- 复制:副本数、同步或异步复制、确认条件;
- 一致性:故障与网络分区时允许读到多旧的数据;
- 持久性:已确认写入能否在节点或区域故障后保留;
- 备份:独立介质、不可变副本、保留周期和恢复验证;
- 恢复:切换后如何补齐增量、处理冲突和防止脑裂。
RPO 与 RTO
- RPO(Recovery Point Objective):最多允许丢失多长时间范围的数据。
- RTO(Recovery Time Objective):事故后最多允许多长时间恢复服务。
RPO 为 0 通常要求同步持久化到独立故障域,可能增加写入延迟;很短的 RTO 通常要求预置容量、自动化切换和经常演练。两者都必须对应具体故障场景。
备份成功不代表可以恢复。需要定期在隔离环境执行恢复,验证数据完整性、应用兼容性、权限和实际耗时。
防止级联故障
隔离舱
按依赖、租户、请求优先级或业务类型划分独立的线程池、连接池和并发预算,使一个故障源不能耗尽全部资源。
熔断
熔断器在依赖持续失败时快速拒绝,减少无效等待;半开状态用少量探测验证恢复。熔断不能替代超时,也不能只依据单次错误打开。
降级
预先定义核心与非核心功能:
- 返回旧缓存或部分结果;
- 暂停推荐、画像等非关键调用;
- 把同步工作改为稍后处理;
- 对低优先级租户或后台任务负载丢弃。
降级路径必须常态化测试。如果它只在事故时执行,往往会因为数据、权限或代码漂移而失效。
安全部署与计划内维护
发布本身是最常见的故障来源之一:
- 使用金丝雀或分批发布限制爆炸半径;
- 兼容新旧版本同时运行时的数据和协议;
- 设置最大不可用实例数和最小健康容量;
- 新实例通过启动与就绪检查后再接流量;
- 旧实例先停止新流量,再优雅处理在途请求;
- 依据 SLO 和关键业务指标自动暂停,而不是只看进程是否启动。
在 Kubernetes 中,PodDisruptionBudget 可以限制遵循 Eviction API 的自愿中断,但不能防止节点故障,也不能约束所有直接删除和滚动更新行为。
灾难恢复层次
| 模式 | 资源准备 | 恢复速度 | 成本 |
|---|---|---|---|
| Backup & Restore | 仅保留备份 | 最慢 | 最低 |
| Pilot Light | 保留核心数据和最小服务 | 较慢 | 较低 |
| Warm Standby | 备用环境以较小规模运行 | 较快 | 较高 |
| Multi-site Active-Active | 多站点持续承载流量 | 最快 | 最高、复杂度最高 |
模式选择由业务 RPO/RTO、数据一致性、合规和成本决定。不要在没有跨区域故障目标时直接建设最复杂的双活。
错误预算
错误预算是 SLO 允许的失败量:
如果 30 天 SLO 为 99.9%,错误预算就是有效事件的 0.1%。团队可以据此决定:
- 何时继续快速发布;
- 何时冻结高风险变更;
- 哪些可靠性工作优先;
- 告警是否对应错误预算的快速消耗。
告警应面向用户影响和预算消耗,而不是每个瞬时 CPU 波动。短窗口发现快速燃烧,长窗口发现持续缓慢退化。
故障验证
故障矩阵
为每种故障记录:
| 故障 | 预期影响 | 自动动作 | 人工动作 | 验证指标 |
|---|---|---|---|---|
| 单实例退出 | 无用户影响 | 负载摘除、补副本 | 无 | 成功率、剩余容量 |
| 单可用区退出 | 核心 SLO 不变 | 跨区转流量 | 确认容量 | SLI、跨区延迟 |
| 数据库主库故障 | 短时写入受限 | 选主或切换 | 检查一致性 | RTO、数据正确性 |
| 依赖超时 | 非核心功能降级 | 熔断、负载丢弃 | 联系依赖方 | Goodput、重试率 |
| 错误发布 | 小范围影响 | 暂停或回滚 | 决定继续/回滚 | 金丝雀 SLO |
故障演练
- 实例、节点、可用区和关键依赖故障;
- 流量峰值与故障同时发生;
- 主从切换、备份恢复和区域恢复;
- 错误配置与发布回滚;
- 遥测或控制面不可用时的人工操作;
- 从大规模重试和崩溃循环中恢复。
演练要测量实际 RTO/RPO 和用户 SLI,并把发现转化为自动化、Runbook 和架构改进。
设计检查
- [ ] 可用性以用户成功事件定义,并有明确 SLO。
- [ ] 已识别实例、节点、区域、依赖、配置和发布故障域。
- [ ] 副本分布与容量余量覆盖目标故障场景。
- [ ] 健康检查区分启动、存活和就绪,不会在高负载下批量误杀。
- [ ] 超时、隔离、熔断、限流和降级路径已经联合验证。
- [ ] 数据复制、备份、RPO、RTO 和一致性目标相互匹配。
- [ ] 发布限制爆炸半径,并能依据 SLO 自动暂停。
- [ ] 故障转移与恢复均经过带真实流量模型的演练。
参考资料
- Google SRE Book:Embracing Risk
- Google SRE Book:Availability Table
- Google SRE Book:Addressing Cascading Failures
- Google SRE Workbook:Implementing SLOs
- AWS:Well-Architected Reliability Pillar
- Kubernetes:Disruptions
- Kubernetes:Liveness, Readiness, and Startup Probes
- Diego Ongaro、John Ousterhout:In Search of an Understandable Consensus Algorithm