Amazon新一代云端关系数据库Aurora(上)

本文由  网易云 发布。

在2017年5月芝加哥举办的世界顶级数据库会议SIGMOD/PODS上,作为全球最大的公有云服务提供商,Amazon首次系统的总结 了新一代云端关系数据库Aurora的设计实现。

Aurora是Amazon在2014 AWS re:Invent大会上推出的一款全新关系数据库,提供 商业级的服务可用性和数据可靠性,相比MySQL有5倍的性能提升,并基于RDS 提供自动化运维和管理;

经过2年时间发展,Aurora已经成长为AWS 客户增长最快的云服务之一,包括全球知名的在线游戏网站Expedia、社交游戏公司 Zynga都在使用Aurora。Aurora的推出一时引起了国内数据库研究人员的热烈讨论,大家关注的一个焦点就是Aurora是否是基于 MySQL推出的一个新的存储引擎?下面我们就根据会议发布的论文,一起走进Aurora。

                                                                                                       

                                                                                                为什么要有Aurora? 

在理解Aurora设计的初衷之前,我们首先来了解一下AWS RDS MySQL的高可用部署架构设计。与我们之前的猜测是一致的, AWS RDS是基于EC2、EBS这样的云基础设施构建的,数据库实例部署在EC2内,数据盘由一组通过镜像实现的两副本EBS提供。

为了实现跨数据中心的高可用,Primary和Replica分别部署在两个可用域内,数据同步采用类似DRBD的方式在操作系统内核通过 块设备级别的同步复制实现,所以AWS RDS的Replica平时是不能被读取的,只能用于跨可用域的故障恢复。Replica与Primary是 完全对称的,通过内核复制到replica的数据同样存放在一组镜像实现的两副本EBS中。从图中可以看到,这样的一个部署架构,数 据库发起的一次写IO需要同步复制5次,其中3次还是串行的,网络延迟对数据库的性能影响非常严重。

MySQL的InnoDB存储引擎遵从WAL协议,所有对数据页的更新都必须首先记录事务日志。此外,MySQL在事务提交时,还会生 成binlog;为了保证被修改的数据页在刷新到硬盘的过程中保证原子性,Innodb设计了double-write的机制,每个数据页会在硬 盘上写两遍。此外,MySQL还有元数据文件(frm)。在AWS RDS的高可用架构中,所有的这些日志文件、数据文件都要经过网 络的传输5次,对网络带宽也是巨大的考验。

这样的一个架构由于不涉及具体数据库内核的改动,满足了AWS发展初期可以快速支持多种类型的关系数据库的需求,但是显然随 着规模的增长,这样的架构的缺陷也越来越明显。当我们还在考虑如何优化我们的网络性能和IO路径时,AWS的注意力已经转移到 如何来减少数据在网络上的传输,这就有了后来Aurora的架构。

                                                                                                                     Aurora的系统架构

Aurora与传统关系数据库相比,最大的一个架构上的创新就是将数据和日志的管理交由底层的存储系统来完成,数据库实例只负责 向存储系统中写入redo log。由于底层存储节点挂载的是本地硬盘,日志的持久化和数据页的更新并不需要跨网络完成,所以只有 redo log需要通过网络传输。由于MySQL的redo log中包含了对某个数据页的某行记录的更新,通过redo log以及先前的数据页 可以构造出更新后的完整页面,所以Aurora选择通过redo log建立起数据库实例和底层存储系统之间的关系。

在Aurora中,数据库实例负责处理SQL查询,事务管理,缓冲池管理,锁管理,权限管理,undo管理,对用户而言,Aurora与 MySQL 5.6完全兼容。底层的存储系统负责redo log持久化,数据页的更新和垃圾日志记录的回收,同时底层存储系统会对数据进 行定期备份,上传到S3中。底层存储系统的元数据存储在Amazon DynamoDB中,基于Amazon SWF提供的工作流实现对Aurora 的自动化管理。

                                                  存储系统的设计

Amazon为Aurora实现了一个高可用、高可靠、可扩展、多租户共享的存储系统。

  多副本

为了实现数据的可靠性,Aurora在多个可用域内部署了多个数据副本,基于Quorum原则确保多个副本数据的最终一致性。 Quorum原则要求V个数据副本,一次读操作必须要读取Vr个数据副本,一次写操作必须要同时写入Vw个数据副本,Vw和Vr需要 满足:Vw + Vr > V,且 Vw > V/2。Quorum原则可以确保一份数据不能被同时读写,同时也确保了两个写操作必须串行化,后一 个写操作可以基于前一个的结果进行更新。

一般最小的Quorum要求最少3个数据副本,Vr = 2 ,Vw =2,在云环境中,就是3个可用域,每个可用域一个数据副本,一个可用 域不可用,不影响数据的读写。但是在真实的场景中,一个可用域不可用的同时,另外一个可用域很有可能也出现故障,为了解决 上述问题,Aurora采用了6副本数据,每个可用域2个数据副本,一次写操作需要4个数据副本,一次读操作需要3个数据副本。这样的设计可以实现:

1. 在一个可用域内两个数据副本同时失效,同时另外一个可用域内的一个数据副本失效,不影响整个系统的读;

2. 任意两个数据副本同时失效,不影响系统的写; 分段存储系统设计

任何一个高可用系统设计的前提假设都是在一段时间内,连续两次发生故障的概率足够的低。对于Aurora基于Quorum的多副本设 计而言,如果一个AZ的副本失效,在修复过程中,同时再有一个副本失效,则整个系统将不可写;如果在AZ+1的副本失效的同 时,又有一个副本再失效,则系统将不可读。我们没有办法去阻止连续故障的发生,但是我们可以通过缩短前一次故障的修复时 间,从而降低连续两次故障出现的概率,这就是分段存储设计的思想来源。

Aurora将一个数据库实例的数据卷划分为10G固定大小的存储单元,这样可以确保每个单元数据可以快速的恢复。每个存储单元有 6个副本,每个可用域内2个副本,6个副本组成了一个PG(Protection Groups)。物理上,由一组挂载本地SSD的EC2云主机充当存储节点,每个存储节点上分布了很多存储单元。一组PG构成了一个Aurora实例的数据卷,通过分配更多的PG,可以线性扩展数据卷的容量,最大支持64TB。

Segment是存储系统故障恢复的最小单元,之所以选择10G大小,如果太小,可能造成元数据过于庞大,如果太大,又可能造成单 个Segment的修复时间过长,经过Aurora测试,10G大小的Segment数据恢复时间在10Gbps的网络传输速度下,只需要10秒时 间,这样就确保了存储系统可以在较短的时间内完成故障修复。Segment的元数据由一个DynamoDB来负责存储。

基于Segment和Quorum的设计,Aurora可以通过人工标记一些Segment下线,来完成数据迁移,对于热点均衡、存储节点操作系统升级更新都非常有帮助。

事务日志的写入

在MySQL数据库InnoDB存储引擎中,所有数据记录都存储在16K大小的数据页中,所有对行记录的修改操作,都首先必须对数据 页进行加锁,然后在内存中完成对数据页行记录修改操作,同时生成redo log和undo log,在事务提交时,确保修改操作对应的 redo log持久化到硬盘中,最终被更新的数据页通过异步方式刷新到硬盘中。redo log确保了数据页更新的持久化,每个redo log record都有一个唯一标识,LSN(log sequence number),标识该记录在redo log文件中的相对位置。为了确保一个更新操作对 多个数据页,或者一个数据页内部多条记录的修改原子性,一个事务会被切分成多个Mini-transaction(MTR),MTR是MySQL内部 最小执行单元,在Aurora中,MTR的最后一个redo log record对应的LSN,称为CPL(Consistency Point LSN),是redo log中一 致点。

在每个MTR提交时,会将MTR生成的redo log 刷新到公共的log buffer中,在MySQL内部,一般log buffer空间满,或者wait 超 时,再或者事务提交时,log buffer中的redo log会被刷新到硬盘中。在Aurora中,每个PG都保存了一部分数据页,每个redo log record在被刷新到硬盘之前,会按照redo log record更新的数据页所在的PG,划分成多个batch,然后将batch发送到PG涉及的6 个存储节点,只有等到6个节点中的4个的ACK,这个batch内的redo log record才算写入成功。最新写入成功的MTR的最后一个记 录对应的LSN,我们成为VDL( Volume Durable LSN ),这个点之前的MTR对数据页的修改,都相当于已经持久化到存储系统 中。

存储节点对事务日志的处理

每个存储节点在接收到redo log record batch之后,首先会将其加入到一个内存队列中,然后将redo log record持久化到硬盘 后,返回ACK 给写入实例(Primary)。接下来,由于每个存储节点可能保存的batch不完整(由于Quorum 4/6机制),所以需要 通过与同一个PG下的其他存储节点进行询问,索要缺失的batch。

Aurora中存储节点对数据的管理采用了log-structured  storage方式,每个PG的redo log record首先按照page进行归类,同一个page的redo log record在写入时,直接append在该页面之后,页面中的已有记录会有一个链接指针,指向最新的记录版本。

除此之外,每个PG内的每个segment上的redo log record都包含一个指针,指向他的前一个log record,通过这个指针,我们很 容易判断每个Segment上的log record完整性,如果缺失,则可已通过与其他的存储节点进行询问,补齐缺失redo log record。

数据页的合并和旧版本记录的回收

类似HBase、Cassandra采用LSM Tree的NoSQL系统,Aurora也需要有一个垃圾回收和数据页合并的过程。在MySQL中,脏页的 刷新是通过Check point的机制来完成的,redo log的空间是有限的,必须要将redo log涉及的数据页持久化到硬盘中,redo log 空间才能释放,新的redo log 才能写入,所以MySQL的脏页刷新与客户端的事务提交是密切相关的,如果脏页刷新过慢,可能导 致系统必须等待脏页刷新,事务无法提交。另外,Check point机制也决定了脏页是否刷新是根据整个redo log大小来决定的,即 使一个页面只是偶尔一次更新,整个数据页在check point推进过程中,都必须重新写入,同时为了确保一个数据页的完整性, MySQL还有double write机制,页面被写两次,代价非常昂贵,显然是不合理的。

Aurora的设计更加巧妙,因为数据是有热点的,不同的数据页的更新频率是不一样的,根据每个Page待更新的redo log record数 量,来决定page是否进行合并。

纵观Aurora的设计,一个核心的设计原则就是将数据页看成是日志的一个缓存,通过牺牲一定的读,换取了很好的写性能,这是所 有基于log-structured system 共性。

对数据库的操作

写操作

在Aurora中,同时会存在很多写事务,这些事务会产生大量的redo log record,因为所有的事务在提交时,都必须确保该事务产 生的redo log已经写入到底层至少4个存储节点中,考虑到网络和存储节点的IO性能,Aurora中会对写事务进行限制,如果当前分 配的LSN大于VDL加上LAL(LSN Allocation limit),则不再分配新的LSN。

提交事务

在MySQL中,虽然在事务执行过程中,各个事务是并发执行的,但是在提交时,都是串行的,虽然MySQL 5.6推出了Group Commit,可以批量提交,但是在前一个group提交过程中,其他线程也不得不sleep等待唤醒,这样无疑造成了资源浪费。

在Aurora中,事务的提交完全是异步的,每个事务执行完成以后,提交的过程只是将该事务加入到一个内部维护的列表中,然后该 线程就被释放了。当VDL大于该列表中等待提交事务commit对应的lsn时,则由一个线程,向各个客户端发送事务提交确认。

读操作

在MySQL中,所有的读请求都是首先读buffer cache的,只有当buffer cache未命中的情况下,才会读取硬盘。Buffer cache的空 间是有限的,在MySQL中,通过LRU的机制,会将一些长时间没有被访问的数据页占用的buffer空间释放。如果这些页面中包含脏 页,则必须要等到脏页刷新到硬盘以后才能释放。这样就确保了下次读取该数据,一定能够读取到最新的版本。

在Aurora中,并不存在脏页刷新的过程,所有数据页的合并都是由底层存储节点来完成的。所以与MySQL实例脏页刷新向上看不 同,Aurora需要向下看,通过将Page LSN大于VDL的数据页释放,可以确保,所有Buffer中Page涉及的更新都已经持久化到硬盘 中,同时在cache未命中的情况下,可以读取到截止到当前VDL的最新版本的数据页。

所以在Aurora中,Buffer Cache更像是一个纯粹的Cache。

在Aurora日常读取中,并不需要达到3/6的Quorum,因为有VDL的存在,我们可以根据读请求发起时的VDL建立一个read point,找到包含小于VDL的所有完整log record的存储节点,直接进行读取。通过同一个PG内部的Segment之间的相互询问,可以建立一个PG的最小的read point,该read point 以下的log record实际上才可以被回收合并。

只读节点

在Aurora中,最多可以为一个writer实例创建15个只读实例,这15个只读实例挂载的是相同的存储卷,只读实例不会额外增加存储 的开销。为了减少延迟,Writer实例会将写入到存储系统的redo log日志同样发送给只读实例,只读实例接收到redo log日志后, 如果要更新的数据页命中了buffer cache,直接在buffer cache中进行更新,但是需要注意的是,如果是同一个mini-transaction 的redo log record,必须确保mini-transaction的原子性。如果buffer cache没有命中,则该记录被丢弃。另外,如果被执行的 log record的lsn大于当前的VDL,也不会被执行,直接丢弃。

这样的设计确保Aurora只读实例相较于Writer实例延迟不超过20ms。

本文未结束,敬请期待下篇。

了解 网易云 :
网易云官网:https://www.163yun.com/
新用户大礼包:https://www.163yun.com/gift
网易云社区:https://sq.163yun.com/

猜你喜欢

转载自www.cnblogs.com/163yun/p/8918695.html