Skip to content

第 9 讲|高可用架构

服务运行过程中,机器宕机、进程退出、磁盘写满、网络抖动、数据库不可写等问题随时可能发生。高可用(High Availability,HA)要解决的核心问题是:在这些故障真实发生时,服务对外仍能保持可用

单机系统的根本风险是单点(Single Point of Failure,SPOF)——任何一个关键组件只有一份,坏了就全停。高可用架构的两个核心动作:消除单点 + 把切换做对

本讲围绕高可用的核心概念展开:单点的识别、主备模式、故障发现机制、集群形态的差异、数据一致性问题、恢复确认与演练。

一、单点的识别

任何"只剩一个"的关键对象都是单点。常见单点及其故障表现:

高可用首先要识别单点而不只是加副本

单点故障后的现象
单台 Web 服务器页面和接口全部不可访问
单个负载均衡器后端正常,但外部入口不可用
单个数据库主库写入失败,所有写接口报错
单个 Redis缓存、会话、限流、分布式锁全部异常
单条公网线路外部访问中断,或部分地区访问异常
单个 DNS 配置域名解析失败,全站不可达
单个机房整个机房故障(电力、网络、空调)→ 全部服务停摆
单条网络链路上游中断 → 某个网段无法访问
单台堡垒机运维无法登录所有机器

单点不只是硬件

单点的概念远不止"一台机器"——软件层、组织层、流程层都可能存在单点:

  • 配置单点:某个关键配置只在一台机器上,丢了无法恢复
  • 密钥单点:加密私钥、证书、SSH 密钥只在一处保存
  • 知识单点:只有一个人懂某个系统,他离职/休假就没人能处理故障
  • 流程单点:某个变更必须通过某一个人审批,他不在就卡住

高可用工作不只是"加机器",还包括配置备份、知识沉淀、流程冗余

多副本不等于高可用

减少单点不是简单加一台机器摆在那儿。多出来的实例必须满足:

  • 被入口发现并接入流量 — 否则只是摆设
  • 坏掉时能被自动摘除 — 否则用户请求会落到坏的实例
  • 角色切换后客户端能找到新主 — 否则切换无效

任何一项缺失,备用机器在真出事时不会自动接管,业务仍然会停。高可用是一整套机制,不只是多副本

二、主备模式

主备(Active-Standby)是最容易理解的高可用方案:一台主节点对外服务,另一台备节点同步数据,主节点故障时备节点接管

主备模式最难的是切换而不是多装一台

主备模式在数据库、负载均衡、网关、存储系统中都很常见。

主备的几种工作模式

模式备节点的状态切换时间
冷备(Cold Standby)平时关机,故障时启动慢(数分钟)
暖备(Warm Standby)平时运行但不接流量,数据同步中(秒到分钟)
热备(Hot Standby)实时同步,可立即接管快(秒级)
Active-Active(双活)两边都对外服务不需切换

热备和双活的可用性更高,但实现复杂度也更高。多数业务系统采用热备:备节点持续同步数据,具备随时接管的能力。

切换:主备最难的部分

主备模式真正的难点是切换那一瞬间。切换有两类典型问题:

问题 1:切得太慢

故障发生到切换完成的时间越长,业务受影响时间越长。典型的切换流程:

  1. 探测主节点不可用(健康检查失败超过阈值)
  2. 提升备节点为主
  3. 更新入口指向(VIP 漂移、DNS 切换、客户端重连)
  4. 验证写入恢复

每一步都需要时间,合起来可能从几秒到几分钟。**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):

集群大小多数派可容忍故障
321 个
532 个
743 个

偶数(如 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 就是把这一原则做到极致:在生产环境随机杀实例,强制系统具备真正的容错能力