运维知识
悠悠
2026年9月20日

系统可用性从 99.5% 到 99.99%:数据从哪里来,四个 9 到底怎么算

月度复盘会上,监控大盘显示系统可用性已经达到 99.99%。

业务负责人却问了三个问题:“这个数字怎么算出来的?数据从哪里来?用户真的只受影响了 4 分钟吗?”

会议室一下安静了。

有人用 Pod 存活率计算,有人统计工单里的故障时长,还有人直接拿网关 5xx 比例当可用性。同一个系统,三种算法分别得到 99.96%、99.99% 和 100%。数字都很漂亮,却没有一个经得住追问。

系统从 99.5% 提升到 99.99%,真正困难的不是多加几台机器,而是建立一条完整证据链:先统一什么叫“可用”,再找到可信的原始数据,定位错误预算消耗在哪里,完成针对性改造,最后用相同口径持续验证。

99.99 不是架构图上的四个 9,而是统一口径下,每一次失败都能回到原始证据。

先算清楚:99.5% 和 99.99% 差的不是一点点

如果按时间计算,可用性的基本公式是:

可用性 =(统计周期总时间 - 不可用时间)÷ 统计周期总时间 × 100%

以一个 30 天的月份为例,总时间为:

30 × 24 × 60 = 43200 分钟

不同可用性对应的最大不可用时间如下:

可用性月度不可用时间年度不可用时间
99.5%216 分钟43 小时 48 分钟
99.9%43.2 分钟8 小时 45 分 36 秒
99.95%21.6 分钟4 小时 22 分 48 秒
99.99%4.32 分钟52 分 33.6 秒

从 99.5% 到 99.99%,错误预算由 0.5% 降到 0.01%,不是提升了 0.49 个百分点这么简单,而是要求失败占比缩小到原来的五十分之一。

这也意味着,一个持续 5 分钟的全站故障,就足以耗尽整个月 99.99% 的时间预算。一次发布抖动、一次主从切换超时,甚至一次错误的配置推送,都可能让当月目标直接失败。

不过,互联网系统通常不能只按“故障持续了几分钟”计算。一次局部故障可能只影响 3% 的请求;另一次故障虽然只有 30 秒,却可能让所有支付请求失败。更贴近用户体验的方式是按请求计算:

请求可用性 = 成功请求数 ÷ 有效请求总数 × 100%

成功请求数 = 有效请求总数 - 失败请求数

关键问题随之而来:什么叫失败请求?

我通常至少把以下情况算入失败:

  • 服务端返回 5xx。
  • 请求在网关、负载均衡器或客户端侧超时。
  • 连接被拒绝、异常断开或者 DNS 解析失败。
  • 返回 HTTP 200,但核心业务结果失败。
  • 延迟超过事先约定的阈值,例如支付接口超过 2 秒。
  • 降级后没有完成核心业务目标,例如查询成功但无法提交订单。

反过来,用户参数错误造成的 4xx,一般不应全部计入服务端不可用。但 401、403、409、429 是否计入,不能一刀切。认证服务异常引发的大量 401、容量不足引发的 429,本质上仍可能是系统问题。

因此,可用性计算必须先形成一份书面口径:统计哪些域名、接口和地区,健康请求如何定义,哪些状态码计入失败,延迟阈值是多少,计划维护是否计入,爬虫和内部探测流量是否排除。

如果前后口径发生了变化,两个数字就不能直接比较。

数据不能只来自一块监控大盘

想证明系统达到 99.99%,至少需要四类数据相互校验。

第一类是入口流量数据,包括 CDN、云负载均衡、Ingress、API 网关或反向代理的访问日志。这是计算请求可用性的主要依据,因为入口最接近用户。

每条日志至少应包含:

timestamp
request_id 或 trace_id
service
route
region
status_code
upstream_status
request_time
upstream_response_time
bytes_sent
client_type

如果只采集应用日志,连接未进入应用、Pod 已崩溃或者上游超时的请求可能完全没有记录,最终算出来的可用性会虚高。

第二类是外部拨测数据。应从用户实际访问路径出发,持续执行 DNS 解析、TCP/TLS 建连、HTTP 请求和关键业务流程探测。拨测能发现“服务端认为成功、用户却无法访问”的问题,例如证书过期、DNS 污染、CDN 回源失败和跨地域网络异常。

第三类是业务结果数据。HTTP 200 不等于业务成功。订单是否创建、支付是否完成、消息是否实际投递,需要通过业务事件、数据库状态或对账结果验证。

第四类是事件证据,包括告警、事故记录、发布记录、配置变更和云平台事件。这些数据不能直接代替可用性指标,但可以解释错误发生的时间、范围和原因。

一条可信的证据链应该是这样的:

入口失败请求
    ↓
request_id / trace_id
    ↓
网关、应用和依赖日志
    ↓
监控指标与外部拨测
    ↓
发布或基础设施事件
    ↓
事故记录与根因

只统计告警持续时间不可靠。告警阈值可能有延迟,也可能发生漏报。只统计用户工单同样不可靠,因为凌晨故障可能没人反馈。只看 Pod 是否 Running 更不可靠,Pod 存活时接口完全可能已经不可用。

这个 99.99% 具体怎样算出来

下面用一个脱敏后的典型场景说明,所有请求量和故障数据均为示例,不代表任何特定公司或真实客户。

某核心交易 API 将可用性定义为:

有效请求:
生产环境、外部用户、核心交易路由的全部请求,
排除压测流量、已识别爬虫和明显非法请求。

失败请求:
HTTP 5xx、网关超时、连接异常,
以及虽然返回 200 但交易状态为失败的请求。

延迟目标:
请求耗时不超过 2 秒。
超过 2 秒的请求也记为“不符合 SLI 的请求”。

改造前选取连续 90 天作为基线窗口:

有效请求总数:900,000,000
符合要求的请求:895,500,000
失败或超时请求:4,500,000

计算结果为:

895,500,000 ÷ 900,000,000 × 100% = 99.5%

对 450 万次失败请求按时间、路由、机房和错误原因聚合后,得到如下示例分布:

原因失败请求数占全部失败的比例
单可用区数据库连接池耗尽2,100,00046.7%
全量发布造成实例集中重启1,350,00030.0%
下游依赖超时向上游扩散720,00016.0%
DNS、网络抖动及其他问题330,0007.3%

这一步非常重要。它说明系统的主要矛盾不是“服务器数量不够”,而是数据库容量、发布方式和依赖隔离消耗了大部分错误预算。

改造完成并稳定运行后,再选取连续 90 天、相同接口范围和相同失败标准进行验证:

有效请求总数:1,200,000,000
符合要求的请求:1,199,880,000
失败或超时请求:120,000

计算结果为:

1,199,880,000 ÷ 1,200,000,000 × 100% = 99.99%

这里不能只把总数丢进 Excel。还要检查每个滚动 30 天窗口是否都达到目标,避免用三个月总量掩盖某一个月的严重故障;同时按地区、运营商、接口和客户端版本拆分,防止总体平均数掩盖局部用户不可用。

如果指标存储在 Prometheus 中,可以按实际标签设计使用类似查询。下面是只读查询,不会修改生产状态:

1 -
(
  sum(increase(http_requests_total{
    service="trade-api",
    sli_result="bad"
  }[30d]))
  /
  sum(increase(http_requests_total{
    service="trade-api"
  }[30d]))
)

这里的 sli_result="bad" 不是 Prometheus 自动生成的标签,需要在采集或指标加工环节,根据状态码、超时和业务结果统一标记。

外部拨测可用性可以用下面的只读查询交叉验证:

avg_over_time(
  probe_success{job="trade-api-public"}[30d]
)

如果请求指标为 99.99%,外部拨测却只有 99.8%,就不能急着宣布目标达成。需要继续排查拨测区域、DNS、证书、CDN和用户侧网络路径。

低流量系统还要特别小心。一个月只有 5000 次请求时,出现一次失败,可用性就是 99.98%。这并不能充分证明架构具备稳定的四个 9,应该结合更长观察窗口、固定频率拨测、故障演练结果和关键组件可靠性一起判断。

四个 9 不是靠扩容堆出来的

从错误分布看,改造动作必须直接对应错误预算的主要消耗项。

数据库侧先处理单可用区和连接耗尽问题:建立跨可用区高可用部署,限制每个应用实例的连接数,为连接池设置合理等待超时,并对慢查询和连接使用率设置提前告警。切换方案需要经过演练,不能等真实故障发生时才第一次执行。

发布侧把全量替换改成分批发布。先放一台或少量实例,观察错误率、P99 延迟、CPU、内存和连接数;达到停止条件立即暂停。如果平台支持,可以逐步放大到 5%、20%、50% 和 100% 流量。

依赖治理不是简单增加重试。没有上限的重试会在下游故障时放大流量,形成重试风暴。正确做法是同时设计超时、有限重试、退避、熔断、并发隔离和必要降级,并保证写操作具备幂等性。

入口和应用还需要消除单点:实例分散到不同故障域,负载均衡执行真正的就绪检查,容量预留覆盖单个故障域退出后的峰值流量。对关键静态数据或允许短暂陈旧的数据,可以使用缓存和兜底结果,减少单一依赖失败造成的连锁反应。

这些都属于有状态的生产变更,存在流量中断、数据不一致和容量不足风险。修改前应备份配置并确认回滚版本,在测试环境完成验证,再从单实例或小流量开始。观察错误率、延迟、饱和度和业务成功率,任何核心指标越过停止线都应立即回滚。

临时止损和根因修复也要分开。扩容、重启和切流可能让服务恢复,却不能证明问题已经解决。只有导致连接耗尽、集中重启或超时扩散的机制被消除,并经过持续观测和演练验证,才算完成修复。

为什么很多“99.99%”经不起审计

最常见的问题是分母被人为缩小。例如只统计成功进入应用的请求,那么在网关就失败的流量根本没有进入计算。

另一个问题是事后修改口径。系统故障发生后,把出问题的接口说成“非核心接口”,或者把故障时段定义为“计划维护”,数字自然会变好。正确做法是统计周期开始前固定口径,任何调整都要保留版本和生效时间。

还有一种做法是把多个实例的平均健康率当成用户可用性。10 个实例中有一个持续故障,如果流量仍被分配过去,用户就会收到失败;如果故障实例已及时摘除,用户可能完全无感。实例健康率只能解释原因,不能代替用户侧 SLI。

计划维护是否计入,也必须由 SLA 或 SLO 文档提前确定。对外承诺中如果明确排除通知充分的计划维护,可以单独计算;内部稳定性目标则建议保留一份包含所有影响的全量口径,避免通过维护窗口美化数据。

更不能用单月偶然达到 99.99% 就宣布架构已经具备四个 9。比较稳妥的判断是:相同口径下,连续多个完整周期达标;外部拨测与业务结果一致;重大故障没有被平均数隐藏;故障演练能够验证切换、降级和回滚机制。

这份检查清单建议直接收藏

  • 不要只看 5xx。 超时、连接失败、慢请求和业务失败同样会让用户无法完成操作。
  • 禁止用 Pod 存活率代替服务可用性。 基础设施正常不代表用户请求成功。
  • 务必在统计前固定分子、分母和排除项。 中途改口径会让前后数据失去可比性。
  • 千万别只使用应用日志。 没有进入应用的失败请求会被遗漏,必须结合网关、负载均衡和外部拨测。
  • 不要只报整体平均值。 应按地区、接口、运营商和客户端拆分,避免局部故障被大流量稀释。
  • 务必保留原始日志和聚合规则版本。 否则无法复算,也无法解释某次可用性突然上升或下降。
  • 不要把一次重启恢复当成根因修复。 恢复只是止损,连接耗尽、发布抖动或依赖扩散机制仍可能再次触发。
  • 所有高风险变更必须具备回滚路径。 数据库切换、扩缩容、流量迁移和超时调整,都应先小范围验证并设置停止条件。
  • 禁止把一次达标写成长期能力。 至少观察多个完整周期,并通过拨测、业务对账和演练交叉验证。
  • 务必监控错误预算消耗速度。 与其等月底发现不达标,不如在短时间快速消耗预算时立即告警。

系统可用性从 99.5% 提升到 99.99%,本质上经历了四个步骤:定义用户真正关心的成功,建立可追溯的数据来源,找到错误预算的主要消耗项,然后用针对性改造和持续观测证明结果。

如果你正在负责一个线上系统,可以马上做一件事:随机抽取最近一次故障,尝试从最终的可用性数字一路追溯到网关请求、外部拨测、业务结果、变更记录和事故原因。任何一环断掉,都说明当前的 99.99% 还缺少证据。

如果这篇文章对你建立可用性指标有帮助,欢迎点赞、在看并转发给负责稳定性建设的同事。

公众号:耕云躬行录

个人博客:躬行笔记

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码