跳转到正文
zeno's blog
返回

分布式基础(三):高可用-通过冗余和自动故障转移消除单点故障

专题: 分布式基础

Table of contents

Open Table of contents

TL;DR

高可用 = 冗余(让故障不致命)+ 自动故障转移(让恢复足够快)。核心公式 Availability = MTBF / (MTBF + MTTR),降低 MTTR 的 ROI 远高于提高 MTBF,因为故障不可避免。每多一个 9,成本和复杂度指数级上升——大多数业务在 99.95%-99.99% 找到平衡点。


SLI → SLO → SLA 体系

三个概念层层递进,不要混用:

”几个 9” 的真实含义

可用性年停机月停机核心要求
99%(2 个 9)3.65 天7.3 小时基本不可接受
99.9%(3 个 9)8.76 小时43.8 分钟多实例 + 手动故障转移
99.95%4.38 小时21.9 分钟自动故障转移 + 跨 AZ
99.99%(4 个 9)52.6 分钟4.38 分钟零停机部署 + 混沌工程 + 7×24 on-call
99.999%(5 个 9)5.26 分钟25.9 秒多 Region 多活 + 秒级自动恢复

从 99.9% 到 99.99% 不是提升 0.09%,而是停机时间缩短 10 倍。成本和复杂度从 3-5x 跳到 5-10x。

核心机制

冗余

冗余的反直觉陷阱:增加组件数量增加单个组件故障概率,但降低全部同时故障的概率。前提是故障必须独立——所有副本放同一机架,断电全完(correlated failure)。

故障检测

Health Check 两种类型必须区分

混淆是常见事故原因——数据库连接池满了应该摘流量(readiness),不应该重启 Pod(liveness),重启反而制造连接风暴。

Phi Accrual Failure Detector(Cassandra 使用):不是二元的活/死判断,而是基于历史心跳间隔的统计分布输出”怀疑程度”,比固定超时更适应网络抖动。

自动故障转移

故障转移时间的构成:

总时间 = 检测时间(5-30s) + 确认时间(1-5s) + 切换时间(1-10s) + 客户端发现时间(0-60s)

客户端发现时间常被忽视——DNS TTL 可能 60s,连接池缓存旧连接。这就是为什么”秒级故障转移”实际恢复时间可能是分钟级。

无状态 vs 有状态的 HA 差异

这是高可用设计中最根本的分水岭:

无状态服务有状态服务
HA 难度简单:多实例 + LB + 健康检查极复杂:数据复制、切换时数据安全、脑裂
故障影响任意实例挂,用户无感知需要共识算法或外部协调

核心原则:尽一切可能把服务做成无状态,把状态推到专门的有状态组件(数据库、缓存),然后用成熟方案让这些组件高可用。

各层的高可用方案

应用层

部署策略对可用性的影响

策略可用性影响回滚速度核心挑战
滚动更新过程中容量降低慢(反向滚动)新旧版本并存的兼容性
蓝绿部署零停机秒级DB schema 必须向后兼容
金丝雀部署影响面可控秒级需要流量分配能力

优雅关闭(Graceful Shutdown)的竞态:Kubernetes 中 Pod 被删除时,SIGTERM 和从 Service 摘除是并行的——Pod 可能正在关闭但还在接收新请求。解法:preStop hook 中 sleep 2-5s。

数据层

同步复制 vs 异步复制

维度同步复制异步复制
数据安全RPO=0可能丢失最近写入
写入延迟受最慢副本影响不受副本影响
可用性副本挂了可能写不进副本挂不影响主库

PostgreSQL 的实际做法:synchronous_standby_names = 'FIRST 1 (replica1, replica2)'——至少一个副本确认即可,平衡数据安全和可用性。

网络层

关键设计模式

Circuit Breaker(熔断器)

防止下游故障导致上游资源耗尽,形成级联故障:

CLOSED (正常通行) ──失败率>阈值──▶ OPEN (快速失败)
    ▲                                    │
    │ 试探成功                    超时后试探
    └─────── HALF-OPEN (放行少量) ◀──────┘

Retry with Backoff + Jitter

等待时间 = min(base_delay × 2^attempt + random_jitter, max_delay)

哪些可以重试:网络超时、503(临时性故障)。不能重试:400/401/404(业务错误)。危险:非幂等操作(除非有 idempotency key)。

超时分层递减

Client 10s → Gateway 8s → Service A 5s → Service B 3s

下游超时必须严格小于上游,否则上游线程全部阻塞在等待下游。

高可用与 CAP

CAP 不是”三选二”——网络分区不可选,实际选择只有分区发生时选 C 还是选 A。没有分区时 C 和 A 可以同时满足。

Error Budget 模型(Google SRE):SLO 99.95% = 每月约 22 分钟的 error budget。Budget 充足可以激进发版,快用完就冻结变更。优雅地解决了”发布速度 vs 稳定性”的矛盾。

Pitfalls

  1. 加了冗余但没测故障转移 — 数据库配了主从但从未做切换演练,真故障时发现从库复制延迟了 3 小时。对策:至少季度级故障演练
  2. 主从切换后应用不重连 — 连接池缓存旧主库连接。对策:用连接代理(PgBouncer),或设 target_session_attrs=read-write
  3. Health check 检查的不是真正的健康 — 只返回 200 但数据库已断连。对策:检查关键依赖,但依赖检查放 readiness 不放 liveness
  4. 脑裂 — 两个节点都认为自己是主。对策:Patroni + etcd 做共识,STONITH 强制关旧主
  5. 隐性共同依赖 — 消除了明显 SPOF 但所有服务共用一个 DNS 解析器、一个配置中心。对策:画完整依赖拓扑图
  6. 灰色故障 — 系统没完全挂但丢包 1%、IO 偶尔飙高。Health check 返回 200 但用户体验已经很差。对策:基于 SLI 的告警比硬件指标告警更有效

生产 Checklist

部署

故障检测与恢复

容错模式

监控与演练


分享这篇文章:

上一篇
分布式基础(四):容灾-容错与灾难恢复
下一篇
tower(四):tower-http-HTTP 专用中间件