"高可用"的谎言:你的 99.99% 是怎么算出来的

目录

关键结论(速览):

1. 引言

"Everything fails, all the time."

— Werner Vogels, CTO of Amazon

2022 年 12 月 18 日,阿里云发生了一次持续超过 12 小时的严重故障。受影响的服务包括 ECS、OSS、RDS、SLB 等核心产品,波及大量企业客户。

阿里云 ECS 的 SLA 承诺可用性不低于 99.975%。按月度计算,这意味着每月允许的停机时间大约是 6.57 分钟。12 小时的故障,超出承诺的 109 倍。按照 SLA 条款,需要赔偿月度服务费的 100%——以代金券形式。

但真正值得思考的问题不是"阿里云为什么挂了",而是:

这个 99.975% 到底是怎么算出来的?它有多大的参考价值?

如果你已经了解 MTTF、MTTR 和基本可用性公式,可以直接继续。如果需要回顾这些基础概念,请参考高可用和数据库冗余实践一文中的详细推导。本文假设你已经熟悉这些概念,我们直接进入深水区。

2. "几个 9" 的直觉陷阱

2.1. 数字的魔力

"几个 9" 大概是高可用领域最有传播力的概念了。把它列成表:

可用性 通称 年停机时间 月停机时间 周停机时间
99% 两个 9 3.65 天 7.31 小时 1.68 小时
99.9% 三个 9 8.77 小时 43.83 分钟 10.08 分钟
99.99% 四个 9 52.60 分钟 4.38 分钟 1.01 分钟
99.999% 五个 9 5.26 分钟 26.30 秒 6.05 秒

数字看起来很精确,对吧?5.26 分钟、26.30 秒——精确到让人产生一种"这是经过严格计算的工程指标"的错觉。

但这些数字的来源,只是一个简单的除法。

2.2. 串联与并联:教科书公式

可用性的基础公式:

\[ A = \frac{MTTF}{MTTF + MTTR} \]

当多个组件 串联 时(任一故障导致系统故障),系统可用性:

\[ A_{串联} = A_1 \times A_2 \times \cdots \times A_n \]

这意味着串联越多,越脆弱。一个由 10 个 99.9% 的组件串联的系统,整体可用性只有 \( 0.999^{10} \approx 99.0\% \)。

当多个组件 并联冗余 时(所有副本都故障才算系统故障),通常的计算是:

\[ A_{并联} = 1 - (1 - A)^n \]

两个 99.9% 的组件做主备冗余,理论上可以达到 \( 1 - 0.001^2 = 99.9999\% \)。

高可用和数据库冗余实践中,我们用 MTTF 和 MTTR 推导过:双副本冗余让有效 MTTF 提升约 4000 倍。数学上无可挑剔。

但这里有一个关键假设,教科书往往一笔带过:

所有故障必须是统计独立的。

即:组件 A 发生故障的概率,不影响组件 B 发生故障的概率。

\( P(F_A \cap F_B) = P(F_A) \cdot P(F_B) \)

这个条件在真实世界几乎从不成立。

接下来的两个章节,我们会看到这个假设是如何崩塌的。

3. SLA 合同里的文字游戏

在讨论故障模型之前,先来看一个更直接的问题:SLA 合同本身就是一套精心设计的数字游戏。

3.1. 三家云厂商 SLA 对比

我们选取三家主流云厂商的计算实例(虚拟机)SLA 条款进行对比:

维度 阿里云 ECS AWS EC2 腾讯云 CVM
承诺可用性 99.975% 99.99% 99.975%
计算周期 自然月 月度 自然月
"不可用"定义 实例无法启动或运行 运行中的实例无法连接 实例不可用
赔偿方式 代金券 信用额度(可抵扣账单) 代金券
最高赔偿 月费 100% 月费 30% 月费 100%
排除条款 用户误操作、不可抗力、非阿里云原因等 不可抗力、用户滥用等 用户误操作、不可抗力等
故障认定 需用户主动申报 需用户提交工单 需用户申报

注意:以上对比基于各厂商公开文档整理,条款会更新。实际使用时请以最新版 SLA 为准。关键的不是具体数字,而是下面分析的 结构性差异

3.2. 陷阱一:计算窗口——月度 vs 年度

假设一个系统在一年中发生了 3 次故障,分别是:

  • 1 月:停机 4 小时
  • 6 月:停机 2 小时
  • 11 月:停机 3 小时

如果按 年度 计算: \[ A_{年} = 1 - \frac{4 + 2 + 3}{365 \times 24} = 1 - \frac{9}{8760} \approx 99.897\% \]

指不到三个 9,看起来挺糟的。

但如果按 月度 计算——有 9 个月可用性是 100%(没有故障),剩下 3 个月分别是:

  • 1 月:\( 1 - \frac{4}{744} \approx 99.46\% \)
  • 6 月:\( 1 - \frac{2}{720} \approx 99.72\% \)
  • 11 月:\( 1 - \frac{3}{720} \approx 99.58\% \)

如果 SLA 条款是"任意月度可用性不低于 99.9%",那只有这 3 个月触发赔偿。如果条款是"年度平均可用性",那 9 个月的 100% 会 稀释 故障的影响。

同一组故障事件,在不同 SLA 规则下的结论:

           年度计算      月度计算(最差月)  月度计算(平均月)
           ─────────     ─────────────       ────────────────
可用性     99.897%       99.46%              99.925%
是否达标   ✗ (< 99.9%)  ✗ (< 99.9%)         ✓ (> 99.9%)

合同条款往往倾向于采用对供应商更有利的计算窗口和聚合方式。 这里不是说所有厂商都这样计算,而是想说明:一旦计算窗口、故障定义、免责条款被写进合同,"几个 9"的含义就已经发生了变化。

3.3. 陷阱二:"不可用"的弹性定义

SLA 中最关键的概念是"不可用"(unavailability),但它远不是"服务挂了" 那么简单。

常见的不算"不可用"的情况:

  1. 性能降级 :响应时间从 50ms 升到 5s,但请求还是返回了——不算停机
  2. 部分不可用 :10 个可用区中 1 个挂了——可能不算(取决于条款如何定义"影响区域")
  3. 间歇性故障 :每分钟有 5% 的请求失败——可能不算(如果阈值定义为"连续 1 分钟失败率超过 50%")

一个极端但合规的例子:系统 99% 的时间完美运行,剩下 1% 的时间响应延迟增加 100 倍、错误率 10%——但因为"还有响应"且"错误率未超过阈值",SLA 考核显示可用性 100%。

这就是为什么:

SLA 中的 "99.99%" 描述的不是你的用户体验,而是 SLA 条款定义的可用性。

两者之间可以有巨大的鸿沟。

3.4. 陷阱三:排除条款的黑洞

几乎所有云厂商的 SLA 都包含大量排除条款。以下是典型的不算入停机时间的情况:

  • 计划内维护 :提前通知的升级窗口
  • 用户自身原因 :配置错误、超出配额、账户欠费
  • 不可抗力 :自然灾害、政策变更、网络运营商故障
  • 第三方原因 :DNS 服务商故障、CDN 供应商问题
  • 安全事件 :DDoS 攻击导致的服务降级

问题是:很多重大故障的根本原因 可以被归类到排除条款中 。DNS 配置错误是"网络问题"还是"平台故障"?上游供应商组件的 bug 是"第三方原因"还是"平台选型责任"?

这些灰色地带给了厂商大量的解释空间。

3.5. 小结:你看到的数字不是你以为的数字

             标称可用性                     实际体验可用性
             ─────────                      ──────────────

定义         SLA 条款规定的                  用户感知到的
             "不可用"计算结果                服务质量

降级         通常不算                        算
排除条款     大量排除                        无排除
计算窗口     月度(最有利的)                实时
故障认定     需主动申报                      自动感知

差距         可能高达 1~2 个数量级

4. Correlated Failure:关联故障让冗余公式失效

到这里我们已经知道 SLA 数字本身就有水分。但更深层的问题是:*即使按照最理想的 SLA 计算方式,冗余带来的可用性提升也被严重高估了。*

原因在于:故障不是独立的。

4.1. 什么是关联故障

*关联故障*(Correlated Failure),也叫 *共模故障*(Common Mode Failure),是指多个"独立"组件由于共享某些基础设施或条件,而在同一时间段内发生故障。

分类:

关联故障的来源
├── 物理层关联
│   ├── 同一机架:共享电源、交换机、散热
│   ├── 同一机房:共享电力、网络出口、冷却系统
│   └── 同一区域:自然灾害、电网故障
│
├── 软件层关联
│   ├── 相同 OS 版本:同一个内核 bug 影响所有节点
│   ├── 相同依赖库:CVE 暴露所有使用该库的服务
│   └── 相同配置:一次错误推送干掉所有实例
│
├── 人为关联
│   ├── 运维操作:同一个人对所有节点执行了相同的错误命令
│   ├── 部署发布:新版本 bug 同时上线到所有副本
│   └── 配置变更:全量推送(而非灰度)
│
└── 时间/负载关联
    ├── 流量高峰:所有节点同时接近过载
    ├── 定时任务:cron job 同时触发 → 瞬间资源争抢
    └── 级联依赖:上游慢了 → 所有下游同时超时

4.2. 数学:引入关联系数后的真实可用性

下面的模型不是严格的可靠性工程公式,而是一个足够直观的 启发式模型 ,用来说明核心论点:即使是微弱的关联性也会大幅削弱冗余收益。工程实践中常用的关联故障建模方法还包括 Beta-factor 模型和 Common Cause Failure(CCF)分析。

在独立假设下,两个副本同时故障的概率是:

\[ P(F_1 \cap F_2) = P(F_1) \cdot P(F_2) \]

但如果引入关联性,我们需要用 条件概率 来建模。定义故障关联系数 \( \rho \),表示一个副本故障时另一个副本也故障的条件概率超出独立预期的程度:

\[ P(F_1 \cap F_2) = P(F_1) \cdot P(F_2) + \rho \cdot P(F_1) \cdot (1 - P(F_1)) \]

其中 \( \rho = 0 \) 退化为独立情况,\( \rho = 1 \) 表示完全关联(一个挂了另一个 100% 也挂)。

对于 n 个副本的并联系统,引入关联系数后(使用简化的对称关联模型):

\[ A_{关联并联} \approx 1 - \left[ (1-A)^n + \rho \cdot (1-A) \cdot (1 - (1-A)^n) \right] \]

当 \( \rho \) 较小时可近似为:

\[ A_{关联并联} \approx 1 - (1-A)^n - \rho \cdot (1-A) \]

这个公式的关键含义是:*当 \(\rho > 0\) 时,系统不可用概率有一个下限,约等于 \(\rho \cdot (1-A)\),与副本数 n 几乎无关。*

换句话说:再加多少个副本,只要存在关联故障可能性,增益就会撞上天花板。

4.3. 数据说话

假设单节点可用性 99.9%(三个 9),3 个副本并联:

关联系数 \(\rho\) 理论可用性(独立) 实际可用性(关联) 差距
0 99.9999999% 99.9999999% 0
0.001 99.9999999% 99.9999% 1000x 差
0.01 99.9999999% 99.999% 10000x 差
0.05 99.9999999% 99.995% 100000x 差
0.1 99.9999999% 99.99% 从 9 个 9 跌到 4 个 9
0.5 99.9999999% 99.95% 从 9 个 9 跌到不到 3.5 个 9

\( \rho = 0.1 \) 意味着什么?一个副本故障时,另一个副本有 10% 的概率也会受到影响。考虑到共享机房、共享网络、共享 OS 版本这些因素,这个数值在很多真实场景中可能并不算大。当然,具体的 \( \rho \) 值取决于架构隔离程度——这里的重点不在精确的数字,而在趋势:即使很小的关联性也会吃掉大部分冗余增益。

correlation-curve.svg

4.4. 真实案例

4.4.1. AWS S3 中断(2017 年 2 月 28 日)

一名工程师在执行常规维护时,运行了一条命令来移除少量 S3 子系统的服务器。由于输入参数错误,移除的服务器远超预期,导致 us-east-1 区域的 S3 服务大面积不可用,持续约 4 小时。

关联模式 :软件层 + 人为关联。同一个运维操作影响了本应隔离的多个子系统。AWS 事后引入了速率限制——即使是删除操作,也不能一次影响太多服务器。

4.4.2. Facebook 全球宕机(2021 年 10 月 4 日)

BGP 配置变更导致 Facebook 的所有服务从互联网上消失,持续约 6 小时。

更致命的是:由于内部工具也依赖这些网络基础设施,工程师无法通过远程方式修复问题。最终需要 物理派人去数据中心 手动操作。

关联模式 :物理层 + 软件层 + 人为关联的完美风暴。管理工具与生产系统共享网络基础设施,形成了死循环:网络挂了 → 没法用管理工具修网络。

4.4.3. Google 全球认证服务中断(2020 年 12 月 14 日)

Google 的全球身份认证服务(Account Authentication)发生约 47 分钟的中断。根因是身份认证服务使用的配额管理系统数据库出现问题:一次存储迁移未能正确保留配额数据,导致认证服务认为自身配额已耗尽,拒绝处理所有请求。

触发点 :存储迁移中的配额数据丢失。 共享依赖 :几乎所有 Google 服务(Gmail、YouTube、Cloud Console 等)都依赖同一套认证服务。 放大路径 :认证失败 → 所有需要登录的服务 同时 不可用。 为什么冗余失效 :多区域冗余只保护了计算和网络层面,但所有区域共享同一个认证系统的配额逻辑——单点逻辑故障让全球冗余形同虚设。

4.5. 关联故障的核心教训

冗余的有效性上限取决于关联度,而不是副本数量。

如果你的 3 个副本共享机房电源、运行相同 OS 版本、由同一个人运维,那么 "3 副本冗余"在关联故障面前可能只比单机好一点点。

5. Cascading Failure:级联故障——倒下的多米诺骨牌

如果说关联故障让冗余公式的 假设 失效,那么级联故障就是让冗余直接 帮倒忙

5.1. 机制解析

级联故障的核心机制:一个组件故障 → 其负载被转移到其他组件 → 其他组件因额外负载而过载 → 继续转移 → 连锁崩溃。

级联故障时间线

t=0   [A] 故障               [B] [C] [D] 正常,各 60% 负载
        ↓ A 的负载被转移
t=1   [A] 故障               [B] 80%  [C] 80%  [D] 80%
                                ↓ B 开始变慢,响应超时增加
t=2   [A] 故障  [B] 过载      [C] 90%  [D] 90%
                    ↓ B 的请求转移到 C 和 D
t=3   [A] 故障  [B] 故障      [C] 过载  [D] 过载
                                ↓ 全部崩溃
t=4   [A] [B] [C] [D] 全部不可用
        ↓
      总停机时间 >> 单节点故障的停机时间

5.2. 排队论视角:为什么 "70% 不是安全的"

在服务系统中,延迟和吞吐的关系不是线性的。根据 M/M/1 排队模型,平均等待时间:

\[ W = \frac{1}{\mu - \lambda} \]

其中 \( \mu \) 是服务速率,\( \lambda \) 是到达速率。利用率 \( \rho_{util} = \frac{\lambda}{\mu} \)。

当利用率接近 1 时,等待时间趋向无穷大:

利用率 相对延迟(以 50% 为基准)
50% 1x
70% 1.67x
80% 2.5x
90% 5x
95% 10x
99% 50x

这就是为什么当一个节点故障、其负载被分摊到其他节点时,系统不是"稍微变慢"而是"指数级恶化"。

假设 4 个节点各承担 70% 负载(看起来很安全)。一个节点故障后,剩余 3 个节点每个需要承担 \( \frac{4 \times 70\%}{3} \approx 93.3\% \) 的负载。延迟从 1.67x 跳到 7.5x——这就是级联崩溃的触发点。

cascading-timeline.svg

5.3. 冗余的悖论

传统思维:"加更多节点,提高冗余,这样单节点故障的影响就更小了。"

但是:

  • 更多节点 = 单节点故障转移到每个剩余节点的增量更小 ✓
  • 更多节点 = 发生至少一个故障的概率更高 ✗
  • 更多节点 = 更复杂的依赖关系,更多潜在的级联路径 ✗

如果单节点故障率为 p,n 个节点中至少一个故障的概率为: \[ P_{至少一个故障} = 1 - (1-p)^n \]

节点数 单节点故障率 0.1% 至少一个故障的概率
3 0.1% 0.30%
10 0.1% 1.00%
50 0.1% 4.88%
100 0.1% 9.52%

当节点数达到 100 时,几乎 10% 的时间里你的集群中都有至少一个节点在故障状态。如果你的系统不能优雅处理这种情况,级联崩溃就只是时间问题。

5.4. 真实案例

5.4.1. Cloudflare 骨干网故障(2020 年 7 月 17 日)

一次网络配置变更导致骨干网流量被错误路由,影响面迅速从一个数据中心扩展到全球。故障持续约 27 分钟。

级联过程 :配置变更 → 部分数据中心路由异常 → 流量涌向其他数据中心 →其他数据中心过载 → 更多路由异常 → 全球影响。

5.4.2. 阿里云 2022.12.18 事件(深入分析)

根据公开信息,此次故障的根本原因涉及 *OSS*(对象存储)服务的组件异常,由于大量其他服务依赖 OSS,形成了典型的级联故障模式:

OSS 组件异常
    ↓
依赖 OSS 的服务(SLB/RDS/ECS 等)开始超时
    ↓
超时导致重试风暴
    ↓
重试流量进一步压垮 OSS
    ↓
更多服务不可用
    ↓
运维工具也依赖受影响的服务 → 修复困难

关键教训 :一个底层服务故障通过依赖链放大为全平台级联故障。这是级联故障和关联故障的叠加效应。

6. 蒙特卡洛模拟:你的 "几个 9" 到底值多少

理论分析只是一面,让我们用模拟数据亲眼看看 "99.99%" 在不同条件下到底值多少。

注意: 这个模拟器是一个教学工具,不是生产级可用性计算器。它使用的简化关联模型旨在直观展示趋势,而非给出精确预测。不要把模拟结果直接用于容量规划或 SLA 谈判。

下面的交互式模拟器让你直接调节参数,观察理论可用性和实际可用性之间的差距。

核心发现 :把关联系数 \( \rho \) 从 0 拨到 0.1——你会看到系统可用性从"五个 9"断崖式跌落。

参数调节
节点数3
单节点可用性99.9%
故障关联系数 ρ0
启用级联模型
节点基准利用率60%

点击上方按钮开始模拟

预设场景

7. 正确的思考框架

看到这里,也许你会觉得高可用是一个不可能的任务。但并非如此——问题不在于追求高可用本身,而在于我们使用了错误的指标和错误的思维模型。

7.1. MTTR 比 MTTF 更重要

回到可用性公式:

\[ A = \frac{MTTF}{MTTF + MTTR} \]

假设当前 MTTF = 10,000 小时,MTTR = 1 小时,则 A = 99.99%。

现在考虑两种投资方向:

投资 A:MTTF 翻倍 \[ A_{new} = \frac{20000}{20000 + 1} = 99.995\% \] 提升了 0.005 个百分点。

投资 B:MTTR 减半 \[ A_{new} = \frac{10000}{10000 + 0.5} = 99.995\% \]

效果相同!但考虑到:

  • MTTF 翻倍需要大幅提升硬件质量或增加冗余(成本指数增长)
  • MTTR 减半通常只需要更好的监控、自动化和演练(成本线性增长)

在大多数互联网服务场景下,MTTR 优化通常更划算。

更重要的是,MTTR 的优化不依赖"故障独立"这个幻觉。无论故障是独立的还是关联的,快速恢复都能减少影响。

7.2. Error Budget:SRE 的务实框架

Google SRE 团队提出了 Error Budget(错误预算)的概念,这是一个比"追求更多的 9"更务实的框架。

核心理念:

100% 可用性是错误的目标。如果你的系统 真的 做到 100%,说明你在可靠性上过度投资了——这些资源本可以用于创新。

具体操作:

  1. 根据业务需求设定 SLO(服务等级目标),比如 99.95%
  2. 计算错误预算:一个月有 \( 30 \times 24 \times 60 = 43200 \) 分钟, 99.95% 的错误预算是 21.6 分钟
  3. 只要错误预算没用完,团队可以大胆发布新功能
  4. 如果错误预算接近耗尽,暂停功能发布,专注稳定性

这个框架把"可用性"从一个空洞的数字变成了一个 可操作的资源分配工具

7.3. 混沌工程:主动制造故障

Netflix 的 Chaos Monkey 是混沌工程的先驱:在生产环境中随机杀死服务实例,强迫工程师构建真正能容错的系统。

核心价值:

  • 在故障 真正发生 之前,找到系统的弱点
  • 验证监控和告警系统是否真的有效
  • 训练团队的应急响应能力
  • 发现那些隐藏的关联故障模式

"你不是在测试系统能不能扛住故障,你是在测试你的 团队 能不能扛住故障。"

7.4. Defense in Depth:多层防御

与其追求单点的极致可用性,不如在多个层次构建防御:

防御层 机制 目标
流量层 限流、熔断、降级 防止过载
服务层 超时控制、重试退避、隔离仓 防止级联
数据层 多副本、跨区域备份 防止数据丢失
运维层 灰度发布、配置灰度、快速回滚 防止人为错误
组织层 故障演练、On-call 机制、Postmortem 防止重复犯错

这些层级的设计可以参考 CAP 定理再审视 一文中关于 PACELC 模型的分析——在非分区状态下,延迟和一致性的权衡同样影响你的可用性策略。

8. 总结:三个你应该问的问题

下次有人告诉你"我们的系统 99.99%",请问他三个问题:

1. 你的故障是独立的吗?

你的副本是否共享机房?共享 OS 版本?由同一个团队运维?通过同一个自动化系统部署?如果答案是"是",那你的冗余公式就需要打一个很大的折扣。

2. 你怎么定义"不可用"?

延迟 10 倍算不算?错误率 5% 算不算?部分区域不可用算不算?SLA 条款中 "不可用"的定义,和你的用户感知到的"不可用",可能是两个完全不同的概念。

3. 你上次全链路故障演练是什么时候?

如果答案是"没做过"或者"去年",那所有的可用性数字都只是一个美好的愿望。真正的高可用不是算出来的——它是 演练 出来的。

几个 9 不是没用——它仍然是合同、预算、容量规划的共同语言和起点。只是,它绝不是可靠性的终点。把"几个 9"当地图没问题,只要你记住:地图不是疆域。

本文属于 「你以为你懂」 系列——颠覆性深度分析。

推荐继续阅读:


By .