Appearance
AWS Aurora MySQL 内核改造方案深度分析:云原生数据库的范式转变
I. 背景:传统架构的瓶颈
在传统 MySQL (InnoDB) 架构中,I/O 和对于数据处理的计算总是在同一台机器上进行着的。这种架构,要求计算实例(mysqld 进程)独自负责所有持久化工作 。
写操作是传统 MySQL 的一大瓶颈 :
- Redo Log 同步刷新: 每次事务提交,都需要将 Redo Log 记录同步刷新到本地磁盘,以保证持久性。
- Doublewrite 双写与 I/O 放大: 在异步将内存中的脏页(Dirty Pages)刷盘过程中,为了避免在写入 16KB 数据页时出现部分写入(Torn Page)的情况 ,数据页必须先完整写入 Doublewrite Buffer 区,再写入最终的数据文件位置 。
- 这个机制虽然保证了数据安全,但本质上要求每一个数据页都被写入两次,造成了写入路径上的 I/O 放大(Write Amplification) 。
写入过程成为了 MySQL 性能的一大瓶颈。在一个主从集群中,这个繁重的持久化过程会在多个从库和主库上重复发生,每个节点同时带来了计算和储存的负担。
Aurora 存算分离方案应运而生。
II. 概述:存算分离与 Quorum 机制
Aurora 存算分离将不同的节点用存储节点和计算节点做区分 。
A. 计算节点
计算节点只负责处理业务逻辑(SQL 计算),不存储具体的数据的权威副本。
- 内存计算: 计算节点使用其内存中的热数据(Buffer Pool)进行 SQL 计算 。
- 发送指令: 在计算后,它将 Redo Log 记录(即物理修改指令)通过网络发送给存储节点。它不再需要在本地储存 Redo Log 文件,也不再执行 Doublewrite 机制,彻底消除了写入路径上的本地 I/O 放大 。
B. 存储节点
存储节点在逻辑上就是这个集群的共享存储卷(Cluster Volume) 。
- 高可用配置: 存储节点自动将数据复制成 6 份,分布在至少三个 AZ(可用区)中,以保障高耐久性。
- 接收日志流: 它异步接收计算节点计算出的 Redo Log 记录。Redo Log 在存储层被精心管理,实现了日志流的顺序写入和持久化。
C. Quorum 法定人数确认与 HA 机制
在 Aurora 的设计当中,一个计算节点对着六个存储节点。
- 耐久性保障: 当事务发生提交(
COMMIT)的时候,计算节点必须在这六个存储节点中等待确认,只有 至少四个存储节点 确认收到并持久化了 Redo Log 记录(包括提交标记)后,事务才被视为持久化成功。 - 热备用节点: 为了实现高可用性(HA),集群中通常存在多个计算节点(最多 15 个读副本)。这些只读副本作为热备节点,随时待命,以便主计算节点挂了之后可以迅速接管其位置,将故障转移时间降至数十秒以内 。
III. 设计优势:性能飞跃与运维简化
通过将计算和储存过程分离,将计算和储存变成一个网络并行的过程。
- I/O 瓶颈消除: 计算节点的所有操作都基于内存,避免了计算过程中的同步 I/O 等待,彻底消除了一系列本地持久化操作(如 Doublewrite)带来的性能瓶颈 。
- 高性能: 这种架构优化使 Aurora 能够提供高达 5 倍于标准 MySQL 的事务吞吐量 。
- 极速故障转移(RTO): 由于数据的一致性已在共享存储层通过 Quorum 机制保障,计算节点本质上是无状态的。新的计算节点接管时,无需执行耗时的日志重放(Crash Recovery),只需接管控制权,故障恢复速度无比迅速,提供了极高的可用性支持。
IV. 计算节点内存处理:NVMe 与二级缓存
在进行 SQL 计算的时候,如果每次都读取冷数据,每次都要发起远程的网络 I/O,可能会比本地 I/O 读取更耗时。
- 高速 SSD 二级缓存: 为了解决这个远程 I/O 延迟问题,Aurora 引入了 NVMe(Non-Volatile Memory Express) 存储来支持 Optimized Reads 功能 。
- 分层缓存(Tiered Cache): 计算节点使用本地 NVMe SSD 作为二级缓存(L2 Cache),它自动存储从内存(L1 缓存,Buffer Pool)中因 LRU 算法淘汰的内容 。
- 效果: L2 缓存可以拦截原本需要发往远程存储层的读取请求,对于以前必须从远程存储获取数据的查询,它能提供高达 8 倍的延迟改善 。
- 临时对象优化: NVMe 存储还用于承载复杂查询(如排序、连接)产生的临时文件和临时表。这避免了对远程存储产生不必要的写入流量,进一步提升查询吞吐量 。
经济效益: 通过使用 Aurora I/O-Optimized 存储配置,客户可以实现 I/O 费用为零 。这消除了高缓存未命中率带来的可变 I/O 成本风险。
V. 故障转移与事务一致性:提交标记的权威性
在事务提交前,计算节点生成的 Redo Log 记录会先发送到储存节点上,异步进行预传输(Pre-Shipping)。这些记录会被标记为未提交的临时数据。
同步时刻:
在事务要提交的时候,计算节点会发一个带有最后一个 LSN 的 Commit Redo Log 记录,并必须同步等待来自存储层的 4/6 法定人数的确认(ACK) 。
故障转移的自动回滚机制
我们用一个的例子来理解是怎么做故障转移的:
假设有计算节点 X, Y。X 是主计算节点,Y 是只读副本。X 执行事务,存储节点已收到 ABC(已提交),而 X 要执行 DEF。当 X 执行完 DE,F 还没执行就挂了。
- 崩溃时的状态: 存储节点中会有 DE 的 Redo Log 记录,但 DE 会被标记为未提交。计算节点 X 内存中的脏页和所有未处理的状态都被丢弃。
- Y 接手: Y 成为新的计算节点,它立即连接到共享存储卷 。
- 存储层判断: 存储层发现 X 崩溃了,并且 DE 记录缺少 Quorum 确认的“提交标记”。
- 自动丢弃(逻辑回滚): 存储层将这些 DE 记录在逻辑上视为无效并自动丢弃。
- 结果: Y 只会读取到已提交的 ABC。系统自动完成了数据的逻辑回滚,而不需要 Redo Log 先加载进来再一个一个 UNDO 的繁琐步骤。
VI. 背压设计与持久化 LSN
由于计算节点将 I/O 瓶颈下移,可能存在存储速率跟不上计算节点写入速率的情况。
- VDL (Volume Durable LSN): 存储卷持久化 LSN。这是整个存储集群已通过 Quorum 确认并持久化的最高日志序列号 。
- LAL (LSN Allocation Limit): LSN 分配限制。目前定义的是 10,000,000 。
LAL 的作用: LAL 机制规定:新分配的 LSN < VDL + LAL。
你的 LSN 分配不能领先最后提交的 LSN (VDL) 超过 1000 万。如果计算节点的写入速度过快,使得这个差距超过 LAL,那么计算节点会被强制停止分配新的 LSN。这引入了**“背压”(Back Pressure)**,避免了存储节点堆积过多 Redo Log 记录,导致储存节点压力过大 。
VII. 储存层数据同步与自我修复
即使事务已提交,仍可能有少数未能持久化日志的存储节点(如 2 个节点丢失了数据)。
- SCL (Segment Complete LSN): 分片完整 LSN。它标志着该存储分片已收到了所有连续链接的 Redo Log 记录。
- Gossip 协议: 存储节点之间会通过 Gossip 协议相互交换 SCL 值 。
- 数据追齐: 当发现某个节点的 SCL 落后太多,落后的节点会向进度靠前的对等节点请求补齐缺失的 Redo Log 记录 。
这是始终在后台中进行的自我修复过程,它与前台事务提交是解耦的 ,确保了即使少数节点有缺失,整个集群的数据耐久性和一致性依然得到维护。
