跳到主要内容
预计阅读 27 分钟

03 | 高可用架构 —— 冗余设计、故障转移、限流熔断、CAP/BASE 理论

系统不是永远不出问题,而是出了问题还能继续服务。


开篇自测

  1. CAP 定理中的 C、A、P 分别代表什么?为什么三者不可兼得?
  2. 限流和熔断的区别是什么?它们分别解决什么问题?
  3. 你能说出至少三种实现故障转移的方式吗?

一、高可用的本质

1.1 什么是高可用

高可用(High Availability,简称 HA)是指系统在面对各种故障时,仍然能够持续提供服务的能力。衡量高可用的核心指标是可用性百分比

可用性 = 正常运行时间 / (正常运行时间 + 故障时间) x 100%

不同级别的可用性对应的年停机时间:

可用性级别百分比年停机时间典型应用
2 个 999%3.65 天内部管理系统
3 个 999.9%8.76 小时普通业务系统
4 个 999.99%52.6 分钟核心交易系统
5 个 999.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 自动切换?

[ ] 降级方案是否就绪?
    - 核心功能能否独立运行?
    - 是否有兜底数据/页面?
    - 降级开关是否可远程控制?

[ ] 是否定期演练?
    - 混沌工程(模拟故障)?
    - 灾难恢复演练?
    - 应急预案更新?

思考题

  1. 一个支付系统应该选择 CP 还是 AP?在网络分区发生时,是让用户看到”系统繁忙请稍后再试”更好,还是允许重复支付更好?

  2. Netflix 提出了”混沌工程”的理念,在生产环境中随机杀死服务实例来测试系统的高可用能力。你认为这种做法适合所有公司吗?什么阶段的公司适合引入混沌工程?


结尾自测

  1. 高可用设计的三大策略是什么?

    • :冗余(消除单点)、自动化(自动检测与转移)、隔离(限流熔断降级,控制故障范围)。
  2. 令牌桶算法相比漏桶算法的优势是什么?

    • :令牌桶允许一定程度的突发流量(桶中积累的令牌可以一次性消耗),而漏桶只能以固定速率处理请求,无法应对合理的突发需求。
  3. CAP 定理在工程实践中,真正需要做的选择是什么?

    • :在分区容忍(P)为前提下,选择一致性优先(CP)还是可用性优先(AP)。同一系统的不同模块可以做不同选择。
  4. BASE 理论中的”软状态”是什么意思?

    • :允许系统中的数据存在中间状态,即不同节点之间的数据可以短暂不一致,只要最终能达到一致即可。
  5. 熔断器的三种状态分别是什么?从 OPEN 状态如何恢复到 CLOSED 状态?

    • :CLOSED(正常放行)、OPEN(全部拒绝)、HALF-OPEN(试探放行)。从 OPEN 超时后进入 HALF-OPEN,放行少量试探请求,若成功率恢复正常则转为 CLOSED,否则回到 OPEN。

下一章预告:高可用保证系统稳定运行,但业务还在不断增长。下一章我们将进入可扩展架构的世界,聊聊分层架构、微服务、SOA 以及中台思想。

购买课程解锁全部内容

面试晋升必学:11 章掌握系统设计

¥29.90