Amazon Aurora(SIGMOD 2018)云数据库关键技术解读

说在前面

本文主要介绍 《Amazon Aurora: On Avoiding Distributed Consensus for I/Os, Commits, and Membership Changes 》(SIGMOD 2018)这篇论文,这篇论文主要是关于Amazon Aurora分布式数据库中一些关键技术的实现思路。
若想直到更多关于Aurora基础架构的,可参考 《Amazon Aurora: Design Considerations for High Throughput Cloud Native Relational Databases》(SIGMOD 2017)这篇论文。

大致介绍

Aurora是亚马逊的分布式云数据库,它与传统的关系型数据库不太一样,它的设计是专门针对分布式环境的,不像MySQL一类也可适用于(或说更适用于)单机状态。

Aurora最大的特点是存储节点和计算节点分离(或说database instance和storage分离),真正的日志式数据库。Aurora采用的是面向服务的架构,计算节点(也称作数据库实例database instance)专门负责提供服务,而存储节点则将数据的存储功能抽象化,方便扩展调整。存储节点仅负责数据落盘以及redo log落盘,计算节点则负责其它功能,如分布式事务、锁管理,读写请求等。将存储分离也使得数据存储、备份更加灵活可变,但同时也增加了容灾和同步的难度,本文将详细介绍这些机制。

优化写效率

需要提前说明的是,写的时候计算节点,也就是数据库实例,仅仅传redo log到存储节点(所以说它是日志式数据库)。而存储节点则会将redo log落盘,同时会周期性的刷data page到磁盘中。

Aurora采用的是quorum模型,即在多份(同样的)数据上进行读写。

这里大致介绍下Aurora的quorum机制(更多信息可搜quorum一致性),Aurora每份数据都有6个副本(也称为节点或者分区segment,这是数据组织的最小单位),分别存储在3个AZ(Avalible Zone,可以理解为3台不同的机器)上。针对6个副本(segment)组成的protection group,每次写的时候需要随机写4个分区,这样能保证其中至少有一个分区包含着上次写的最新数据;而读的时候则要随机读3个分区,这样能保证读到最新的数据。

Amazon Aurora(SIGMOD 2018)云数据库关键技术解读_第1张图片

当然,这种6个副本中随机挑4个写会出现以下的情况:
Amazon Aurora(SIGMOD 2018)云数据库关键技术解读_第2张图片
日志序列中会存在“缝隙”,这些缝隙会通过gossip协议从其它同组的分区获取日志记录来填补。当一个分区收到新的LSN时,就会推进SCL(segment complete LSN),它代表了当前已持久化的最新的LSN,可用于前面说的gossip协议计算hot log边界。
这样设计的原因是因为在这种分布式环境中,写操作很可能会因为各种各样的原因而丢失,所以存储节点必须要能高效妥善地处理上述的“缝隙”。

但这样还会有另一个问题:每个节点都只能看到自己所收到过的日志记录。所以为了使每个节点能看到完整的日志,所以每条日志记录还会记录它的前一条日志序列号LSN:准确地说会记录3条previous LSN——全局的前一条LSN、当前分区的前一条LSN和针对当前LSN所属同一数据块做修改的前一条LSN。

实际上为了保证这种环境下依旧能获得完整的日志链,Aurora还维护了Protection Group Complete LSN(PGCL)和Volume Complete LSN(VCL)。只要维护好这两个LSN再加上SCL,便足以表示每个分区的瞬时状态,也就不需要其它的一致性协议了。

为了演示写的高效率,下图为一个写请求的处理过程:
Amazon Aurora(SIGMOD 2018)云数据库关键技术解读_第3张图片
上图中仅有1、2步是串行的需要等待,剩下的步骤都是并行的,可由存储节点(storage node)后续自己执行。存储节点只需要将数据库实例(上图的Primary Instance)传过来的redo log保存下来便可回复确认消息ACK了。而对于数据库实例来说,只需要收到4个存储节点的确认消息便认为写成功了。

而具体什么时候通知用户写操作已完成呢?会有个线程专门扫描写请求队列,这些请求都带有SCN(System Commit Number),只要某条请求的SCN比全局已完成LSN(VCL)小,便说明该条请求已完成,此时便可返回用户提示写成功。

Aurora在发送redo log上还取了个巧。对于传统数据库来说,redo log由于都是本地生成本地存储,所以可以将短时间内的一批redo log“打包”落盘,但如果对这种redo log需要网络传输的场景来说,“打包”将会大大增加写响应时间。针对这个问题,Aurora在提交第一个异步网络操作时会继续接受新的网路操作并填充到缓冲区直到网络操作真正开始执行,这样子继降低了延迟,也一定程度上实现了打包处理redo log。

优化读效率

Aurora也支持类似MySQL的MVCC(multi-version concurrency control),同个事务中每次读都保证读到的数据版本是一致的,即使后续有新的事务对数据进行了修改。思路跟MySQL一样,通过读取最新的数据,并应用对应的undo log来回退到指定的版本。同样的,直到所有读某一版本的事务都结束时,与它相关的undo log才会被清除。

在一般的quorum系统,由于需要读多份数据,导致读效率是个大问题,也很容易导致网络拥塞。这也是许多分布式框架不愿意采用quorum协议的原因。

Aurora实际上也是不进行quorum read的,因为计算节点,也就是数据库实例实际上是直到哪个节点有最新数据的,而且计算节点在请求时也会记录请求时间,它会自动请求时间最短的节点,不过若请求时间过长时,它也会再请求其它节点并接收处理第一个收到的消息——这样大大减少了节点切换等导致节点不可用的情况带来的延迟。

关于读,Aurora在集群建设上也与传统数据库不一样,它不需要像MySQL或者Redis一样的主从复制。在Aurora中,写副本实例和至多15个读副本实例共享一套分布式存储服务,因此增加读副本实例并不会消耗更多的磁盘IO写资源和磁盘空间。这也是共享存储的优势,零存储成本增加新的读副本。读副本和写副本实例间通过日志同步。写副本实例往存储节点发送日志的同时向读副本发送日志,读副本按日志顺序回放, 如果回放日志时,对应数据页不在缓冲池中,则直接丢弃。这整个过程是异步的,所以并不会影响用户的请求时间。

Amazon Aurora(SIGMOD 2018)云数据库关键技术解读_第4张图片
而且为了保证原子性,写实例是以mini-transaction chunk为单位将日志传输到读实例的。mini-transaction(MTR)包含了对一个或多个数据块的修改,若干MTR构成真正的transaction。

故障恢复

先大致说明Aurora的容灾策略:对于一个protection group来说,它能在挂掉2个节点后依旧提供读写服务;再极端点挂掉3个节点时依旧能提供读服务(read quorum是3/6,write quorum是4/6)。注意按照每个节点存储10G数据的量来估算,一次恢复大概为10s,也就是上述情况潜台词都是在10s内同时挂掉的情况,而且3个AZ往往至少有一个是异地的,所以算是比较极端的情况了。

简单来说,全局一致的已完成LSN点VCL是恢复时的定标位置,所有大于VCL的LSN都要被放弃掉。
Amazon Aurora(SIGMOD 2018)云数据库关键技术解读_第5张图片
Aurora每一个Volume都要保证至少能满足read quorum(即至少有3个节点可以提供读服务),所以容灾恢复回退到VCL点后,也能保证与其它同组节点同步一致性点(consistency point)后能重新得到新的PGCL点和VCL点(说通俗点也就是能重新满足数据一致性)。

若真的同时挂掉了3个节点,也就是只满足read quorum但不满足write quorum时,这时候会通过read quorum修复错误分区。直到能重新提供读写服务时,Aurora会增加epoch,这个是针对整个protection group维护的值,它代表了当前组的状态,一旦组发生了改变都会导致该值自增。所有请求都需要带上epoch,如果epoch不符则存储节点不会接受该请求。相较之下,一些系统的方式是等待原有的连接过期,如此延迟必定是较高的。

此外,得益于Aurora的架构特点,回放日志的工作可以一直在存储节点后台做,任何一次读磁盘I/O操作,如果数据页不是最新版本,都会触发存储节点回放日志,得到新版本的的数据页。这与传统数据库的故障恢复操作本质是一样的,所以Aurora在故障恢复时不需要做太多的事,甚至可以等到需要访问某个数据页时再对这个页进行“故障恢复”,如此大大减少了故障恢复的时间。

在Aurora中,一旦一个节点出现故障,实际上并不会干等这个节点恢复,为了保证稳定的云数据库服务,它会直接用一个新的节点来顶上。整个节点替换过程是不会阻塞读写服务的。
这里举个例子,假设有protection group ABCDEF,F节点出现故障要替换为G节点。这里并不直接进行组替换,即直接从ABCDEF变为ABCDEG,而是先把G添加到组中,注意此时逻辑上有ABCDEF和ABCDEG两个组,且这两个组都能提供正常的服务。如果过程中F恢复正常,则重新将组切换回ABCDEF,反之则正式变更为ABCDEG。同样的逻辑可以推广到若将F切换成G时,E也出现故障的情况。并且这整个过程中,protection group都是满足quorum协议的。

Amazon Aurora(SIGMOD 2018)云数据库关键技术解读_第6张图片
实际上为了优化存储空间,一个protection group中的6个segment中有3个是full segment——存data block和redo log;另外3个是tail segment——只存redo log。所以对于tail segment的修复来说,只需要通过gossip协议与其它节点同步一致性点就可以了;但full segment稍微麻烦一些,因为它可能是仅有的包含最新的redo log(但中间有“缝隙”)的分区,这种情况下需要从另一个full segment处获取data block,再重新从其它节点中同步至完整的redo log,最后重建完整的full segment。

总结

Aurora是针对云环境设计的数据库,它通过存储计算分离,可以透明地增加存储节点,扩展存储空间。在此之上,IO不再是数据库地瓶颈,因为可以打散在多个节点上,不过相对的,对网络IO的需求就比较高了。

Aurora用了很巧妙的方式避开了类Paxos的一致性协议,仅仅通过LSN来进行一致性维护,这大大降低了数据通讯量,降低了节点之间的耦合度。同时可处理日志缺失,能快速进行故障恢复,epoch机制也大大简化了集群变更操作。在云环境下,Aurora确实有独特的优势。

下面附上推荐阅读,本文也参考了以下的文章:
Amazon Aurora 深度探索(一)
Amazon Aurora 深度探索(二)
Amazon Aurora 深度探索(三)

你可能感兴趣的:(杂记)