目录
一、基本认知
二、分布式中的持久化
(一)复制和关联故障
(二)分片的存储
(三)可迅速恢复
三、log就是数据库
(一)写放大的负担
(二)把redo处理过程下沉到存储层
(三)存储服务设计要点
四、log机制细节
(一)方案梗概:异步处理
(二)普通操作
写
提交
读
复制
恢复
五、将这些东西组合起来
一、基本认知
理解《Amazon Aurora: 面向高吞吐量云原生关系型数据库的设计考虑》zooming对于亚马逊AWS的关系数据库服务Aurora的设计理念和架构。Aurora采用了分离计算和存储的方案,将事务和并发控制放在计算层,同时将Redo Log推送到可扩展存储的服务中。
在现代分布式云服务中,通过解耦计算和存储,并实现多节点数据复制、自恢复和可扩展性,有效地解决了IO瓶颈问题。Aurora的存储层采用面向服务的架构,管理着虚拟化的分段Redo Log,并与数据库实例协同工作。相比传统架构,Aurora具有以下三点优势:
首先,通过构建容灾、自修复的跨数据中心服务,将数据库隔离于短暂或持久的故障之外;
其次,将Redo Log写入存储层可降低网络IOPS;
最后,将复杂且昂贵的功能转移到跨多个节点的异步分批操作中,使得崩溃恢复不再需要CheckPointing和影响前台处理的数据备份。
二、分布式中的持久化
(一)复制和关联故障
在大型分布式系统中,复制数据是确保系统容错性的关键措施之一。在面对持续的底层节点、磁盘和网络故障等后台噪音时,必须以某种方式复制数据,以保证系统的可用性和可靠性。一种常见的容灾方式是使用Quorum投票来进行数据复制,其中要求满足的条件是 Vr + Vw > V,并且 Vw > V/2。通常情况下,使用的Quorum配置是 V = 3, Vr = 2,Vw = 2。
然而,对于Amazon Aurora来说,这种通用的2/3 Quorum组合并不足够。Aurora采用了一种更为强大的设计原则,即挂掉一个AZ并且挂掉另一个AZ的节点不会导致数据丢失,挂掉一个AZ也不会影响数据写入。为了实现这一目标,Aurora将数据复制到了三个AZ中,每个AZ存储两份数据副本,总共达到了六个副本。这样,即使发生一个AZ的故障加上另一个AZ的节点故障(共计三个节点故障),数据仍然可以读取。而如果发生任意两个节点的故障或一个AZ的故障,数据仍然可以写入。通过降低Vw的值可以确保在数据复制的情况下重新构建足够的Vw,从而实现数据的恢复和写入。
(二)分片的存储
为了确保持久性,并使两次故障的可能性低于修复一次故障的时间,Amazon Aurora采用了一种分片的存储策略。当故障事件是独立发生的时候,降低两次故障的概率是非常困难的。因此,Aurora聚焦于降低故障修复的时间(MTTR),以减少在数据修复过程中遇到二次故障的可能性。
Aurora通过将数据库卷分割成固定长度为10GB的分片来降低MTTR。每个分片都会被复制成六份,形成一个PG(Protection Group),这六个副本会跨越三个可用区(AZ),每个AZ中有两个副本。一个数据库卷由一系列PGs组成,而Volume中的PGs会随着Volume的增长而动态分配。目前,Aurora支持的最大卷大小可达到64TB。
分片是Aurora处理后台故障和修复的最小独立单元。在万兆卡的环境下,10GB的数据可以在10秒内修复完成。因此,只有在10秒内同时发生两个节点的故障,并且同时挂掉一个AZ,且该AZ不包含任何一个出现故障的节点,系统才无法形成Quorum。这种情况几乎是不可能发生的。
(三)可迅速恢复
拥有可迅速恢复的运维优势意味着系统设计能够从长时间的故障中快速恢复,也能够在短暂的故障发生时快速自愈。一个能够处理一个AZ长时间挂掉的存储系统,同样也能够处理一个机房短暂停电或者程序回滚的情况。同样地,一个系统能够处理几秒钟的节点不可用,也能够处理网络重点或者节点负载突然增加的短暂故障。
因此,当系统具备高容错性时,可以利用这一特性执行一些可能导致分片不可用的维护操作。例如,对于热点管理,我们可以将一个热点磁盘或者节点上的数据分片标记为损坏,然后集群会快速将数据迁移到其他负载较低的节点,迅速修复这些数据分片。对存储节点进行安全补丁或服务升级也将成为短暂的不可用事件,但由于系统的高容错性,这些事件不会对系统造成严重影响。
三、log就是数据库
(一)写放大的负担
虽然把Volume分片,并且复制6个副本到3个AZ的方式提供了极强的自恢复能力。但是,这个模型如果跑在一个传统的数据库上,性能会是非常差的。Volume的IO会因为复制而放大,并且有一个高的PPS(Packets per Second)。并且,IO导致同步和阻塞,增加延迟。
像MySQL,会写数据页,并且会写Redo Log记录到 Write-Ahead Log。每一条Redo Log由数据页改变前后的diff组成。将Redo Log应用到改变前的数据页,可以得到改变后的数据页。
如果还有一个镜像MySQL从库作为高可用备份。每个数据库实例还会将数据写到EBS上,这时候可能还会有一个通过软件的镜像EBS卷。
上图展示了一个数据库引擎需要写的多种数据:Redo Log,备份到S3的binlog,写的数据页,为防止数据页写坏的Double Write的数据页,还有MetaData文件。第一步、第二步,数据是写到EBS(一主一备),两个都写成功后,会有一个ACK。第三步,写会通过块级别的软件镜像传输到备份实例。最终,第四、五步,会写入到备份实例的EBS及其镜像。
镜像mysql模型里,1,3,5都是顺序同步写入的,会增加延迟。从分布式的视角,这个系统是一个4/4 写quorum,无法容忍失败和性能抖动。另外,就是用户的操作生成了好多种不同的写,但是实际上这些写是代表了同样的信息,比如double write
(二)把redo处理过程下沉到存储层
在Aurora,唯一会穿越网络的写入只有redo log。数据库层不会往下写data page,也不会有后台写,checkpointing,cache剔除。aurora会持续的通过redo log在后台实例化数据页以避免每次请求的时候重复生成这些页。不同于checkpoinging,只有一个页有过多的修改的时候才需要被实例化,而checkpointing是受限于整个redo log链的长度。Aurora页实例化只受限于单个页的redo log数目。
Aurora虽然写了6个副本数据,但是仍然极大地降低了网络IO量。如图三展示了一个一主多从跨多机房部署的Aurora集群。在这种架构下,主只写log记录到存储层服务,并且将这些log记录传输到从库上作元数据更新。IO流将有序的属于同一个目的地(同PG)的log记录打包,然后把每个批量包发送到所有的6个副本上。然后,数据库引擎会等待6个副本中有超过4个回复了ack就认为满足了写quorum,log记录已经持久化成功。从库使用redo log记录来更新到他们自己的buffer caches。
为评估网络IO情况,用sysbench跑了一个只写负载测试,测试数据集有100GB。一个用同步镜像mysql,跨多机房部署,一个使用多副本跨多中心部署的RDS Aurora。两个测试都运行超过30min以上,数据库引擎云星宇EC2实例上。
实验的结果如表:在这30min时间内,Aurora可以处理超过镜像mysql 35倍的事务量。每个事务的IO数量不到镜像mysql的1/7,尽管aurora有6倍的写放大,并且还没有计算EBS的链式复制和mysql的跨机房写。在存储层每个存储节点处理请求消耗的IO只有mysql的1/46。通过写更少的数据到网络层节省下来的资源,可以让我们为了数据持久性和可用性有更多的数据副本,并行发出请求以减小抖动的影响。
(三)存储服务设计要点
存储服务的核心设计宗旨是尽量减少前台写请求的延迟。为了实现这一目标,大部分存储处理被转移到了后台执行。
由于存储层的峰值和平均前台请求之间存在自然的变化,我们有足够的时间在前台路径之外执行这些任务。此外,我们也有机会通过牺牲一部分CPU资源来换取更高的磁盘性能。举例来说,当存储节点忙于处理前台写请求时,我们不会立即运行垃圾回收等任务,除非磁盘接近容量。在Amazon Aurora中,后台处理与前台处理呈负相关。这一点与传统数据库不同,传统数据库中,后台写入和检查点与前台负载呈正相关。如果在系统中建立了一个后台处理的积压队列,我们将会抑制前台活动,以防止长队列的形成。由于数据段以高熵分布在系统中的各个存储节点上,一个存储节点上的节流很容易通过我们的4/6仲裁写入处理,从而导致该节点表现为慢速节点。
以下是存储节点上各种活动的更详细描述:
-
接收日志记录并添加到内存队列中: 存储节点首先接收来自前台写请求的日志记录,并将它们添加到内存队列中。这些日志记录包含了要写入数据库的数据更新或变更。
-
将记录保存在磁盘上并进行确认: 存储节点将内存队列中的日志记录写入磁盘,并在写入完成后进行确认。确认确保数据已经成功地写入到持久存储中。
-
组织记录并识别日志中的空白: 存储节点组织并处理磁盘上的日志记录,同时识别可能存在的空白或缺失的日志记录。这些缺失的记录可能是由于批次丢失或其他原因导致的。
-
通过gossip协议与对等方进行通信以填补空缺: 如果发现了缺失的日志记录,存储节点会通过gossip协议与其他对等方进行通信,以获取缺失的数据并填补空缺。
-
将日志记录合并到新的数据页面中: 存储节点将已确认的日志记录合并到新的数据页面中。这些数据页面包含了最新的数据库状态和更新。
-
定期将日志和新页面转移到Amazon S3: 存储节点定期将已确认的日志记录和新的数据页面转移到Amazon S3,以实现持久化和备份。
-
定期执行垃圾回收以清理旧版本: 存储节点定期执行垃圾回收操作,清理掉数据库中的旧版本数据和不再需要的日志记录,以释放存储空间并优化性能。
-
定期验证页面上的循环冗余校验代码: 最后,存储节点定期验证数据页面上的循环冗余校验代码,以确保数据的完整性和一致性,防止数据损坏或错误。
四、log机制细节
(一)方案梗概:异步处理
将数据库建模成一个redo log流,每个log记录都有一个关联的log序列码(LSN),这个序列化是由数据库生成的单调递增的值。
Aurora维护了一致性和持久性的视图,并随着我们收到了存储层的确认,持续地演进这个视图状态。任何一个节点可能会丢失一个或多个log记录,它们通过gossip与同一个PG的其他节点通信,以补全这些丢失的log。
数据库可能会有多个隔离的事务,就像数据库正在往本地磁盘写入一样,追踪部分提交的事务并回滚它们的逻辑在数据库引擎里。然而,当数据库重启时,在数据库被允许接触其存储卷之前,存储服务会执行它自己的恢复过程,这个过程会保证数据库能够看到一个统一的存储视图。
存储服务会确认它可以保证在其之前的log都可用的最大LSN(VCL,volume complete LSN)。在存储服务恢复的过程中,每个LSN比VCL大的log记录都会被删除。数据库服务,可以通过标记log并识别CPL(consistency point LSN)来做进一步限制。我们定义小于等于VCL的最大CPL为VDL(volume durable LSN),并把所有大于VDL的log记录都删除。例如,如果我们有VCL 1007,数据库有900,1000,1100这些CPL,在这种情况下,我们需要删除1000以上的log,也就是说虽然我们有到1007的数据,但只能保证到1000数据的持久性和一致性。
完整性和持久性是不同的。CPL可以认为是存储系统事务的一些必须按顺序执行的限制。数据库和存储服务交互的过程如下:
-
每个数据库事务被分散成多个小事务(MTR,mini-transaction),这些小事务内部必须按顺序且原子地执行。
-
每个小事务是由多个(尽可能多的)相邻的log记录组成。
-
每个小事务的最后一条log记录就是一个CPL。
在恢复的过程中,数据库和存储服务交互建立起每个PG的持久视图,然后以此建立起VDL,并发送命令删除大于VDL的log记录。
(二)普通操作
写
在Aurora中,数据库和存储服务持续交互,以维护一致性和提升卷的持久化点(VDL)。每个记录都有一个唯一的有序的LSN(Log Sequence Number),由数据库生成并保证单调递增。当数据库收到足够数量的ACK来建立Quorum后,会提升当前的VDL。每个存储节点只能看到与其相关的数据页的相关log记录。这些记录包含指向上一个记录的链接,用于构建出该分片收到的最大连续LSN log记录。存储节点在gossip通讯中使用SCL(Segment Complete LSN)来发现和交换丢失的log记录。
提交
在Aurora中,事务提交是完全异步的。当客户端提交一个事务时,处理线程将提交LSN添加到等待提交事务列表中,并继续处理其他请求。事务提交完成时,最新的VDL大于等于事务的提交LSN。此时,数据库会找出完成的事务,并使用单独的线程向等待的客户端发送提交ACK。处理线程不会因为事务提交而阻塞,而是会继续处理等待中的请求。
读
与大多数数据库类似,Aurora的页是从缓冲区读取的,只有当请求的页不在缓存中时,才会进行存储层的IO请求。如果缓冲区已满,系统会从中踢出一页。在Aurora中,被踢出的页不需要回写,而是保证了踢出页的LSN必须小于等于VDL。这个协议确保了读取最新数据时不需要额外的写入操作。
复制
Aurora可以有一个写实例和最多15个读实例挂载到同一个存储卷上。主库产生的log会发送到所有的从库上,从库会按顺序消费这些log。从库会将log应用到缓存页中的相关页上,然后将其删除。从库在应用log时会遵循两个原则:a)被应用的log记录必须小于或等于VDL;b)属于同一个MTR的多个log记录必须原子地应用到从库的缓存页上,以保证一致性视图。
恢复
传统数据库在处理崩溃恢复时通常会写入redo log,并周期性地将脏页刷新到硬盘,并记录checkpoint以建立持久性点。在系统崩溃恢复过程中,会将上次checkpoint之后的redo log应用到相关的数据库页上,并通过执行undo log将未提交的事务回滚,以恢复到崩溃之前的一致性状态。
然而,Aurora将redo log应用程序从数据库中分离出来,放置在后台存储节点上并并发运行。由于它不保证数据页都是最新的,因此在启动时不需要像传统数据库那样回放checkpoint之后的redo log。即使在处理超过10w tps的流量时崩溃,Aurora也能在10秒内完成数据恢复。
数据库在崩溃后重新启动时,会与每个PG通信,读取足够的副本以确保发现成功写入的最新数据。一旦数据库为每个PG读取到足够数量的副本状态,它就能计算出VDL,然后告知存储节点删除大于VDL且小于等于当前存储层可能出现的最大LSN的log记录(VDL+1000w)。删除区间通过epoch number进行标记,并持久化到存储服务中,因此在恢复过程被中断并重新启动时不会发生多个删除区间的混淆。
五、将这些东西组合起来
数据库引擎是基于社区版MySQL/InnoDB的一个复制品,主要区别在于InnoDB读写数据的方式。Aurora的主库支持与社区版MySQL相同的事务隔离级别。Aurora的从库持续从主库获取事务的开始和提交信息,并根据这些信息支持本地读取事务的快照级别隔离。存储服务提供底层数据的统一视图,类似于社区版InnoDB将数据写入本地存储的逻辑。
Aurora之所以被称为Amazon RDS,是因为它的控制台。在每个数据库实例上都有一个名为Host Manager(HM)的代理程序,HM监控集群的健康状态并决定是否需要执行故障切换,或者替换某个实例。一个集群由一个主库和零个或多个从库组成。集群中的多个实例位于同一地理区域的不同机房,并连接到该区域的存储节点组。
存储服务部署在一个EC2虚拟机集群上,这些虚拟机跨越每个地理区域的三个机房。它们共同负责所有用户的存储卷。这些存储节点操作本地SSD盘,并与数据库实例、其他存储节点以及备份/恢复服务进行交互。它们持续将变更数据备份到S3,并在需要时进行数据恢复。存储控制台使用DynamoDB数据库服务持久化集群和存储卷的配置、卷的元数据以及备份到S3的数据的详细描述。
原始论文:
Amazon Aurora:高吞吐量云原生关系数据库的设计考虑_aws aurora iops限制-CSDN博客