availability模式
Availability 怎么算、怎么提高:几个 9 对应多少停机时间,串联与并联公式及其独立性前提,一条结账链路的逐项算例;active-passive 与 active-active 的切换过程,健康检查、DNS、数据库故障切换各花多少秒(ALB、Route 53、RDS Multi-AZ、Patroni 的真实参数),脑裂与 fencing,常见翻车和面试答法。
Availability 指系统在某个时间段内持续可用、能完成其功能的比例。这一章回答三个问题:目标是几个 9、对应多少停机时间;多个组件串起来、并起来以后整体还剩多少;一个组件坏了,从坏掉到流量切走实际要多少秒。 SLO 与 error budget 的写法见 SLA / SLO / SLI,整个区域丢失后的 RTO / RPO 见 Disaster Recovery。
The Nine's of availability
Availability 常用 uptime(或 downtime)占比来度量,也叫「几个 9」。
$$ Availability = \frac{Uptime}{(Uptime + Downtime)} $$
99.00% 称为 "2 nines",99.9% 是 "3 nines",以此类推。下表按一年 365.25 天、一个月 30 天、一周 7 天计算:
| Availability (Percent) | Downtime (Year) | Downtime (Month) | Downtime (Week) |
|---|---|---|---|
| 90% (one nine) | 36.53 days | 72 hours | 16.8 hours |
| 99% (two nines) | 3.65 days | 7.20 hours | 1.68 hours |
| 99.9% (three nines) | 8.77 hours | 43.2 minutes | 10.1 minutes |
| 99.99% (four nines) | 52.6 minutes | 4.32 minutes | 1.01 minutes |
| 99.999% (five nines) | 5.26 minutes | 25.9 seconds | 6.05 seconds |
| 99.9999% (six nines) | 31.56 seconds | 2.59 seconds | 604.8 milliseconds |
| 99.99999% (seven nines) | 3.16 seconds | 259 milliseconds | 60.5 milliseconds |
| 99.999999% (eight nines) | 315.6 milliseconds | 25.9 milliseconds | 6 milliseconds |
| 99.9999999% (nine nines) | 31.6 milliseconds | 2.6 milliseconds | 0.6 milliseconds |
读这张表要带着第三部分的数字:99.99% 每月只有 4 分 19 秒,而一次数据库自动故障切换常常就要一两分钟。
有约束的设计问题
一个在线课程站的结账链路:ALB → 应用服务 → PostgreSQL → 第三方支付网关。业务要求结账可用性 99.95%,也就是每 30 天不超过 21.6 分钟。下面各组件的可用性是本章的假设值,用来演示计算方法:
- ALB:99.99%
- 应用服务:单个实例 99.5%,部署 3 个,分布在 3 个可用区
- PostgreSQL:主库加同城备库、自动切换,按 99.95% 算
- 支付网关:SLA 99.9%
这个目标能不能达到?差在哪一环?
Availability in Sequence vs Parallel
Sequence
请求必须依次经过每个组件,任何一个坏了请求就失败,整体 availability 是各项相乘:
$$ Availability \space (Total) = Availability \space (Foo) * Availability \space (Bar) $$
Foo 与 Bar 都是 99.9%,串联后约 99.8%。串联只会越来越低,而且结果不会高于最差的那一环。
Parallel
多个副本中任意一个能工作就算可用,整体不可用的概率是各项不可用概率相乘:
$$ Availability \space (Total) = 1 - (1 - Availability \space (Foo)) * (1 - Availability \space (Bar)) $$
Foo 与 Bar 都是 99.9%,并联后是 99.9999%。
并联公式有两个前提,面试里说出来比背公式更重要:
- 故障相互独立。 三个实例跑同一份代码、读同一份配置,一次错误发布会让它们同时坏;放在同一个可用区,一次机房电力故障会让它们同时坏。相关故障下,并联的收益远小于公式。
- 切换是自动的而且足够快。 公式假设坏掉的副本立即被绕开。实际上从坏掉到流量切走要经过检测和切换,这段时间照样计入停机。
算一遍结账链路
应用层 3 个实例并联:1 − 0.005³ ≈ 99.99999%,几乎可以忽略。整条链路串联:
0.9999 × 0.99999988 × 0.9995 × 0.999 ≈ 99.84% → 每 30 天约 69 分钟
离 99.95% 差得很远,最大的一项是支付网关。接第二家支付网关作为并联备选(各 99.9%,假设故障独立且能自动切换):
支付层:1 − 0.001² = 99.9999%
整条链路:0.9999 × 0.99999988 × 0.9995 × 0.999999 ≈ 99.94% → 每 30 天约 26 分钟
仍然差一点,现在瓶颈变成了数据库。结论不是「再加副本」,而是逐项看:数据库切换时间能不能缩短,或者把结账改成数据库短暂不可用时先把订单写进队列、稍后再处理,让数据库不再处于同步路径上。这种算法能直接告诉你钱该花在哪一环。
故障切换:Active-passive 与 Active-active
| 模式 | 谁在接流量 | 切换要做什么 | 典型停机 | 主要风险 |
|---|---|---|---|---|
| Active-passive | 只有主节点;备节点通过心跳监控主节点 | 检测主节点失效 → 提升备节点 → 客户端改连新地址 | 秒级到分钟级,取决于检测和提升 | 脑裂;备节点是冷的,接管瞬间缓存全空 |
| Active-active | 所有节点同时接流量 | 负载均衡把坏节点剔出去,其他节点分担 | 检测时间内的部分请求失败 | 剩下的节点要能扛住全部负载;有状态数据要处理并发写冲突 |
无状态的应用服务几乎总是 active-active;数据库主库大多是 active-passive,因为两个节点同时接受写入要解决冲突,见 Database Replication。
Active-active 有一条容易忽略的容量规则:3 个可用区各跑一份,要容忍任意一个可用区丢失,平时每个可用区的利用率就不能超过约三分之二,否则剩下两个会被转移过来的流量压垮,一个可用区的故障变成全站故障。
从坏掉到切走,每一段花多少秒
停机时间 = 检测 + 决策 + 切换 + 客户端重连。每一段都有真实参数:
负载均衡健康检查。 ALB 对 instance / ip 类型目标的默认值:每 30 秒检查一次,超时 5 秒,连续失败 2 次判为不健康,连续成功 5 次才恢复。一个实例挂掉后,最坏大约 60 秒才被剔除,这 60 秒里分到它的请求会失败。把间隔调到 10 秒,检测时间缩短到约 20 秒,代价是健康检查请求变多。
DNS 故障切换。 Route 53 健康检查的间隔可选 30 秒(标准)或 10 秒(fast,额外收费),连续失败次数达到你设的 failure threshold 才判为不健康。判定之后,客户端还要等本地缓存的 DNS 记录过期,所以记录的 TTL 要设短,例如 60 秒;有些客户端和运行时会缓存 DNS 比 TTL 更久,这部分只能靠应用重试。
数据库自动切换。 RDS Multi-AZ(单备库)文档写的是故障切换通常 60–120 秒,大事务或较长的恢复过程会更久。自建 PostgreSQL 常用 Patroni,它的默认参数是 ttl: 30(leader 锁的 TTL,可以理解为自动故障切换开始前的等待时间)、loop_wait: 10、retry_timeout: 10,并要求 loop_wait + 2 × retry_timeout ≤ ttl。把 ttl 调小能更快切换,但网络抖一下就可能误切。
客户端重连。 数据库切换完成后,连接池里的旧连接要先报错再重建;应用如果没有重试,这一步会让切换后的第一批请求全部失败。
回到结账链路:数据库每月发生一次切换,按 90 秒算,加上连接池重建,就用掉 21.6 分钟预算里的一大块。可用性目标越高,越需要把这几段的秒数写进设计里。
脑裂与 fencing
Active-passive 最危险的故障不是切不过去,而是切了两次:主库和备库之间网络断开,备库以为主库挂了,把自己提升为主库,而原主库其实还活着、还在接受写入。两边各写各的,恢复后数据无法合并。
防脑裂靠两件事:
- 多数派或租约。 只有拿到多数派投票、或持有分布式锁(租约)的节点才能当主。Patroni 的做法是:节点只有能更新 DCS(etcd、Consul 等)里的 leader 锁,才被允许作为主库运行;更新失败时,PostgreSQL 会立即被降级为只读。
- Fencing。 新主库上任前,确保旧主库不能再写入,例如通过 API 关掉旧实例、撤销它的存储访问,或让下游拒绝旧任期号的写入。
健康检查查什么
- 浅检查(进程活着、端口能连):发现不了「进程在但数据库连接池耗尽」这类半死状态。
- 深检查(顺带查数据库和下游):数据库一抖,所有实例同时判为不健康。ALB 文档写明,当目标组里所有目标都不健康时,负载均衡会 fail open,把流量发给所有目标。这个行为避免了全站 0 流量,但也说明深检查在共享依赖故障时起不到隔离作用。
常见做法是健康检查只查实例自身能不能处理请求(进程、线程池、本地资源),下游依赖的故障交给超时、熔断和降级处理,见 Circuit breaker。
常见翻车
| 翻车 | 用户看到什么 | 修法 |
|---|---|---|
| 并联副本共享同一个故障域(同一可用区、同一份配置同时发布) | 「三副本」一起宕机 | 跨可用区部署;分批发布;配置变更先发一个副本 |
| 故障切换从没演练过 | 真出事时切换脚本报错,停机从 2 分钟变成 2 小时 | 定期演练,把实测切换时间记下来 |
| 健康检查间隔用默认 30 秒 | 实例挂掉后近一分钟内部分请求失败 | 按可用性目标调间隔和阈值 |
| DNS TTL 设成一天 | DNS 已切到备用区域,大量用户仍连旧地址 | 需要 DNS 切换的记录用短 TTL |
| 没有 fencing | 脑裂,两个主库同时写,数据分叉 | 多数派 / 租约 + fencing |
| active-active 平时利用率 90% | 丢一个可用区,剩下的被压垮 | 按丢失一个故障域后的容量规划 |
| 深检查依赖数据库 | 数据库一抖,负载均衡把所有实例判死 | 健康检查只查自身,依赖故障交给熔断 |
Availability vs Reliability
系统可靠(reliable),通常也可用;但可用不代表可靠。高 reliability 有助于高 availability,也可以通过冗余和快速切换,让一组不那么可靠的组件整体上保持高 availability,上面的并联公式就是例子。
High availability vs Fault Tolerance
两者都为了提高 uptime,方式不同。Fault-tolerant 系统在组件故障时没有服务中断,通常需要完全冗余的硬件或同步运行的副本,成本很高;high availability 系统接受故障切换期间的短暂中断,目标是把中断缩到秒级或分钟级。本章讨论的健康检查、自动切换都属于后者。
面试时这样回答
- 先把目标换算成时间:「99.95% 每 30 天是 21.6 分钟。」
- 画出请求路径,逐项串联,指出最弱的一环;对要加冗余的一环用并联公式,并主动说出独立性前提。
- 说清每一环的切换方式和秒数:无状态层 active-active,健康检查间隔 × 阈值就是检测时间;数据库 active-passive,托管服务一次切换通常一两分钟。
- 说脑裂怎么防:多数派 / 租约决定谁是主,fencing 让旧主不能再写。
- 说一个代价和一个故障:active-active 要预留丢一个可用区后的容量;深健康检查会在共享依赖故障时把所有实例一起判死。
相关章节:SLA / SLO / SLI、Disaster Recovery、Database Replication、Load Balancer、Clustering、多区域架构。
一手证据
- Google SRE Book:Embracing Risk
- Elastic Load Balancing:Health checks for Application Load Balancer target groups
- Amazon Route 53:Values that you specify when you create or update health checks
- Amazon Route 53:How Route 53 determines whether a health check is healthy
- Amazon RDS:Failing over a Multi-AZ DB instance
- Patroni:Dynamic configuration settings
- Patroni:DCS Failsafe Mode