前言
Aurora特点:1.计算存储分离,redo日志下移到存储层。2.“日志即数据库”即:redo日志包含所有数据,因为它可回放。3.基于quorum6副本,写4/6,读3/6。4.分片存储来降低故障修复时间(MTTR)——10G。
一、Aurora简介
1.Aurora是一个AWS上的分布式OLTP(在线事务处理)关系数据库,高性能、高可靠。而本文的一个重要观点是:性能瓶颈从CPU和存储变成了网络,因此将计算与存储分离。
(1)数据库实例(计算层)和存储层松耦合。
(2)数据库实例(计算层)仍包含大部分核心功能,如查询、事务、锁、缓存、访问接口、undo日志等。
(3)redo日志相关功能下移到存储,如日志处理、故障恢复、备份还原等。
2. 这样的优势有:
(1)底层存储层本身是分布式存储服务,可应对故障
(2)数据库实例(计算层)只往下面存储层写redo日志,降低两层之间的网络压力
(3)部分核心功能(故障恢复、备份还原等)移到存储层,可后台异步执行,不影响前台任务
3.论文主要包括三大块:
(1)如何在云上实现持久化,基于quorum模型,使存储层能弹性容错(第三部分)
(2)如何将计算层下层的日志处理功能转移到存储层上(第四部分)
(3)如何在分布式存储中消除多阶段同步,并避免开销巨大的崩溃恢复和检查点操作(第五部分)
4.论文内容是关于如何让一个高性能高可靠的数据库作为云基础架构。
5.在处理事务的速度上,Aurora宣称比其他数据库快35倍。
6.Amazon首先使用的是自己的通用存储,但是后来发现性能不好,然后就构建了完全是应用定制(Application-Specific)的存储,并且几乎是抛弃了通用存储。
二、Aurora诞生背景
1.EC2(Elastic Cloud 2)
(1)最早的时候,Amazon提供的云产品是EC2,它可以帮助用户在Amazon的机房里和Amazon的硬件上创建类似网站的应用。在一个服务器上,有一个VMM(Virtual Machine Monitor),还有一些EC2实例,其中每一个实例都出租给不同的云客户。
(2)最早的时候EC2用的都是服务器的本地硬盘,每个EC2实例会分到本地盘的一小部分。
(3)对于无状态的web服务器,EC2实例是很完美的。但是对于数据库,当服务器宕机时,因为数据存储在EC2实例所在的服务器本地硬盘,就会造成数据丢失。
虽然Amazon会采用S3块存储服务对数据库快照存储,并基于快照来实现故障恢复,但是这种定期的快照意味着你可能会损失两次快照之间的数据。
2.EBS(Elastic Block Store)
(1)EBS是一个容错的且支持持久化存储的服务。
从EC2实例来看,EBS就是一个硬盘。
在实现上,EBS底层是一对互为副本的存储服务器,这两个EBS服务器会使用上一篇论文中的Chain Replication进行复制。(写请求第一个到第二个,读请求从第二个EBS服务器读)
所以,现在你运行了一个数据库,相应的EC2实例将一个EBS volume挂载成自己的硬盘。当数据库执行写磁盘操作时,数据会通过网络送到EBS服务器。
(2)EBS运行数据库时,传输数据会产生大量的网络流量。
Aurora花费了大量的精力来降低数据库产生的网络负载。
(3)EBS的容错性不是很好。出于性能的考虑,Amazon总是将EBS volume的两个副本存放在同一个数据中心/AZ(Availability Zone)。
注:在Amazon的术语中,一个AZ就是一个数据中心。
三、可伸缩的持久性
本节介绍quorum模型,并介绍存储分段。
Aurora将两者结合,实现持久性、可用性、少抖动,并解决大规模存储的相关问题。
1.复制及其相关的错误
大规模下,错误经常发生,所以需要复制以容错。
(1)Auroma使用基于quorum的复制协议:
- 记副本个数为V个,读需要Vr个副本的投票,写需要Vw个副本的投票
- 首先要保证必有一个副本是最新的,即Vr+Vw>V
- 写需要有多数的同意,防止写冲突,即Vw>V/2
客户端如何知道从R个服务器得到的R个结果中,哪一个是正确的呢?
在Quorum系统中使用的是版本号(Version)。所以,每一次执行写请求,你需要将新的数值与一个增加的版本号绑定。之后,客户端发送读请求,从Read Quorum得到了一些回复,客户端可以直接使用其中的最高版本号的数值。
(2)常用配置下,Aurora通常将副本分布到3个AZ(可用区),每个AZ有2个副本,即V=6,然后设置Vr=3,Vw=4。这样它可以: - 一个AZ整个故障,依旧不影响写服务(AZ)
- 一个AZ整个故障,另一个AZ一个副本故障,依旧不影响读服务,且不丢失数据(AZ+1)
2. 分片存储
(1)以上面的AZ+1故障为例,我们需要保证两个完整AZ故障的概率足够低,才能保证quorum有效。
而故障频率(MTTF)不可知,因此需要降低故障修复时间(MTTR),从而降低2个AZ同时故障的概率。
解决方式是分片存储:
- 副本根据固定大小分片(如10GB)
- 每个分片被6路复制到3个AZ上,每个AZ有2个,这6个分片组成一个保护组(PG)
例:如果一个数据库需要20GB的数据,那么这个数据库会使用2个PG(Protection Group),其中一半的10GB数据在一个PG中,包含了6个存储服务器作为副本,另一半的10GB数据存储在另一个PG中,这个PG可能包含了不同的6个存储服务器作为副本。
你可以将磁盘中的data page分割到多个独立的PG中,比如说奇数号的page存在PG1,偶数号的page存在PG2。如果可以根据data page做sharding,那是极好的。
Aurora存储由若干个PGs构成,而PGs由EC2+SSD存储节点构成。
分片后,每个分片就是一个故障单位,恢复时只要恢复指定分片即可,速度会非常快。这降低了MTTR,从而提高了可用性。
(2)出现故障时重建副本策略
Aurora实际使用的策略是,对于一个特定的存储服务器,它存储了许多Protection Group对应的10GB的数据块。对于Protection Group A,它的其他副本是5个服务器。
这个存储服务器还为Protection Group B保存了数据,但是B的其他副本存在于与A没有交集的其他5个服务器中。类似的,对于所有的Protection Group对应的数据块,都会有类似的副本。
这种模式下,如果一个存储服务器挂了,假设上面有100个数据块,对于每一个数据块,我们会从Protection Group中挑选一个副本,作为数据拷贝的源。这样,对于100个数据块,相当于有了100个数据拷贝的源。之后,就可以并行的通过网络将100个数据块从100个源拷贝到100个目的。
这就是Aurora使用的副本恢复策略,它意味着,如果一个服务器挂了,它可以并行的,快速的在数百台服务器上恢复。
3.弹性容错的运维优势
Aurora能对长短故障都有弹性,这基于quorum协议和分段存储。
因此,系统可灵活应对故障,且运维方便,如:
- I/O压力过大:可剔除某个分段,通过迁移热点数据到冷节点上,quorum机制可很快地修复
- 系统更新导致的不可用:可临时将节点剔除,待完成后再加入到系统
- 软件更新:同系统更新
以上操作,都是分片粒度滚动进行,对外完全透明。
四、日志即数据库
“日志即数据库”即:redo日志包含所有数据,因为它可回放。
即数据库就是redo日志数据流
本节介绍Aurora的计算存储分离架构是如何体现这个思想的。
1.写放大问题
(1)关系型数据库(Amazon RDS)
在MySQL基础上,结合Amazon自己的基础设施,Amazon为其云用户开发了改进版的数据库,叫做RDS(Relational Database Service)。论文中的Figure 2基本上是对RDS的描述。
RDS是第一次尝试将数据库在多个AZ之间做复制。
对于RDS来说,有且仅有一个EC2实例作为数据库。这个数据库将它的data page和WAL Log存储在EBS,而不是对应服务器的本地硬盘。当数据库执行了写Log或者写page操作时,这些写请求实际上通过网络发送到了EBS服务器。所有这些服务器都在一个AZ中。
每一次数据库软件执行一个写操作,Amazon会自动的,对数据库无感知的,将写操作拷贝发送到另一个数据中心的AZ中的另一个EC2实例,AZ2的副数据库会将这些写操作拷贝AZ2对应的EBS服务器。
在RDS架构中,即在下图Figure 2中,每一次写操作,例如数据库追加日志或者写磁盘的page,数据除了发送给AZ1的两个EBS副本之外,还需要通过网络发送到位于AZ2的副数据库。副数据库接下来会将数据再发送给AZ2的两个独立的EBS副本。之后,AZ2的副数据库会将写入成功的回复返回给AZ1的主数据库,主数据库看到这个回复之后,才会认为写操作完成了。
RDS每一次调用写操作,需要等待4个服务器更新完成,并且等待数据在链路上传输。
如论文中表1的性能描述
这种Mirrored MySQL比Aurora慢得多的原因是,它通过网络传输了大量的数据。
(2)mirrored MySQL总结
在传统关系数据库跨数据中心的复制场景下,会产生非常多的磁盘和网络I/O,性能糟糕。
下图以MySQL为例:
- AZ1和AZ2各部署MySQL实例,进行同步镜像复制
- 底层存储使用EBS,每个EBS还有一个自己的镜像
- 另部署S3执行redo日志和bin log的归档,以实现基于特定时间点的恢复
那么一个写操作需要5步,因此性能很差。以分布式观点,它是一个4/4的write quorum(4个EBS镜像)。
2. 将Redo日志处理下放到存储层
(1)Aurora架构
在替代EBS的位置,有6个数据的副本,位于3个AZ,每个AZ有2个副本。所以现在有了超级容错性。但是,为什么Aurora不是更慢了,之前Mirrored MySQL中才有4个副本。,这里通过网络传递的数据只有Log条目,这才是Aurora成功的关键。这是Aurora的第一个特点,只发送Log条目。
这里的存储系统不再是通用(General-Purpose)存储,这是一个可以理解MySQL Log条目的存储系统。
EBS是一个非常通用的存储系统,它模拟了磁盘,只需要支持读写数据块。EBS不理解除了数据块以外的其他任何事物。而这里的存储系统理解使用它的数据库的Log。
所以这里,Aurora将通用的存储去掉了,取而代之的是一个应用定制的(Application-Specific)存储系统。
Aurora并不需要6个副本都确认了写入才能继续执行操作。相应的,只要Quorum形成了,也就是任意4个副本确认写入了,数据库就可以继续执行操作。所以这里的Quorum是Aurora使用的另一个聪明的方法。
(2)Aurora架构总结
传统数据库中:
- 写操作不仅修改数据页,修改后同步产生redo日志项
- 提交事务时,将redo日志刷屏成功才能返回
Aurora中:
- 写操作只产生redo日志项
- 存储层收到redo日志项,回放日志得到新数据页
- 避免从头回放,存储层定期物化数据页版本
架构如下所示:
- 三个AZ中,AZ1是主,其它AZ是从
- (计算层)实例和存储层只传递redo日志和元数据
- 写时主实例向6个存储节点发送redo日志,需要4个应答才认为日志持久化了
- 回放日志可在后台运行,若读操作得到的是旧数据页,则会触发日志回放
- 存储节点定期物化数据页,恢复时只需回放很少的日志,非常快速
(3)Aurora读写存储服务器
Aurora中的写请求并不是像一个经典的Quorum系统一样直接更新数据。对于Aurora来说,它的写请求从来不会覆盖任何数据,它的写请求只会在当前Log中追加条目(Append Entries)。所以,Aurora使用Quorum只是在数据库执行事务并发出新的Log记录时,确保Log记录至少出现在4个存储服务器上,之后才能提交事务。
这里的存储服务器并没有从数据库服务器获得到新的data page,它们得到的只是用来描述data page更新的Log条目。
但是存储服务器内存最终存储的还是数据库服务器磁盘中的page。
在存储服务器的内存中,会有自身磁盘中page的cache,例如page1(P1),page2(P2),这些page其实就是数据库服务器对应磁盘的page。
当一个新的写请求到达时,这个写请求只是一个Log条目,Log条目中的内容需要应用到相关的page中。可以等到数据库服务器或者恢复软件想要查看那个page时才执行。
存储服务器会在内存中缓存一个旧版本的page和一系列来自于数据库服务器有关修改这个page的Log条目。这里的Log列表从上次page更新过之后开始(相当于page是snapshot,snapshot后面再有一系列记录更新的Log)。
数据库服务器发出一个读请求。请求发送到存储服务器,会要求存储服务器返回当前最新的page数据。在这个时候,存储服务器才会将Log条目中的新数据更新到page,并将page写入到自己的磁盘中,之后再将更新了的page返回给数据库服务器。同时,存储服务器在自身cache中会删除page对应的Log列表,并更新cache中的page。
数据库服务器写入的是Log条目,但是读取的是page。这也是与Quorum系统不一样的地方。Quorum系统通常读写的数据都是相同的。
数据库服务器执行了Quorum Write,但是却没有执行Quorum Read。因为它知道哪些存储服务器有最新的数据,然后可以直接从其中一个读取数据。这样的代价小得多,因为这里只读了一个副本,而不用读取Quorum数量的副本。(Log编号判断)
3. 存储服务设计的关键点
Aurora设计的关键原则是减少前台响应时间,所以尽可能将操作在后台异步执行,并根据压力自适应分配资源。
下面是一个写操作的具体流程:
①数据库实例(计算层)收到写请求,生成redo日志项,发送给存储节点,存储节点将日志项添加到内存队列中
②存储节点本地持久化redo日志项,并响应数据库实例
③整理日志,检查日志缺失
④和其它存储节点gossip,填补日志缺失
⑤回访redo日志,生成新数据页
⑥周期生成快照,备份数据页和日志到S3系统
⑦周期回收过期版本的数据页/快照
⑧周期对数据页进行校验
整个过程中,只有1和2是前台串行的(会影响延迟),其它都是后台操作。
五、日志延伸:一致性
本节介绍如何不用2PC提交协议下,如何生成redo日志,以保证持久化状态、运行时状态和副本状态的一致性。
具体如下:
- 在崩溃恢复时,如何避免昂贵的回放开销
- 解释常规操作,并解释如何维护运行时和副本的一致性
- 提供恢复过程的具体细节
1. 异步日志处理
(1)首先介绍一些概念:
- LSN(Log Sequence Number):每个redo日志全局唯一且单调递增的序列号
- VCL(Volume Complete LSN):表示LSN在VCL之前,收到的日志在本存储节点是完整的
故障恢复时,VCL之后的日志都要截断 - CPLs(Consistency Point LSNs):事务可分成多个最小原子的mini-transaction(MTR),每个都有对应的最后一条日志LSN,这就是CPL;一个事务有多个CPL,所以叫做CPLs
- VDL(Volume Durable LSN): 最大已持久化的LSN,满足
①VDL ≤ VCL
②VDL是CPLs中最大的一个
从上面可知,日志完整性和日志持久化是不一样的。
易知VDL表示了数据库处于持久化一致的位点,所以在故障恢复时:数据库以PG为单位,确认VDL,截断大于VDL的日志。
(VCL之前日志完成,无空洞。CPL是微事务的结束点,即提交处。VDL是所有CPL中最大的一个,即最后一个微事务的结束点)
(2)Aurora有多个只读数据库,这些数据库可以从后端存储服务器读取数据。所以,Figure 3中描述了,除了主数据库用来处理写请求,同时也有一组只读数据库。论文中宣称可以支持最多15个只读数据库。如果有大量的读请求,读请求可以分担到这些只读数据库上。
当客户端向只读数据库发送读请求,只读数据库需要弄清楚它需要哪些data page来处理这个读请求,之后直接从存储服务器读取这些data page,并不需要主数据库的介入。
只读数据库也需要更新自身的缓存,所以,Aurora的主数据库也会将它的Log的拷贝发送给每一个只读数据库。这就是你从论文中Figure 3看到的蓝色矩形中间的那些横线。
主数据库会向这些只读数据库发送所有的Log条目,只读数据库用这些Log来更新它们缓存的page数据,进而获得数据库中最新的事务处理结果。
有一些问题:①我们不想要这个只读数据库看到未commit的事务。②数据库背后的B-Tree结构非常复杂,可能会定期触发rebalance。只有在rebalance结束了才可以从B-Tree读取数据。但是只读数据库直接从存储服务器读取数据库的page,它可能会看到在rebalance过程中的B-Tree。
这就涉及到了上面讨论的微事务(Mini-Transaction)和VDL/VCL。
所以当一个只读数据库需要向存储服务器查看一个data page时,存储服务器会小心的,要么展示微事务之前的状态,要么展示微事务之后的状态,但是绝不会展示中间状态。
2. 标准操作
(1) 写
数据库实例向存储节点传递redo日志,收到足够的票数后,标记事务提交,VDL增加,从而进入新的一致状态。
不过由于系统并发执行很多事务,所以要避免VDL追不上LSN,有:
- 定义并设置LAL,即LSN与VDL差值的上限,避免存储层成为瓶颈
而由于底层存储是分片的,每个PG/每个分片只能见到部分日志,为保证PG/分片上日志的完整性,有: - 每条日志有当前PG前一条日志的指针
- 定义SCL,表示LSN在SCL之前,收到的日志在本PG是完整的,存储节点通过gossip填补缺失的日志,以推进SCL
(2) 提交
提交是异步的:
- 每个事务包含commit LSN,当VDL大于事务的commit LSN时,表示事务redo日志都已经持久化,则向客户端回复事务已成功执行
- 处理回复是由单独线程执行的,因此整个提交流程不会阻塞
异步提交极大提高了系统的吞吐。
(3)读
读先从缓冲池获取,若缓冲池满才读盘,此时会挑选并淘汰数据页。
但是对于淘汰的数据页,Aurora直接丢弃,不会刷盘。所以需要保证被淘汰的数据页page LSN要不超过VDL,这就需要两个约束:
- 数据页上的修改都已经持久化了
- 缓存未命中时,通过数据页和VDL总能得到最新的数据页版本
对于读操作,正常情况下,不需要quorum:选定一个存储节点进行读取,它需要满足有最新VDL的数据。
(4)复制
写副本实例(主实例)可与至多15个读副本实例(从实例)共享一套分布式存储,因此增加读副本(从实例)不会消耗I/O资源,即零成本。
从上文可知,写副本会向读副本发送日志,读副本会回放日志,它需要满足,若不满足则忽略日志项:
- 回放日志的LSN不超过VDL
- 回放日志要以MTR为单位,确保副本能看到一致性视图
3. 故障恢复
(1)MySql的实现
因为Aurora使用的是与MySQL类似的机制实现,但在Mysql实现方式的基础上,进行了一些加速操作。
对于Aurora来说,最重要的是事务(Transaction)和故障可恢复(Crash Recovery)。
事务是通过对涉及到的每一份数据加锁来实现:
具体实现如下:
硬盘存储了数据的记录或以B-Tree方式构建的索引。所以有一些data page用来存放数据库的数据,每一个data page通常会存储大量的记录,而X和Y的记录是page中的一些bit位。
在硬盘中,除了有数据之外,还有一个预写式日志(Write-Ahead Log,简称为WAL)。预写式日志对于系统的容错性至关重要。
在服务器内部,有数据库软件DB,通常数据库会对最近从磁盘读取的page有缓存。
当你在执行一个事务内的各个操作时,例如执行 X=X+10 的操作时,数据库会从硬盘中读取持有X的记录,给数据加10。但是在事务提交之前,数据的修改还只在本地的缓存中,并没有写入到硬盘。提交之前我们还不想向硬盘写入数据,因为这样可能会暴露一个不完整的事务。
为了让数据库在故障恢复之后,还能够提供同样的数据,在允许数据库软件修改硬盘中真实的data page之前,数据库软件需要先在WAL中添加Log条目来描述事务。所以在提交事务之前,数据库需要先在WAL中写入完整的Log条目,来描述所有有关数据库的修改,并且这些Log是写入磁盘的。
a.第一条表明,作为事务的一部分,我要修改X,它的旧数据是500,我要将它改成510。
b.第二条表明,我要修改Y,它的旧数据是750,我要将它改成740。
c.第三条记录是一个Commit日志,表明事务的结束。
前两条Log记录会打上事务的ID作为标签,这样在故障恢复的时候,可以根据第三条commit日志找到对应的Log记录,进而知道哪些操作是已提交事务的,哪些是未完成事务的。
数据库写磁盘是一个lazy操作,它会对更新进行累积。当数据库重启时,恢复软件会扫描WAL日志,发现对应事务的Log,并发现事务的commit记录,那么恢复软件会将新的数值写入到磁盘中。这被称为redo,它会重新执行事务中的写操作。
(总结:硬盘中有操作日志的记录WAL,每一条有事务id通过是否有commit判断该事务是否提交。修改到磁盘前先写入WAL,实现故障可恢复事务。)
(2)总结
ARIES(Algorithm for Recovery and Isolation Exploiting Semantics,,这篇文章应该是WAL里面最有名的一篇了,发表在1992年,现在很多数据库WAL的设计上都有它的影子。
常规数据库采用基于ARIES协议进行故障恢复:
- 周期性做checkpoint
- 从上一个checkpoint回放redo日志
- 根据undo日志回滚未提交的事务
这需要检查点频率和故障恢复时间做权衡,但Aurora不需要。
Aurora虽然也采用类似的方法,但是基于计算存储分离以及分片的设计: - 存储层部分失效时,能快速恢复
- 数据库实例宕机后:①故障恢复只需要通过与存储节点read quorum,即可读到最新数据,并得到VDL以截断后面的日志。 ②由于LSN与VDL有差值限制,undo操作不会很多,且可在后台进行。
六、集成
整体在云上集成的架构如下所示:
- 应用层:部署应用程序,与数据库实例交互
- 数据库实例层:部署数据库实例(Aurora MySQL)
① 数据库实例包含一个主实例(读写)和多个从实例(只读),位于同一个物理区域,跨AZ部署
② 使用RDS管理元数据,每个数据库实例部署一个RDS HM,监控集群健康度,确定实例是否要切换或重建等 - 存储层:部署存储节点
① 存储节点尾与同一物理区域,跨AZ部署(如至少跨3个AZ)
② 其中部分节点和S3相连,后台备份数据,必要时还原数据
③ 使用Amazon DynamoDB持久化存储配置、元数据信息、S3备份信息
④ 所有关键操作都会被监控,若性能和可用性不达标,则会触发警报
七、总结
本文的主要贡献点在于:
- 重提“数据库即日志”的观念
- 基于quorum机制,和下放redo日志处理,使用“计算和存储分离”的架构,减少I/O开销,提高吞吐,可用性增加,且能保证一致性
- 提出分片存储,降低存储层恢复的时间,提高可用性
- 大部分关键操作异步化、后台化,极大降低前台任务的延迟