03 | 高可用架构 —— 冗余设计、故障转移、限流熔断、CAP/BASE 理论
系统不是永远不出问题,而是出了问题还能继续服务。
开篇自测
- CAP 定理中的 C、A、P 分别代表什么?为什么三者不可兼得?
- 限流和熔断的区别是什么?它们分别解决什么问题?
- 你能说出至少三种实现故障转移的方式吗?
一、高可用的本质
1.1 什么是高可用
高可用(High Availability,简称 HA)是指系统在面对各种故障时,仍然能够持续提供服务的能力。衡量高可用的核心指标是可用性百分比:
可用性 = 正常运行时间 / (正常运行时间 + 故障时间) x 100%
不同级别的可用性对应的年停机时间:
| 可用性级别 | 百分比 | 年停机时间 | 典型应用 |
|---|---|---|---|
| 2 个 9 | 99% | 3.65 天 | 内部管理系统 |
| 3 个 9 | 99.9% | 8.76 小时 | 普通业务系统 |
| 4 个 9 | 99.99% | 52.6 分钟 | 核心交易系统 |
| 5 个 9 | 99.999% | 5.26 分钟 | 电信级系统 |
1.2 故障是常态而非意外
在分布式系统中,故障不是”会不会发生”的问题,而是”什么时候发生”的问题。
硬件故障频率参考(行业参考数据):
- 服务器年故障率:2-4%
- 硬盘年故障率:2-8%
- 交换机年故障率:约 5%
如果你有 1000 台服务器,按 3% 的年故障率计算,平均每 12 天就有一台服务器出故障。
高可用设计的核心理念:假设任何组件都会失败,并为每种失败准备应对方案。
1.3 高可用设计的三大策略
+-----------------------------------------------------------+
| 高可用设计三大策略 |
+-----------------------------------------------------------+
| |
| 1. 冗余 (Redundancy) |
| - 多实例部署,消除单点 |
| - 数据多副本存储 |
| |
| 2. 自动化 (Automation) |
| - 自动故障检测 |
| - 自动故障转移 |
| - 自动恢复 |
| |
| 3. 隔离 (Isolation) |
| - 故障范围控制 |
| - 避免级联失败 |
| - 限流、熔断、降级 |
| |
+-----------------------------------------------------------+
二、冗余设计与故障转移
2.1 应用层冗余
应用服务器天然具备水平扩展能力(无状态),通过负载均衡器实现冗余和流量分发:
客户端请求
|
+-------------+
| 负载均衡器 |
| (LB) |
+-------------+
/ | | \
+----+ +----+ +----+ +----+
|App1| |App2| |App3| |App4|
+----+ +----+ +----+ +----+
当 App2 健康检查失败时:
+-------------+
| LB |
+-------------+
/ | \
+----+ +----+ +----+
|App1| |App3| |App4| App2 自动摘除
+----+ +----+ +----+
健康检查机制:
- TCP 检查:验证端口是否可连接(最基本)
- HTTP 检查:发送 HTTP 请求,验证返回状态码(推荐)
- 自定义检查:调用特定接口,验证业务逻辑是否正常(最严格)
2.2 数据层冗余
数据层冗余比应用层更复杂,因为涉及数据一致性问题。
MySQL 主从复制:
写入操作
|
+----------+
| Master |
| (主库) |
+----------+
| |
Binlog同步 Binlog同步
| |
+-------+ +-------+
|Slave 1| |Slave 2|
|(从库1)| |(从库2)|
+-------+ +-------+
故障转移(Failover):
主库宕机 --> Slave 1 提升为新主库 --> Slave 2 指向新主库
Redis 哨兵模式:
+----------+ +----------+ +----------+
|Sentinel 1| |Sentinel 2| |Sentinel 3| 哨兵集群
+----------+ +----------+ +----------+ (监控+选举)
| | |
+------+-------+------+------+
| |
+---------+ +---------+
| Redis | | Redis |
| Master | | Slave |
+---------+ +---------+
故障转移流程:
1. 哨兵定期对主节点做 PING 检测
2. 当多数哨兵判定主节点不可达(客观下线)
3. 哨兵之间选举一个 Leader
4. Leader 哨兵选择一个 Slave 提升为新 Master
5. 通知其他 Slave 指向新 Master
6. 通知客户端切换连接
2.3 异地多活
对于可用性要求极高的系统,单机房冗余还不够,需要跨机房甚至跨地域的冗余。
用户请求
|
+-------------+
| GSLB/DNS | 全局负载均衡
+-------------+
/ \
+----------------+ +----------------+
| 北京机房 | | 上海机房 |
| | | |
| +----+ +----+ | | +----+ +----+ |
| |App | |App | | | |App | |App | |
| +----+ +----+ | | +----+ +----+ |
| | | |
| +------+ | | +------+ |
| |Master| <-----|---->|Master| | 双向同步
| +------+ | | +------+ |
+----------------+ +----------------+
异地多活的核心挑战:
- 数据同步延迟(跨城 30ms+)
- 数据冲突处理
- 路由策略(就近访问)
- 故障切换时的数据一致性保障
异地多活的三种模式:
| 模式 | 描述 | 复杂度 | 适用场景 |
|---|---|---|---|
| 冷备 | 备用机房平时不接流量 | 低 | 灾难恢复 |
| 热备 | 备用机房承载部分读流量 | 中 | 读多写少的业务 |
| 双活 | 两个机房同时承载读写 | 高 | 核心业务系统 |
三、限流
3.1 为什么需要限流
限流的目的是保护系统不被超出其处理能力的流量压垮。就像高速公路入口的红绿灯,在车流高峰时控制进入量,防止道路拥堵。
3.2 常见限流算法
固定窗口计数器:
时间窗口: 1秒 限制: 100次
|<--- 窗口1 --->|<--- 窗口2 --->|
0s 1s 2s
[ 80 次请求 ] [ 60 次请求 ] 正常
问题:窗口边界突发
0.9s 1.0s
[ 90次 ] + [ 90次 ]
实际 1 秒内有 180 次请求,超过限制
滑动窗口计数器:
将窗口细分为多个小格,滑动计算总量
|--格1--|--格2--|--格3--|--格4--|--格5--|
20次 30次 25次 15次 10次
^ ^
|<----- 1秒滑动窗口 ------->|
当前窗口总计: 80次 < 100次限制,放行
漏桶算法(Leaky Bucket):
请求以不规则速率流入,以固定速率流出
请求流入(突发性)
|
+----v----+
| |
| 漏 桶 | 桶满则拒绝新请求
| |
+----+----+
|
固定速率流出(匀速处理)
特点:平滑流量,消除突发
缺点:无法应对合理的突发请求
令牌桶算法(Token Bucket):
以固定速率往桶中放入令牌,请求需要获取令牌才能通过
令牌以固定速率生成
|
+----v----+
| 令牌 | 桶满时,新令牌被丢弃
| 令牌 |
| 令牌 | 桶容量 = 允许的最大突发量
+----+----+
|
请求获取令牌 --> 成功: 放行
--> 失败(桶空): 拒绝/等待
特点:允许一定程度的突发请求
适用:大多数限流场景的首选
3.3 分布式限流
单机限流只能控制单个实例的请求量,分布式环境需要全局限流:
方案:基于 Redis 的分布式限流
App1 --+
| +-------+
App2 --+--> | Redis | 所有实例共享计数器
| | INCR |
App3 --+ +-------+
实现(令牌桶 + Redis):
1. 使用 Redis 存储令牌数量和最后填充时间
2. 每次请求通过 Lua 脚本原子化地计算并获取令牌
3. 令牌不足则拒绝请求
四、熔断与降级
4.1 熔断器模式
熔断器借鉴了电路保险丝的概念:当下游服务出现大量失败时,主动断开对它的调用,避免请求堆积导致级联故障。
熔断器的三种状态:
+--------+ 失败率超过阈值 +---------+
| 关闭 | ------------------> | 打开 |
| CLOSED | | OPEN |
+--------+ +---------+
^ |
| 超时后进入半开
| |
| +----------+ |
+------| 半开 | <----------+
成功率恢复 | HALF-OPEN|
+----------+
试探性放行少量请求
各状态行为:
CLOSED: 正常放行所有请求,同时统计失败率
OPEN: 直接拒绝所有请求,返回兜底数据
HALF-OPEN: 放行少量请求试探,根据结果决定恢复或继续熔断
4.2 降级策略
当系统负载过高或某些依赖服务不可用时,主动关闭部分非核心功能:
降级等级设计示例(电商系统):
等级 0(正常):所有功能正常
- 商品详情 + 推荐 + 评论 + 优惠券 + 搭配
等级 1(轻度降级):关闭非核心功能
- 商品详情 + 推荐 + 评论
- [关闭] 优惠券、搭配
等级 2(中度降级):只保留核心功能
- 商品详情
- [关闭] 推荐、评论、优惠券、搭配
等级 3(重度降级):返回兜底数据
- 静态缓存页面
- [关闭] 所有动态内容
4.3 限流、熔断、降级的对比
| 维度 | 限流 | 熔断 | 降级 |
|---|---|---|---|
| 触发条件 | 请求量超过阈值 | 依赖服务失败率过高 | 系统负载过高/手动触发 |
| 作用对象 | 自身入口流量 | 下游服务调用 | 自身非核心功能 |
| 行为 | 拒绝超量请求 | 快速失败,不再调用 | 关闭部分功能 |
| 目的 | 保护自身 | 防止级联故障 | 保障核心功能 |
五、CAP 与 BASE 理论
5.1 CAP 定理
CAP 定理由 Eric Brewer 于 1999 年首次发表,2000 年在 PODC 大会 keynote 中作为猜想正式提出(2002 年由 Gilbert 和 Lynch 给出形式化证明),指出分布式系统不可能同时满足以下三个特性:
- C(Consistency)一致性:所有节点在同一时间看到的数据相同
- A(Availability)可用性:每个请求都能在合理时间内得到响应
- P(Partition Tolerance)分区容忍性:网络分区发生时系统仍能工作
C (一致性)
/ \
/ \
/ \
/ CAP \
/ 定理 \
/ \
A ----------- P
(可用性) (分区容忍)
在分布式系统中,P(分区容忍)是必须的——网络分区一定会发生。
因此实际的选择是在 C 和 A 之间做取舍:
CP 系统:保证一致性,牺牲可用性
例:ZooKeeper、HBase、MongoDB(强一致配置)
AP 系统:保证可用性,牺牲一致性(允许最终一致)
例:Cassandra、DynamoDB、Eureka
5.2 CAP 的常见误解
误解 1:“CAP 三选二”——实际上 P 是前提,真正的选择是 CP 还是 AP。
误解 2:“一个系统只能是 CP 或 AP”——实际上同一个系统的不同模块可以做不同的选择。例如电商系统中,库存扣减选择 CP(数据必须准确),商品展示选择 AP(允许短暂不一致)。
误解 3:“选择 AP 就是不要一致性”——实际上 AP 系统通常通过最终一致性来保证数据最终会收敛到一致状态。
5.3 BASE 理论
BASE 是 CAP 中 AP 方向的具体实践指导:
- BA(Basically Available)基本可用:系统出现故障时,允许部分功能降级,但核心功能正常
- S(Soft State)软状态:允许系统中的数据存在中间状态,即数据在不同节点之间存在短暂不一致
- E(Eventually Consistent)最终一致性:经过一段时间后,所有节点的数据最终会达到一致
ACID vs BASE 对比:
ACID(传统数据库) BASE(分布式系统)
+-----------+ +-----------+
| 强一致性 | | 最终一致性 |
| 事务隔离 | | 软状态 |
| 持久性 | | 基本可用 |
+-----------+ +-----------+
适用场景: 适用场景:
金融交易 社交动态
库存扣减 日志系统
账户余额 搜索索引
5.4 最终一致性的实现模式
| 模式 | 描述 | 一致性延迟 | 适用场景 |
|---|---|---|---|
| 读时修复 | 读到不一致数据时主动修复 | 取决于读取频率 | Dynamo 风格系统 |
| 写时修复 | 写入时通过反熵过程同步 | 后台周期 | 分布式数据库 |
| 异步复制 | 写入主节点后异步同步到从节点 | 毫秒~秒级 | MySQL 主从 |
| 消息补偿 | 通过消息队列保证操作最终执行 | 秒~分钟级 | 跨服务数据同步 |
六、故障处理实战:FMEA 分析法
6.1 什么是 FMEA
FMEA(Failure Mode and Effects Analysis,故障模式与影响分析)是一种系统化的故障分析方法,用于识别系统中可能的故障点以及每种故障的影响和应对方案。
6.2 FMEA 分析模板
| 组件 | 故障模式 | 故障原因 | 影响范围 | 严重等级 | 检测方式 | 应对方案 |
|---|---|---|---|---|---|---|
| 负载均衡 | 单点宕机 | 硬件故障 | 全站不可用 | 致命 | 心跳检测 | 双机热备+VIP漂移 |
| Redis 主节点 | 宕机 | 内存溢出 | 缓存全部失效 | 严重 | 哨兵监控 | 自动故障转移 |
| MySQL 主库 | 磁盘满 | 数据增长 | 无法写入 | 严重 | 磁盘监控 | 自动扩容+告警 |
| 应用服务器 | OOM | 内存泄漏 | 单实例不可用 | 一般 | JVM 监控 | 自动重启+多实例 |
| 消息队列 | 消息堆积 | 消费者故障 | 业务延迟 | 一般 | 队列深度监控 | 自动扩容消费者 |
6.3 构建高可用的检查清单
[ ] 单点是否已消除?
- 应用层多实例?
- 数据层主从?
- 负载均衡主备?
[ ] 故障检测是否完善?
- 健康检查配置?
- 监控告警覆盖?
- 日志收集完整?
[ ] 故障转移是否自动?
- 数据库自动切主?
- 缓存自动故障转移?
- DNS 自动切换?
[ ] 降级方案是否就绪?
- 核心功能能否独立运行?
- 是否有兜底数据/页面?
- 降级开关是否可远程控制?
[ ] 是否定期演练?
- 混沌工程(模拟故障)?
- 灾难恢复演练?
- 应急预案更新?
思考题
-
一个支付系统应该选择 CP 还是 AP?在网络分区发生时,是让用户看到”系统繁忙请稍后再试”更好,还是允许重复支付更好?
-
Netflix 提出了”混沌工程”的理念,在生产环境中随机杀死服务实例来测试系统的高可用能力。你认为这种做法适合所有公司吗?什么阶段的公司适合引入混沌工程?
结尾自测
-
高可用设计的三大策略是什么?
- 答:冗余(消除单点)、自动化(自动检测与转移)、隔离(限流熔断降级,控制故障范围)。
-
令牌桶算法相比漏桶算法的优势是什么?
- 答:令牌桶允许一定程度的突发流量(桶中积累的令牌可以一次性消耗),而漏桶只能以固定速率处理请求,无法应对合理的突发需求。
-
CAP 定理在工程实践中,真正需要做的选择是什么?
- 答:在分区容忍(P)为前提下,选择一致性优先(CP)还是可用性优先(AP)。同一系统的不同模块可以做不同选择。
-
BASE 理论中的”软状态”是什么意思?
- 答:允许系统中的数据存在中间状态,即不同节点之间的数据可以短暂不一致,只要最终能达到一致即可。
-
熔断器的三种状态分别是什么?从 OPEN 状态如何恢复到 CLOSED 状态?
- 答:CLOSED(正常放行)、OPEN(全部拒绝)、HALF-OPEN(试探放行)。从 OPEN 超时后进入 HALF-OPEN,放行少量试探请求,若成功率恢复正常则转为 CLOSED,否则回到 OPEN。
下一章预告:高可用保证系统稳定运行,但业务还在不断增长。下一章我们将进入可扩展架构的世界,聊聊分层架构、微服务、SOA 以及中台思想。
购买课程解锁全部内容
面试晋升必学:11 章掌握系统设计
¥29.90