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断开  │       │  与O和S同时断开
           │       │
       ┌───▼───┐   │
       │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 (AZ1的EP) → 监控副本在 (AZ2, AZ3), (AZ2, AZ4), (AZ3, AZ4) 的数据库
G2 (AZ2的EP) → 监控副本在 (AZ1, AZ3), (AZ1, AZ4), (AZ3, AZ4) 的数据库
G3 (AZ3的EP) → 监控副本在 (AZ1, AZ2), (AZ1, AZ4), (AZ2, AZ4) 的数据库
G4 (AZ4的EP) → 监控副本在 (AZ1, AZ2), (AZ1, AZ3), (AZ2, AZ3) 的数据库

分配算法

1. EP1启动,发现1000个数据库需要监控
2. EP1发现4个EP覆盖该可用区对
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:
    标记EP为DEAD
    重新调整监控工作负载

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的数据库(备用副本在AZ2)
G3 (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到分区3(8334个桶)
从分区2迁移桶25001-33333到分区3(8333个桶)

实现负载的公平重分配

数据迁移问题:

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

解决方案:双分区映射

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

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

写入新数据进入新分区

查询处理:

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

性能考虑:

  • 优先查询当前分区(新数据为主)
  • 历史分区查询可能未命中
  • 由于仅保留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状态下故障转移
  • 严格的故障转移前置条件
  • 不确定状态下保守处理