Published on

AWS RDS HA Deep Dive

Authors
  • avatar
    Name
    Guming
    Twitter

基于早期架构分析

背景

AWS RDS-HA(高可用性)系统是一个分布式数据库复制解决方案,旨在通过多可用区部署策略,确保数据库服务的连续性和数据完整性。系统核心采用主备复制架构,结合智能监控和自动故障转移机制,为客户提供接近零数据丢失和最小服务中断的数据库服务

系统设计目标

  1. 多可用区复制支持

系统支持数据库实例在不同可用区的EC2实例上运行复制配置 每个实例独立挂载专用EBS卷,无共享存储依赖

  1. 数据持久性保障

一旦数据库写入操作提交完成,系统保证数据不会丢失 仅在极端黑天鹅事件(如多可用区同时故障)下可能出现数据不可用

  1. 高可用性承诺

单个副本故障不会导致长时间数据库服务中断 主副本故障时,系统能够自动切换到备用副本 故障转移过程对应用层透明

  1. 增强的持久性

相比非复制数据库方案,提供更高的数据持久性 有效防御可用区级别故障和单一EBS卷故障

Hight Level 架构设计

  1. 系统采用双副本架构,每个数据库至少运行两个副本实例:

主副本(Primary):处理所有读写请求的活动实例 备用副本(Secondary):接收同步复制数据的待命实例

  1. 每个副本实例具有以下特征:

运行在独立的EC2实例上 挂载专用的EBS卷(非共享存储) 部署在不同的可用区内 通过DRBD进行块级同步复制


┌─────────────────┐         ┌─────────────────┐
│   可用区 A      │         │   可用区 B│  ┌───────────┐  │         │  ┌───────────┐  │
│  │ Primary   │  │◄───────►│  │ Secondary │  │
│  │ Instance  │  │  DRBD   │  │ Instance  │  │
│  └─────┬─────┘  │  Sync   │  └───────────┘  │
│        │        │         │                 │
│  ┌─────▼─────┐  │         │  ┌───────────┐  │
│  │   EBS     │  │         │  │   EBS     │  │
│  │  Volume   │  │         │  │  Volume   │  │
│  └───────────┘  │         │  └───────────┘  │
└─────────────────┘         └─────────────────┘
         ▲                           ▲
         │                           │
         └───────────┬───────────────┘
              ┌──────▼──────┐
Observer                (可用区 C)              └─────────────┘

Core components

DRBD

DRBD是一个内核级模块,实现了无共享架构的复制存储解决方案。其工作机制如下:

  • 块级镜像:在服务器间镜像块设备内容
  • 主从架构:采用主从复制模式,主副本接收所有更新
  • 同步复制:所有写入操作被DRBD内核模块拦截并同步复制
  • I/O拦截:在块设备层面拦截所有I/O请求,确保数据一致性
应用层        [MySQL数据库]
               ↓ 读写请求
文件系统层    [EXT4/XFS文件系统]
               ↓ 块I/O
DRBD[DRBD内核模块] ← 在这里拦截所有I/O
               ↓              ↓
 (通过网络复制)
               ↓              ↓
物理设备层    [本地EBS]    [远程EBS]
1. MySQL执行写入:UPDATE users SET ...
2. 文件系统将写入转换为块I/O操作
3. DRBD内核模块拦截该I/O请求
4. DRBD执行双写:
   ├─→ 写入本地磁盘(主节点)
   └─→ 通过网络发送到远程节点
5. 远程节点DRBD接收数据
6. 远程节点写入本地磁盘(从节点)
7. 远程节点返回确认
8. 主节点DRBD收到确认
9. 向MySQL返回写入成功

代际标识符(Generation Identifiers)

DRBD使用代际标识符(GI)机制来管理数据版本和同步状态

GI的作用:

  • 验证节点是否属于同一复制对
  • 确定后台重新同步的方向
  • 判断是否需要完全重新同步或部分重新同步

识别脑裂场景

GI生成触发条件:

  • 复制对首次初始化时
  • 断开的备用副本切换为主副本时
  • 主副本与备用副本断开连接时

GI工作示例:

假设复制对S1(主)和S2(备):

  • 初始化阶段:S1生成初始GI(GI1)
  • 网络分区:S1与S2断开,S1进入降级模式并生成新GI(GI2)
  • 元数据存储:S1保存当前GI(GI2)和前一个GI(GI1)
  • 恢复优化:网络恢复后,S1识别S2的GI为其前一个GI,仅传输增量变更
  • 主故障切换:S1故障后,S2被提升为主副本时生成新GI,确保S1重新加入时能识别数据代际变化

Observer

Observer是故障检测和故障转移的编排组件,负责:

  • 健康监控:定期检测主副本和备用副本的健康状态
  • 故障检测:识别各种故障场景和网络分区情况
  • 故障转移决策:根据预定义算法决定是否触发故障转移
  • DNS管理:更新DNS指向以切换流量到新的主副本

监控机制

心跳检测:

  • Observer每隔T_heartbeat秒向主副本和备用副本发送心跳
  • 通过HTTP getStatus()命令查询实例状态
  • 副本执行DRBD状态检查并返回当前状态(IN_SYNC、STALLED、DEGRADED或DEAD)

状态信息收集:

  • 副本报告当前代际标识符(GI)
  • Observer在内存中维护主副本和备用副本的GI
  • 新Observer启动时从强一致性数据存储读取副本对列表
  • 每次状态检查时验证GI是否变化并更新内存记录

客户端连接机制

DNS路由策略: 系统通过DNS机制实现客户端与当前主副本的连接

  • 主副本和备用副本:各自拥有独立的外部DNS名称
  • DNS_primary:客户端连接的统一入口,CNAME指向当前主副本的DNS名称
  • 故障转移更新:主副本故障时,Observer更新DNS_primary的CNAME指向新主副本

流量切换流程:

  • 正常状态:DNS_primary → Primary Instance DNS
  • 故障切换:DNS_primary → Secondary Instance DNS (promoted to Primary)

读写处理模式

写操作流程:

  • 所有写请求发送到主副本数据库
  • 主副本接收写请求后,通过DRBD同步写入备用副本
  • 两个副本都成功写入后,确认写操作成功
  • 客户端收到写入成功响应

读操作流程:

  • 所有读请求仅在主副本上执行
  • 备用副本不对外提供读取服务
  • 确保读取数据的一致性和准确性

副本状态模型

主副本状态

观察状态(Observation State)

  • OBSERVED:实例与Observer保持连接
  • NOT_OBSERVED:实例与Observer断开连接

数据同步状态(Data Synchronization State)

IN_SYNC(同步状态)

  • 主副本和备用副本正常通信
  • 所有写入操作在两个副本间同步执行
  • 系统处于最佳运行状态

STALLED(停滞状态)

  • 主副本与备用副本断开连接
  • 无法继续执行任何I/O操作
  • 等待Observer或网络恢复的过渡状态

DEGRADED(降级状态)

  • 主副本与备用副本断开连接
  • 切换到非复制模式继续服务
  • 仅在主副本上执行读写操作
  • 备用副本不可用或不可达时的临时运行模式
  • 通常不会长期保持,RDS会创建新的备用副本

SUICIDAL(自杀状态)

  • 主副本与备用副本断开且与Observer失联
  • 只能从STALLED状态转换而来
  • T_failover秒后主副本主动关闭或重启自身
  • 避免脑裂场景的保护机制

主副本状态转换图

        ┌──────────────┐
IN_SYNC/OBSERVED        └──┬───────┬───┘
           │       │
    S断开  │       │  与OS同时断开
           │       │
       ┌───▼───┐   │
STALLED│   │
       └───┬───┘   │
           │       │
    听到O  │       │  未听到O
           │       │
       ┌───▼───┐ ┌─▼────────┐
DEGRADED│ │ SUICIDAL/OBSERVED│ │/NOT_OBS       └────────┘ └──────────┘

备用副本状态

Connected(连接状态)

  • 备用副本健康运行
  • 与主副本保持通信连接
  • 正常接收复制数据

Disconnected(断开状态)

  • 备用副本健康运行
  • 与主副本失去通信连接
  • 无法接收复制数据

Observer根据主副本和备用副本的综合状态,维护整个数据库系统的全局状态视图:

  • ALL_OK:主副本和备用副本都正常运行且同步
  • P_ONLY:仅主副本可达,备用副本不可用
  • S_ONLY:仅备用副本可达,主副本不可用
  • P_DEGRADED:主副本处于降级模式运行
  • NEED_STANDBY_RECOVERY:需要恢复或重建备用副本

Failover 算法

故障转移触发条件 Observer在满足以下所有条件时触发故障转移: 条件一:主副本失联超时

Observer与主副本失去联系超过T_failover秒 T_failover = n × T_heartbeat + T_buffer n ≥ 2,T_buffer为主副本自我终止的最大预期时间

条件二:备用副本状态确认

备用副本处于DISCONNECTED状态 备用副本无法与主副本通信

条件三:最后已知状态验证

失去联系时主副本的最后状态为IN_SYNC 如果无法确定主副本状态(如Observer重启),则视为DEGRADED,不触发故障转移

故障转移执行流程

步骤1:发起提升请求

  • Observer向备用副本的HostManager发送promoteToPrimary(oldGI)请求
  • oldGI是主副本的最后已知代际标识符

步骤2:备用副本验证

a. 确认仍无法与主副本通信
b. 检查当前GI是否等于oldGI
c. 如果GI匹配:
   - 执行DRBD命令提升为主副本
   - 返回新GI给Observer
   - 挂载文件系统并启动MySQL
d. 如果GI不匹配:
   - 可能存在脑裂场景
   - 中止提升操作
   - 触发告警,等待人工介入

步骤3:DNS切换

  • Observer成功提升备用副本后
  • 更新DNS_primary的CNAME指向新主副本
  • 客户端流量自动路由到新主副本

步骤4:恢复标记

  • 标记实例状态为需要备用副本恢复
  • 启动备用副本重建流程

分布式监控架构

Event Processor(EP)架构

RDS监控系统由多个Event Processor(EP)节点组成,每个EP承担一部分监控工作负载:

  • EP节点:运行Observer软件组件的监控节点
  • 分布式架构:多个EP协同工作,无单点故障
  • 工作负载分片:基于哈希的分区算法分配监控任务

分区策略:

  • 使用DBInstanceIdentifier作为哈希键
  • 确保监控工作负载均匀分布
  • 支持动态扩展和缩减EP节点
┌─────────────────────────────────────────────────┐
RDS Monitoring System│                                                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐     │
│  │   EP-1   │  │   EP-2   │  │   EP-N   │     │
  (AZ-A)  (AZ-B)  (AZ-C)  │     │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘     │
│       │             │             │            │
└───────┼─────────────┼─────────────┼────────────┘
        │             │             │
        └─────────────┴─────────────┴───────────┐
        ┌────────────────────────────────────────▼──┐
Database Instances        │  ┌──────────┐  ┌──────────┐             │
        │  │ DB1(P-S) │  │ DB2(P-S)...        │  │ AZ1-AZ2  │  │ AZ2-AZ3  │             │
        │  └──────────┘  └──────────┘             │
        └──────────────────────────────────────────┘

EP工作负载分区

设计原则

线性可扩展性:

  • 监控系统随数据库实例数量线性扩展
  • 通过简单添加EP节点实现扩展
  • 无需修改现有架构

EP位置约束:

  • EP必须部署在与其监控的数据库副本不同的可用区
  • 防止可用区故障导致数据库和Observer同时失效
  • 避免出现不可恢复的状态

EP故障处理:

  • 所有数据库实例持续被监控
  • EP故障时,其监控的数据库实例重新分配给其他EP
  • 工作负载自动重新平衡

分区算法实现

EP分组策略:

  • 按可用区将EP节点分组
  • 每组EP仅监控副本不在该可用区的数据库

示例场景:

假设4个可用区,4个EP组:

G1 (AZ1EP)监控副本在 (AZ2, AZ3), (AZ2, AZ4), (AZ3, AZ4) 的数据库
G2 (AZ2EP)监控副本在 (AZ1, AZ3), (AZ1, AZ4), (AZ3, AZ4) 的数据库
G3 (AZ3EP)监控副本在 (AZ1, AZ2), (AZ1, AZ4), (AZ2, AZ4) 的数据库
G4 (AZ4EP)监控副本在 (AZ1, AZ2), (AZ1, AZ3), (AZ2, AZ3) 的数据库

分配算法

1. EP1启动,发现1000个数据库需要监控
2. EP1发现4EP覆盖该可用区对
3.EP标识符字典序排序
4. 均匀分配:
   - EP1: DB1 - DB250
   - EP2: DB251 - DB500
   - EP3: DB501 - DB750
   - EP4: DB751 - DB1000

EP故障检测与恢复

EP间心跳机制

心跳协议:

  • 每个EP定期向所有其他EP发送心跳消息(HTTP)
  • 心跳间隔:10秒
  • 传输EP的状态信息和监控负载

EP状态管理:

  • 每个EP维护其他EP的状态列表
  • 状态包括:AVAILABLE或DEAD
  • 记录最后心跳检入时间

故障判定:

if (当前时间 - 最后心跳时间) > heartbeat_failure_time:
    标记EPDEAD
    重新调整监控工作负载

heartbeat_failure_time = 6 × 心跳间隔

EP启动与恢复流程

BOOTSTRAP模式:

  1. EP启动进入BOOTSTRAP模式
  2. 持续时间:heartbeat_failure_time
  3. 操作:
    • 接收其他EP的心跳
    • 启动自己的心跳代理
    • 不承担监控工作负载

OPERATIONAL模式:

  1. BOOTSTRAP期满后进入OPERATIONAL模式
  2. 基于当前EP状态确定监控负载
  3. 开始监控分配的数据库实例

多EP监控的一致性保证

潜在问题:

  • 网络分区可能导致多个EP同时监控同一数据库
  • 可能造成多个EP尝试故障转移

解决方案:租约机制

EP租约机制

确保在任何时刻,只有一个EP有权对特定数据库实例执行故障转移操作,防止脑裂场景。

租约获取流程

租约授予条件:

EP向数据库副本发送status ping时,主副本的HostManager仅在以下条件全部满足时授予租约:

  • 该副本是DRBD主副本
  • 副本处于同步状态
  • 当前没有授予其他EP的有效租约

Lease 持久化

1. 主副本将租约信息写入DRBD磁盘:
   - 租约到期时间
   - EP标识符
   
2. 由于DRBD同步机制,备用副本自动收到租约信息

3. 写入成功(两个副本都完成)后,主副本向EP返回租约

租约参数:

  • 租约有效期:T_lease
  • 仅在租约期内EP有权执行故障转移

防止脑裂:

场景:网络分区导致两个EP分别可达主备副本
EP1: 有租约,可达主副本
EP2: 无租约,可达备用副本

结果:
- EP2无法触发故障转移(无租约)
- 只有EP1能执行故障转移操作
- 避免双主副本出现

一致性保证:

  • 租约信息通过DRBD同步,保证主备一致
  • 租约过期机制防止僵尸EP持续持有权限
  • 基于时间的租约自动失效机制

可用区故障处理

初始配置:

  • G1 (AZ1) → 监控 (AZ2, AZ3) 的数据库
  • G2 (AZ2) → 监控 (AZ3, AZ1) 的数据库
  • G3 (AZ3) → 监控 (AZ1, AZ2) 的数据库

AZ1故障时的问题:

  • 需要调整EP部署策略
secondary-az-colocation-override flag = true

重新配置:
- EP组监控主副本不在该AZ的数据库
- 允许EP与备用副本在同一AZ
- 容忍包含主副本的AZ故障

示例重配置:
G2 (AZ2) → 监控主副本在AZ3的数据库(备用副本在AZ2G3 (AZ3) → 监控主副本在AZ2的数据库(备用副本在AZ3

监控数据库分区

分区需求

监控数据库(Monitoring DB)会持久化与客户数据库及其安全组相关的事件。虽然我们可能会将这些事件保留更长时间,但计划仅向客户展示过去 14 天 的事件。随着数据库数量和客户数量的持续增长,单个数据库的存储空间可能不足

需要分区的数据:

  • 数据库实例列表(db_poll_list)
  • 客户事件记录(db_events表)
  • 安全组事件(security group events表)

设计目标:

  • 支持水平扩展
  • 超越单一物理系统限制
  • 提前规划架构以支持跨多数据库系统扩展

分区键选择

分区键:customer_id(客户标识符)

选择理由:

事件的广泛性

  • 某些事件不限于单个数据库
  • 安全组变更影响多个数据库
  • 客户级别事件需要统一存储

可扩展性假设

  • 单个客户的事件不会超过单数据库存储容量
  • 事件仅保留14天
  • 合理的数据增长预期

桶分区(Bucket Partitioning)

桶分区概念:

在数据和物理分区之间增加抽象层:

客户数据 → 哈希 → 桶(Bucket) → 映射 → 物理分区

系统设置:

桶数量

  • 初始配置:65,000个桶
  • 桶数量在系统生命周期内固定
  • 足够大以确保负载均匀分布

数据到桶的映射

  • 使用一致性哈希算法
  • 客户标识符 → 桶编号
  • 客户永久关联到特定桶

桶到分区的映射

  • 使用范围映射,方便配置
  • 多个桶映射到一个物理分区
初始配置(2个分区):
分区1 = {1 -25000}
分区2 = {25001 -50000}

示例:
客户A的哈希 → 桶100
客户A的所有数据存储在分区1

动态重分区

扩展场景:添加新分区以分担负载

初始状态:

分区1 = {1-25000}
分区2 = {25001-50000}

添加分区3后:

分区1 = {1-16666}
分区2 = {33333-50000}
分区3 = {16667-33333}
重分配结果:

从分区1迁移桶16667-25000到分区38334个桶)
从分区2迁移桶25001-33333到分区38333个桶)

实现负载的公平重分配

数据迁移问题:

重分区后,同一桶的数据可能分散在不同物理分区

解决方案:双分区映射

每个桶可以关联两个分区:

  • 当前分区:新数据写入位置
  • 历史分区:旧数据存储位置

写入新数据进入新分区

查询处理:

查询桶11000的事件:
1. 首先查询当前分区(分区32. 如果需要,查询历史分区(分区13. 合并两个分区的结果

性能考虑:

  • 优先查询当前分区(新数据为主)
  • 历史分区查询可能未命中
  • 由于仅保留14天数据,历史数据量可控

副本故障恢复流程

主副本重启

重启后状态变化:

  • 重启前:DRBD状态为 primary/secondary

  • 重启后:DRBD状态变为 secondary/secondary

恢复流程:

主副本重启后行为

  • HostManager检测到DRBD状态变化
  • 主副本以secondary身份上线
  • 不自动提升为主副本

Observer决策

  • 检测到两个副本都是secondary状态
  • 确定哪个副本应为主副本
  • 向选定的副本发送promoteToPrimary()请求

状态恢复

  • 选定副本提升为主副本
  • 另一副本保持备用副本角色
  • 恢复正常的主备复制

与故障转移的区别:

  • 如果Observer尚未执行故障转移,原主副本可能被重新提升
  • 如果Observer已完成故障转移,原主副本作为新的备用副本

备用副本重启

Observer行为:

  1. 检测到备用副本离线
  2. 标记实例需要恢复
  3. 启动备用副本恢复工作流

快速恢复路径:

if 备用副本在短时间内重新上线:
    恢复工作流检测到备用副本已返回
    请求备用副本HostManager重新连接到主副本
    避免创建新备用副本的开销
else:
    执行完整的备用副本重建流程

优化效果:

  • 简单重启场景快速恢复
  • 减少不必要的资源消耗
  • 缩短恢复时间窗口

副本实例故障

主副本故障恢复

故障检测与初步恢复:

  1. Observer检测到主副本故障
  2. 提升备用副本为新主副本
  3. 更新DNS_primary指向新主副本
  4. 在Admin DB中标记状态: PENDING/DEGRADED_NEED_SECONDARY_RECOVERY
  5. 触发恢复扫描器启动恢复工作流

恢复工作流决策树:

恢复工作流检查旧主副本状态:

if 旧主副本已作为secondary上线:
    // 可能重启时间较长导致被标记为死亡
    将旧主副本连接到新主副本
    等待副本完全同步
    标记数据库状态为 OK
    恢复完成
    
else if 旧主副本完全不可达:
    终止旧实例
    使用创建复制实例的标准流程创建新备用副本:
        1. 启动新EC2实例
        2. 配置EBS        3. 从新主副本同步数据
        4. 建立DRBD复制
    恢复完成
  • 优先尝试重用旧实例
  • 仅在必要时创建新实例
  • 确保最终恢复到双副本状态

备用副本故障恢复

故障检测:

  1. Observer检测到备用副本故障
  2. 标记Admin DB状态: PENDING/DEGRADED_NEED_SECONDARY_RECOVERY
  3. 触发恢复工作流

恢复流程:

与主副本故障类似,但更简单:

  1. 主副本保持正常运行
  2. 主副本进入DEGRADED模式
  3. 恢复工作流:
    • 检查旧备用副本是否可恢复
    • 如可恢复:重新建立复制连接
    • 如不可恢复:创建新备用副本
  4. 完成后恢复IN_SYNC状态

优先级:

  • 主副本故障优先级更高(影响服务)
  • 备用副本恢复可以后台进行
  • 两种故障都需要最终恢复双副本状态

数据库进程崩溃

自动恢复机制:

1. 数据库进程(MySQL)因任何原因崩溃
2. 主副本的HostManager作为守护进程监控数据库状态
3. 检测到数据库进程死亡
4. 自动重启数据库进程
5. 恢复服务

HostManager的职责:

  • 持续监控数据库进程健康状态
  • 进程崩溃时自动重启
  • 记录崩溃事件和恢复操作
  • 无需Observer介入

优势:

  • 快速本地恢复
  • 减少服务中断时间
  • 进程级故障无需实例级故障转移
  • 降低恢复复杂度

设计要点总结

可靠性保障

数据持久性:

  • 同步复制确保数据写入双副本后才确认成功
  • DRBD块级复制提供强一致性保证
  • 代际标识符防止数据不一致

故障隔离:

  • 跨可用区部署避免单点故障
  • Observer部署在独立可用区
  • 故障域隔离设计

自愈能力:

  • 自动故障检测
  • 自动故障转移
  • 自动副本恢复

性能与可扩展性

监控系统扩展:

  • EP节点水平扩展
  • 基于哈希的负载均衡
  • 监控数据库分区支持

故障转移速度:

  • T_failover参数可调优
  • 预防性超时机制
  • DNS快速切换

资源利用:

  • 仅主副本处理客户请求
  • 备用副本专注于复制
  • 合理的资源分配策略

安全性设计

防止脑裂:

  • 主副本SUICIDAL机制
  • EP租约系统
  • GI验证机制

一致性保证:

  • 同步复制
  • 代际跟踪
  • 状态机严格转换

最小化数据丢失:

  • 仅在IN_SYNC状态下故障转移
  • 严格的故障转移前置条件
  • 不确定状态下保守处理