Appearance
第 9 讲|高可用架构
服务运行过程中,机器宕机、进程退出、磁盘写满、网络抖动、数据库不可写等问题随时可能发生。高可用(High Availability,HA)要解决的核心问题是:在这些故障真实发生时,服务对外仍能保持可用。
单机系统的根本风险是单点(Single Point of Failure,SPOF)——任何一个关键组件只有一份,坏了就全停。高可用架构的两个核心动作:消除单点 + 把切换做对。
本讲围绕高可用的核心概念展开:单点的识别、主备模式、故障发现机制、集群形态的差异、数据一致性问题、恢复确认与演练。
一、单点的识别
任何"只剩一个"的关键对象都是单点。常见单点及其故障表现:

| 单点 | 故障后的现象 |
|---|---|
| 单台 Web 服务器 | 页面和接口全部不可访问 |
| 单个负载均衡器 | 后端正常,但外部入口不可用 |
| 单个数据库主库 | 写入失败,所有写接口报错 |
| 单个 Redis | 缓存、会话、限流、分布式锁全部异常 |
| 单条公网线路 | 外部访问中断,或部分地区访问异常 |
| 单个 DNS 配置 | 域名解析失败,全站不可达 |
| 单个机房 | 整个机房故障(电力、网络、空调)→ 全部服务停摆 |
| 单条网络链路 | 上游中断 → 某个网段无法访问 |
| 单台堡垒机 | 运维无法登录所有机器 |
单点不只是硬件
单点的概念远不止"一台机器"——软件层、组织层、流程层都可能存在单点:
- 配置单点:某个关键配置只在一台机器上,丢了无法恢复
- 密钥单点:加密私钥、证书、SSH 密钥只在一处保存
- 知识单点:只有一个人懂某个系统,他离职/休假就没人能处理故障
- 流程单点:某个变更必须通过某一个人审批,他不在就卡住
高可用工作不只是"加机器",还包括配置备份、知识沉淀、流程冗余。
多副本不等于高可用
减少单点不是简单加一台机器摆在那儿。多出来的实例必须满足:
- 被入口发现并接入流量 — 否则只是摆设
- 坏掉时能被自动摘除 — 否则用户请求会落到坏的实例
- 角色切换后客户端能找到新主 — 否则切换无效
任何一项缺失,备用机器在真出事时不会自动接管,业务仍然会停。高可用是一整套机制,不只是多副本。
二、主备模式
主备(Active-Standby)是最容易理解的高可用方案:一台主节点对外服务,另一台备节点同步数据,主节点故障时备节点接管。

主备模式在数据库、负载均衡、网关、存储系统中都很常见。
主备的几种工作模式
| 模式 | 备节点的状态 | 切换时间 |
|---|---|---|
| 冷备(Cold Standby) | 平时关机,故障时启动 | 慢(数分钟) |
| 暖备(Warm Standby) | 平时运行但不接流量,数据同步 | 中(秒到分钟) |
| 热备(Hot Standby) | 实时同步,可立即接管 | 快(秒级) |
| Active-Active(双活) | 两边都对外服务 | 不需切换 |
热备和双活的可用性更高,但实现复杂度也更高。多数业务系统采用热备:备节点持续同步数据,具备随时接管的能力。
切换:主备最难的部分
主备模式真正的难点是切换那一瞬间。切换有两类典型问题:
问题 1:切得太慢
故障发生到切换完成的时间越长,业务受影响时间越长。典型的切换流程:
- 探测主节点不可用(健康检查失败超过阈值)
- 提升备节点为主
- 更新入口指向(VIP 漂移、DNS 切换、客户端重连)
- 验证写入恢复
每一步都需要时间,合起来可能从几秒到几分钟。**RTO(Recovery Time Objective)**就是衡量"故障到恢复"耗时的指标。
问题 2:脑裂(Split Brain)
如果两个节点同时认为自己是主,各自接受写请求,数据分别写到两边,后续合并几乎不可能,可能丢数据。
脑裂的典型成因:
- 主节点其实没死,只是网络分区(主节点与监控之间网络断了,但主节点本身还在服务客户端)
- 备节点没收到主节点心跳就抢了主
- 同时存在两个"主",数据双写
Keepalived + VIP:经典的主备入口方案
Keepalived 是 Linux 上常用的主备实现工具:
- VIP(Virtual IP):一个虚拟 IP 地址,平时挂在主节点上
- 主备节点之间通过 VRRP 协议发送心跳
- 主节点心跳停止后,备节点接管 VIP
- 客户端始终访问同一个 VIP,后端切换对客户端透明
数据库主备切换的额外复杂度
数据库主备切换比 VIP 漂移复杂得多,需要确认:
| 检查项 | 含义 |
|---|---|
| 复制进度 | 主库最新的数据是否已经同步到备库 |
| 备库状态 | 备库是否处于可写模式 |
| 旧主隔离 | 旧主必须切换为只读或下线,避免接收新写入 |
| 应用连接刷新 | 应用的连接池可能还连着旧主,需要触发重连 |
漏掉任何一项都可能造成数据不一致——例如旧主没正确隔离,应用继续往旧主写入,新主上没有这些数据。
三、故障发现机制
高可用系统必须先发现故障,才能做切换。故障发现的常见机制:
| 机制 | 关注内容 |
|---|---|
| 心跳(Heartbeat) | 节点之间定期发包,确认对方还活着 |
| 健康检查 | 入口层定期探测后端,判断是否能处理请求 |
| Leader 选举 | 多个节点投票选出一个主节点 |
| 仲裁(Quorum) | 多数派同意才能做决定,避免脑裂 |
| 外部协调 | etcd / ZooKeeper 提供分布式锁、租约 |
| 监控告警 | 从错误率、延迟、资源使用率发现异常 |
心跳的关键陷阱:网络分区
心跳失败不等于对方真的死了——可能只是中间网络断了。这种情况下:

- 主节点本身正常,与客户端的连接也正常
- 但主备之间的心跳网络断了
- 备节点收不到心跳,以为主节点死了
- 备节点抢主,与原主形成"双主"
- 客户端有的请求落到旧主,有的落到新主,数据分散
这就是网络分区导致的脑裂。分布式系统的核心难题之一就是处理这种"无法区分网络故障与节点故障"的场景。
避免脑裂的几种机制
| 机制 | 工作方式 |
|---|---|
| 仲裁节点 | 引入第三方节点投票,主备 + 仲裁三方协商 |
| Quorum(法定人数) | 需要多数派(N/2 + 1)同意才能切换 |
| 租约(Lease) | 主节点必须定期向仲裁服务续约,续约失败自动失效 |
| Fencing(隔离) | 切换前主动断电/隔离旧主,确保它无法再写 |
| 外部锁 | 通过 etcd/ZooKeeper 持有的锁决定谁是主 |
Quorum 为什么是奇数
Raft、Paxos 这类共识算法的 Quorum 通常要求"多数派",所以集群节点数推荐奇数(3、5、7):
| 集群大小 | 多数派 | 可容忍故障 |
|---|---|---|
| 3 | 2 | 1 个 |
| 5 | 3 | 2 个 |
| 7 | 4 | 3 个 |
偶数(如 4)的情况下:多数派是 3,可容忍故障 1 个——与 3 节点效果一样,但多了一台机器的成本。所以 etcd、ZooKeeper、Consul 等都推荐部署 3/5/7 节点。
切换后的验证
切换完成后,不能仅看入口能否访问就认为恢复了——必须做实际的写入验证:
bash
# 1. 入口能访问
curl https://api.example.com/healthz
# 2. 关键路径能写入
curl -X POST https://api.example.com/api/test_write
# 3. 错误率回落到正常
# 看监控面板
# 4. 旧主已隔离,不再接收写入
# 检查旧主的连接数、SQL 日志,确认无新连接仅看入口可达就宣布"已恢复"是常见误判——可能新主还在初始化、复制还没追上、连接池还连旧主,真实业务还没恢复。
四、集群形态
"集群"在不同组件里含义差异巨大,不能用统一思路理解。
| 类型 | 工作方式 |
|---|---|
| Web/应用集群 | 多个无状态实例,前面负载均衡分发请求 |
| Redis Cluster | 数据按 slot 分散到不同节点,每节点负责一部分 key |
| Kafka 集群 | Topic 的 partition 分布到多个 broker,消费者按 group 分担 |
| 数据库集群 | 主从、组复制(MGR)、分片、多主——模型多种多样 |
| Elasticsearch | 数据按 shard 分片,每个 shard 有副本 |
| Kubernetes 集群 | 控制面调度,工作节点承载 Pod |
| etcd / ZooKeeper | 一致性集群,强同步 |
无状态服务做集群:最简单
无状态服务的集群最容易做:
- 任何实例处理任何请求都一样(状态都在外部数据库/缓存)
- 加一个实例 = 多一个工作者
- 减一个实例 = 少一个工作者,流量自动转移到其他实例
- 实例之间完全等价,挂一个不影响其他
无状态服务的横向扩展可以无限继续——直到数据库等其他层成为瓶颈。
有状态服务做集群:难得多
有状态服务的集群需要回答一系列问题:
- 数据放在哪个节点?(分片策略)
- 谁能写?(主从模型 vs 多主模型)
- 副本同步到什么程度算"提交"?(同步/半同步/异步)
- 节点坏了由谁接管?(故障转移机制)
- 节点新加入时怎么补数据?(数据再平衡)
- 客户端怎么知道连哪个节点?(路由层)
不同有状态服务的集群模型完全不同:
- Redis Cluster:数据按 slot 分散,每节点一部分。客户端通过 MOVED 重定向找到正确节点
- Kafka:Topic 分 partition,每 partition 有 leader 和 follower。Producer 按 partition 路由
- MySQL:主从、MGR(组复制)、分片(Vitess)各有取舍
- etcd:Raft 一致性,所有节点都有完整数据,写操作必须多数派确认
有状态服务的运维比无状态服务难得多——不能简单类比。MySQL 集群的故障处理思路不能套用到 Kafka 集群,反之亦然。每种有状态系统都需要专门学习其内部机制。
五、数据一致性
应用层的高可用相对简单(无状态),数据层的高可用要面对一致性问题。

一致性问题的常见表现
| 场景 | 用户侧表现 |
|---|---|
| 主从复制延迟 | 刚提交的数据,马上查列表查不到 |
| 主库故障切换 | 故障前几秒的数据丢失(还没同步就切了) |
| 多主写入冲突 | 两个地方同时改同一记录,结果不可预测 |
| 网络分区 | 部分客户端访问旧主,部分访问新主 |
| 缓存与数据库不一致 | 数据库改了,但缓存还是旧值 |
"写后立即读"的经典坑
读写分离场景下最常见的体验问题:
用户修改头像:写到主库
0.1 秒后用户刷新个人页:读请求路由到从库(刚好选中)
从库还没同步过来,读到旧头像
用户:"修改没生效?"处理方式:
| 方案 | 说明 |
|---|---|
| 强制读主 | 写操作后短时间内的同用户读请求,强制打主库 |
| 应用层缓存 | 写成功后,在应用内存中保留最新值短时间 |
| Session 一致性 | 同一用户的请求保证看到自己最近写的(read-your-writes) |
| 业务侧避免 | UI 设计上让用户不立即看到列表(写成功后跳转到详情页) |
一致性模型与可用性的取舍
CAP 定理指出:分布式系统在网络分区时,一致性(Consistency)和可用性(Availability)二选一。
| 选择 | 含义 | 典型系统 |
|---|---|---|
| CP | 优先保证一致性,网络分区时拒绝服务 | etcd、ZooKeeper、HBase |
| AP | 优先保证可用性,接受暂时的不一致 | Cassandra、DynamoDB |
业务系统大多采用 AP + 最终一致性的折中——可用性优先,允许短暂不一致,但保证最终所有副本会同步到一致状态。强一致性场景(金融转账)用 CP 系统或者通过事务保证。
多主架构的复杂度
多主(每个数据中心都能写)能提供最高可用性,但写冲突的处理极其复杂:
- 同一记录在两地同时被修改,以谁为准?
- "最后写入获胜"会丢失部分修改
- CRDT(无冲突复制数据类型)只能解决特定数据结构
- 业务层定义合并规则,成本高且容易出错
绝大多数业务系统选择单主 + 多从而非多主——单主架构的写冲突天然不存在,工程复杂度低得多。多主架构主要用于全球级业务(跨大洲数据中心)、对可用性要求极高的特殊场景。
六、高可用的成本观与分级投入
不同系统的高可用投入应当不同——不能所有系统都按最高标准做。
| 系统类型 | 高可用投入 |
|---|---|
| 内部低频工具(运维平台、内部 Wiki) | 备份 + 监控 + 快速重建文档,可以容忍数小时不可用 |
| 一般业务系统(普通 SaaS) | 主备/集群 + 跨机房备份,可用性 99.9% |
| 核心交易系统(支付、电商) | 多机房 + 多副本 + 实时同步 + 灰度发布 + 限流熔断,可用性 99.95%+ |
| 金融核心系统(银行) | 异地双活 + 实时容灾 + 严格事务,可用性 99.99%+ |
把所有系统都按金融核心标准建设,成本承受不住——服务器、网络、人力都会增加数倍。合理做法是按业务重要性分级,核心系统重点投入,边缘系统保留基础能力。
衡量高可用的指标
| 指标 | 含义 |
|---|---|
| 可用性(SLA) | 一段时间内系统可用时间占比(如 99.95%) |
| RTO(Recovery Time Objective) | 故障到恢复的目标时间 |
| RPO(Recovery Point Objective) | 允许丢失的数据时间窗口 |
| MTBF(Mean Time Between Failures) | 平均故障间隔 |
| MTTR(Mean Time To Repair) | 平均修复时间 |
可用性的换算参考:
| SLA | 年不可用时长 | 月不可用时长 |
|---|---|---|
| 99% | 3.65 天 | 7.2 小时 |
| 99.9% | 8.77 小时 | 43.8 分钟 |
| 99.95% | 4.38 小时 | 21.9 分钟 |
| 99.99% | 52.6 分钟 | 4.4 分钟 |
| 99.999% | 5.26 分钟 | 26.3 秒 |
每多一个 9,所需的工程投入呈指数级增长。99.999%(五个 9)通常需要异地多活、专业团队 24 小时值守、自动化运维体系——成本极高,只有金融、电信等关键基础设施才需要。
七、恢复确认与演练
切换后的检查项
故障切换完成后,留下能审计的记录,便于复盘与确认:
| 检查项 | 看什么 |
|---|---|
| 入口 | 域名、VIP、负载均衡是否指向新节点 |
| 应用 | 错误率、延迟、实例重启情况、日志异常 |
| 数据库 | 新主可写、旧主只读或下线、复制链路状态 |
| 用户路径 | 登录、查询、提交、支付、后台任务是否全部恢复 |
| 监控 | 告警是否消除,有无残留错误 |
| 数据完整性 | 切换前后的数据一致性,有无数据丢失 |
关键时间点的记录
切换过程的时间点要精确记录:
14:23:45 监控告警 - 主库连接失败
14:23:50 人工确认主库不可达
14:24:10 触发自动切换
14:24:25 备库提升为新主,VIP 漂移完成
14:24:30 应用第一个写入成功
14:25:00 错误率回落正常
14:30:00 旧主被隔离,确认无新连接这些精确到秒/分钟的时间点,在复盘 RTO/RPO 时远比一句"已恢复"有价值。
高可用演练:验证机制真的有效
未演练过的高可用机制,真出事时大概率不会按预期工作——配置漂移、依赖变化、人员变动都可能让原本可用的机制失效。
定期演练的项目:
| 演练项 | 频率 |
|---|---|
| 主备切换(数据库、Redis) | 每季度 |
| 节点宕机模拟(Chaos Engineering) | 每月 |
| 备份恢复 | 每季度 |
| 跨机房切换 | 每半年 |
| 完整灾备演练 | 每年 |
"演练过的机制"才是真正的高可用机制。备份从没恢复过、切换从没切过、灾备机房从没接管过——这些都不是真实的高可用,只是文档里的"高可用"。Netflix 的 Chaos Monkey 就是把这一原则做到极致:在生产环境随机杀实例,强制系统具备真正的容错能力。