https://notebooklm.google.com/notebook/12848566-c8ce-4004-97ae-3e0daf0f41bd/audio
微信链接: https://mp.weixin.qq.com/s/JZOzbOQ5VOlsx_PygUH9jQ
给我留下的较深的印象:
- Aurora 为了性能牺牲了通用性,是应用专用的, Aurora 只支持特定数据库格式,比如 MySQL / PostgreSQL 等
- 数据分片,好恢复
我们来一起深入探讨一下 Amazon Aurora 的设计。
在云计算的早期,如果你想在云上部署一个数据库,比如 MySQL,通常会怎么做呢?
- EC2 + 本地磁盘 :最直接的方式是在一个 EC2 虚拟机上运行
MySQL,数据就存在虚拟机挂载的本地磁盘上。这种方式很简单,但有个致命缺陷:虚拟机实例是有生命周期的,一旦实例崩溃或其所在的物理机发生故障,你的数据就可能随之丢失。这对于数据库来说是不可接受的。 - EC2 + EBS :为了解决数据持久性问题,Amazon 推出了弹性块存储(Elastic Block Store, EBS)。EBS 是一种网络附加存储,它独立于 EC2 实例的生命周期。即使你的数据库实例崩溃了,你也可以在另一个新的实例上重新挂载同一个 EBS 卷,数据安然无恙。为了保证高可用,EBS 内部通常会有一主一备两个副本,但这两个副本位于同一个 可用区 (Availability Zone, AZ)内,也就是同一个数据中心里,以保证写入的低延迟。
- 多可用区 RDS (Multi-AZ RDS) :虽然 EBS 解决了实例故障问题,但如果整个数据中心因为火灾、洪水或网络故障而瘫痪,你的数据库依然会下线。为此,Amazon 推出了跨可用区的数据库服务。如论文中的图 2 所示,它在另一个可用区建立了一个完整的镜像。主数据库的每一次写入,不仅要同步到同区的 EBS 副本,还要通过网络同步到另一个可用区的镜像实例及其 EBS 副本上,总共需要等待 4 个副本的写入确认。这极大地增强了容灾能力,但也带来了新的问题: 性能灾难 。
每一次数据库的修改,哪怕只是几行数据,在传统的数据库架构中,不仅要写日志,还要在未来的某个时刻把被修改的整个“数据页”(通常是 8KB 或 16KB)刷到磁盘上。这种现象被称为 写放大 (Write Amplification)。在多可用区 RDS 架构下,这些放大了的数据(日志 + 数据页)要在网络中传来传去,导致网络不堪重负,数据库吞吐量急剧下降。
正是在这样的背景下,Aurora 诞生了。它的核心目标是:在提供云级别的高可用和持久性的同时,彻底解决传统架构的性能瓶颈。
Aurora 的设计者们认为,在现代云环境中,计算和存储资源已不再是主要瓶颈,真正的瓶颈在于 网络 。既然如此,设计的核心就应该是想尽一切办法减少网络传输的数据量。
传统数据库之所以慢,是因为它把存储当成一个“笨家伙”,认为它只会读写数据块。因此,数据库引擎需要把修改后的、完整的 8KB 数据页通过网络发给存储层。
Aurora 的做法则完全不同。它认为,其实只需要把描述“如何修改”的 重做日志 (redo log)发送给存储层就足够了。这些日志记录通常非常小,只包含被修改的字节信息。存储节点不再是“笨家伙”,而是变得非常“聪明”,它们自己懂得如何解析这些日志,并将修改应用到对应的数据页上。
这个“将日志处理下推到存储层”的思路,就是 Aurora 的灵魂。 数据库引擎不再写入任何数据页,无论是检查点、缓存淘汰还是后台写入,一概没有。从数据库引擎的视角看, 日志本身就是数据库 (The log is the database)。存储层物化出的数据页,仅仅是应用了日志后的一个缓存而已。
这一改变带来了惊人的效果。论文的表 1 显示,在一次 30 分钟的测试中,Aurora 处理的事务量是传统镜像 MySQL 的 35 倍,而每笔事务产生的网络 I/O 却减少了 7.7 倍。这正是因为它极大地减少了网络上的数据传输量。
解决了性能问题,下一个关键是如何保证高可用和数据持久性。Aurora 的目标非常明确:
- 即使一整个可用区(AZ)挂掉,数据库依然能够 正常写入 。
- 即使一整个可用区(AZ)加上任何另外一个节点都挂掉,数据库依然能够 提供读取 (此时无法写入)。
这个目标被称为 “AZ+1” 的容灾能力。为了实现它,Aurora 使用了 法定人数 (Quorum)模型。
Quorum 的基本思想是,对于 N 个副本,一次写入操作必须成功写入 W 个副本,一次读取操作必须成功读取 R 个副本。只要满足 R + W > N,那么你每次读取的 R 个副本中,必然至少有一个副本包含了最近一次成功写入的数据。
一个简单的 N=3, W=2, R=2 的 Quorum 系统能容忍单个节点故障。但它无法满足 Aurora 的 “AZ+1” 目标。想象一下,3 个副本分布在 3 个 AZ 中,如果一个 AZ 发生火灾(这是一个相关性故障,该 AZ 内所有节点同时失效),此时你只剩下 2 个副本。如果恰好在另一个 AZ 里还有一个节点因为常规的硬盘损坏而离线,那么你就只剩 1 个副本了,无法组成读 Quorum,也就无法确定这最后一份数据是不是最新的。
因此,Aurora 采用了更为健壮的方案: 在 3 个不同的 AZ 中,共部署 6 个数据副本,每个 AZ 两个 。
它的 Quorum 配置是:
- 写入法定人数
Vw = 4 - 读取法定人数
Vr = 3
这个 6-4-3 模型精妙地满足了 “AZ+1” 的目标:
- 写可用性 :当一个 AZ(包含 2 个副本)挂掉时,还剩下 4 个副本,正好满足写入法定人数
Vw=4,数据库可以继续写入。 - 读可用性 :当一个 AZ(2 个副本)和另一个副本挂掉时(共损失 3 个副本),还剩下 3 个副本,正好满足读取法定人数
Vr=3,数据库依然可以读取数据,并以此为基础修复和重建副本。
仅仅有强大的 Quorum 模型还不够。在分布式系统中,故障是常态。我们需要尽可能缩短从故障中恢复的时间,也就是降低 平均修复时间 (Mean Time to Repair, MTTR)。MTTR 越短,系统暴露在风险下的窗口就越小。
为此,Aurora 引入了 分段存储 (Segmented Storage)的概念。一个庞大的数据库卷(最大可达 64TB)被切分成很多个固定大小(目前为 10GB)的逻辑块。每一个 10GB 的块,连同它的 5 个副本,共同组成一个 保护组 (Protection Group, PG)。每个 PG 的 6 个 10GB 分段 (Segments)同样遵循 6-4-3 模型,分布在 3 个 AZ 中。
一个数据库卷 (Volume) = [PG 1] + [PG 2] + [PG 3] + ...
PG 1 = [Segment 1.1 (AZ A), Segment 1.2 (AZ A),
Segment 1.3 (AZ B), Segment 1.4 (AZ B),
Segment 1.5 (AZ C), Segment 1.6 (AZ C)]
PG 2 = [Segment 2.1 (AZ A), Segment 2.2 (AZ A),
... ]分段存储的妙处在于 并行修复 。假设一个存储节点彻底坏掉了,它上面可能承载着来自上千个不同 PG 的分段。在传统模式下,你需要从一个备份节点拷贝海量数据(比如 10TB)来恢复,这可能需要数小时。
但在 Aurora 中,恢复工作可以完全并行化。PG-1 的分段可以从它的健康副本(比如在节点 X 上)恢复,PG-2 的分段可以从它的健康副本(比如在节点 Y 上)恢复…… 所有这些恢复任务可以同时在整个存储集群中进行。一个 10GB 分段的恢复,在万兆网络下只需要大约 10 秒钟。这意味着整个故障节点的恢复时间被极大地缩短了。
我们已经知道 Aurora 的核心是日志。那么,系统是如何精确地管理和使用这些日志的呢?这里有两个非常关键的概念,它们都与 日志序列号 (Log Sequence Number, LSN)有关,LSN 是一个由数据库引擎生成的、单调递增的数字,用于标识每一条日志记录的顺序。
- 写入 :客户端提交一个事务,数据库引擎会为其生成一系列日志记录。这些日志记录被发送给 6 个存储副本。只有当负责这条事务的所有日志记录都得到了至少 4 个副本的持久化确认后,数据库引擎才会认为这次提交是成功的,并向客户端返回确认。这个过程对客户端来说是异步的,提交请求的线程不必傻等,可以继续处理其他工作,由专门的线程负责返回确认,大大提高了并发性能。
- 读取 :当数据库因为缓存未命中而需要从存储层读取一个数据页时,它 通常不需要执行 Quorum 读 。因为数据库引擎自己就在追踪每个存储分段的日志接收进度,这个进度被称为 分段完成 LSN (Segment Complete LSN, SCL)。引擎知道哪些节点的数据足够新,可以直接向其中任何一个节点发起读取请求,非常高效。
当数据库主实例崩溃后,一个新的实例会启动并接管工作。此时,新实例需要确定一个全局一致的、可以从中恢复的日志点。
-
确定卷完成 LSN (Volume Complete LSN, VCL) :新实例首先会发起一次 Quorum 读 (读取 3 个副本)。它的目标是找出整个存储卷中,日志记录保证是连续且没有空洞的那个最高的 LSN 。这个点就是 VCL。任何 LSN 大于 VCL 的日志记录都可能是不完整的(比如,在崩溃前只发送给了 1、2 个副本),因此必须被截断和抛弃。
-
确定卷持久 LSN (Volume Durable LSN, VDL) :仅仅保证日志连续还不够。数据库内部的复杂操作(比如 B+ 树的分裂)可能需要多条日志记录才能完成,这些日志记录构成一个不可分割的原子单元,称为 迷你事务 (mini-transaction, MTR)。如果只恢复到 MTR 的中间状态,数据结构就会不一致,导致系统出错。因此,Aurora 在每个 MTR 的最后一条日志上打上一个标记,称之为 一致性点 (Consistency Point, CPL)。
VDL 就是在 VCL 之内(小于等于 VCL)的那个最高的 CPL 。VDL 确保了数据库从一个绝对安全和数据结构完整的点开始恢复。确定 VDL 后,所有 LSN 高于 VDL 的日志都会被截断。
最后,数据库会回滚(undo)那些在 VDL 之前开始、但没有提交记录的事务。由于所有繁重的日志重做(redo)工作都由存储层持续在后台进行,Aurora 的崩溃恢复过程非常快,通常在 10 秒内就能完成,无论崩溃前的写入负载有多大。
除了核心架构的创新,Aurora 还具备许多为生产环境量身定制的功能:
- 低成本、低延迟的只读副本 :Aurora 最多支持 15 个只读副本。与传统
MySQL不同,这些副本共享同一份存储卷,不需要额外的存储成本。主实例会将日志流实时发送给只读副本,副本在内存中应用这些日志来更新自己的缓存页。这使得副本的延迟极低(通常在 20 毫秒内),远胜于传统复制动辄数秒甚至数分钟的延迟。 - 在线 DDL :现代应用开发中,频繁修改表结构(Schema)是常态。
MySQL的很多 DDL 操作会锁表,甚至拷贝整张表,对生产环境影响巨大。Aurora 实现了高效的在线 DDL,它通过版本化的 Schema 和写时修改(modify-on-write)技术,使得大部分结构变更可以平滑进行,不影响业务。 - 零停机补丁 (Zero-Downtime Patching, ZDP) :对于云服务,即使是计划内的几十秒停机更新,也可能让客户难以接受。ZDP 技术可以在一个没有活动事务的瞬间,将数据库引擎的状态保存下来,快速替换引擎二进制文件,然后恢复状态,整个过程对用户的连接和会话是无感知的。
与 Google Spanner 的对比 :Aurora 和 Spanner 都强调跨 AZ 部署和使用 Quorum 模型。但最大的区别在于,Aurora 是一个 单写多读 (single-writer)系统,所有写操作都由一个主实例处理,这极大地简化了设计(例如 LSN 的生成)。而 Spanner 是一个真正的多写(multi-writer)分布式数据库,它通过 Paxos 和两阶段提交来处理分布式事务,扩展性更强,但复杂性也更高。
- 核心启示 :Aurora 的成功是一次 跨层优化 的典范。它打破了计算与存储之间的传统界限,让存储变得“智能”,深刻理解数据库的需求。它告诉我们,在设计云规模的系统时,必须正视网络这一核心瓶颈,并通过精巧的异步化、并行化和定制化设计来克服它。最终,Aurora 不仅实现了卓越的性能和可用性,还构建了一个更简单、更易于扩展的数据库架构基础。