这是一个创建于 2555 天前的主题,其中的信息可能已经有所发展或是发生改变。
五倍吞吐量的提升,跨可用区的六副本,低于一分钟的宕机恢复,兼容 MySQL 协议,这是 AWS 推出 Aurora 数据库时给出的数据。
这种量级的提升不可能是小修小补,大都是在架构上有了变革性的突破才能达到,坊间流传了很多 Aurora 性能提升的秘密,在 Sigmod'17 上 Amazon 终于自己发了一篇论文介绍了在云环境下如何重新打造数据库这种传统软件。很多的传统软件都可以看一下上云是不是只是装一个软件那么简单。
数据持久性
尽管 Aurora 的性能数据很亮眼,但对于数据库来说最基本的要求还是一旦数据写入成功就不能丢,数据的持久性才是第一位的。多副本是解决数据持久性的常用办法,AWS 采用了基于 quorum 投票的协议来管理副本。简单说就是如果有 N 个副本,则一次写数据要求至少写入( N/2 )+1 个节点才算写入成功,剩下的节点通过相互之间的一致性协议可以达到共同的状态,而读数据则要求至少从 N/2 个节点中读出相同的数据才能决定哪个数据是最新的。
对于一个 3 副本的 quorum 系统,读写的 quorum 数都是 2,这也是一个比较常见的高可用系统选择的 quorum 数。但是这个副本数放在大规模多组户的系统里是不能保证数据的持久性的。因为在大规模系统中,节点出问题是每时每刻都会发生的,如果只有三副本的话,基本上每时每刻都会有一个用户的系统处在 2/3 的健康状态。AWS 的 3 副本是分布在 3 个不同的可用区的,如果一个可用区出了故障,那么所有的数据库副本就会变成 2/3 的健康状态,这种情况下任意一个机器的故障都会导致一个用户的 quorum 健康数变为 1/3,这时候就没有办法判断数据是否一致了。
所以 Aurora 采用的是六副本,每个可用区两副本。这种架构可以保证写入操作可以容忍一个可用区不可用的情况 4/6,而读数据可以容忍一个可用区外加一台机器不可用的情况 3/6。一旦出现了一个可用区加一台机器不可用的 3/6 情况,写数据会被禁止,但是由于读数据可以进行可以很快的根据读出的数据再恢复出一个副本达到 4/6 状态恢复写入。
数据分片
现在来想一下出现了一个 AZ 加一台机器挂掉的情况,这时我们需要重建一个副本,那么重建的时间就取决于数据库副本文件有多大,随着数据库增长,恢复时间也会线性增加。这个对一个发展越来越好的业务是不能接受的,所以需要尽可能降低数据恢复时间。恢复时间过长还会带来另一个隐患就是在恢复的过程中如果又有一个机器故障,那么数据就没办法直接通过其他副本来恢复了,而恢复时间越长这个风险越大。从 CAP 的角度来看 Aurora 作为一个利用分布式存储的数据库是选择了 CP,但是如果可用性出了问题能在极短的时间恢复,那么从实际使用角度也就和 CAP 系统差不多了。
一个直接的做法就是把副本分片,这样一个副本就可以散落在多台机器,这样一台机器挂的情况下我们就不需要恢复完整的副本,只需要恢复机器上的分片就可以。另一方面由于数据进行了分片,读数据的时候也可以利用多台主机的 IO 带宽,对性能也有提升。AWS 采用了 10G 的分片大小,这样在万兆网络的内网环境下,恢复一个分片可以在 10s 内完成,这样在比较极端的 1AZ+1 出故障的情况下,可以保证 10s 内恢复数据库读写。
这种数据分片的方式保证了数据库有很高的可用性,从运维角度来说就可以对这个系统进行 rolling update 了。比如想要升级底层的操作系统和软件,可以直接把机器下线进行升级,上面的分片都会很快在别的机器上进行重建,然后再把机器加回集群升级下一个。发现某台机器硬件有异常也不需要做手工的数据迁移,直接把机器下线送修。另外当出现数据热点的时候也可以直接将这个热点机器的其他分片标记为不可用把分片迁移走,来避免某一个用户的行为造成其他用户的性能下降。这些都是分片带来的好处。
副本的副作用
前面说了多副本带来的数据持久和高可用,但是多副本并不是没有代价的。本来磁盘 IO 的速度就慢,多个副本之间即使是并行的写入操作,latency 也会变成最慢的节点的 latency,随着副本数的增多,抖动会变得更大。另外在云环境下的数据库和传统环境下数据库很重要的一点不同就是计算和存储是分离的,这种情况下读数据可以从本地缓存中拿,而写数据就需要网络 IO,由于存储分散在了多台机器上可以并行利用磁盘带宽,瓶颈就来到了网络。
而 MySQL 自己的一些 IO 机制主要是针对本地磁盘设计的,很多优化方法并不适用于网络存储,此外 MySQL 自己的一些事务和故障恢复的机制,会造成写放大的问题。来看一下 MySQLmirror 机制下的 IO 流程:
可以看到数据库一次写入除了要把数据写入还有 log, binlog,double-write,frm-file 这些东西要写入,而且需要在 master 上完成后才能再去 mirror 上执行同样的过程,等到 mirror 完成后一次请求才能结束。如果直接用 MySQL 的 mirror 机制那么上面提到的 6 副本系统实际上写入需要满足 6/6 才能成功,延迟会极大的加大。如果 AWS 采用这种机制,吞吐量不降到六分之一就算万幸了,更不要提五倍的提升了。这大概也是 AWS 想要自己搞数据库的原因,那么该怎么搞呢?
Log 即数据
要说 AWS 比起 Oracle ( MySQL 现在的爹)做 MySQL 有什么优势的话那就是虽然数据库我没你们熟,但是底层的系统硬件我们都可以自己控制呀。看一下上面那张 IO 流程图,其实很多 log 都是怕底层存储出问题,机器突然断电造成数据不一致和考虑如何进行恢复才生成的。此外这里的一些数据是有冗余的,比如数据库在写入数据前都要先写入 WAL 再进行数据页的写入,至于为什么要这么做可以参考 《 WAL 是如何保证数据库事务一致性的》,这里只要知道通过 WAL 我们是可以重建数据的,只是这样做读数据效率会比较低所以数据库都会再写一次完整数据到硬盘,保证读的效率。
另外由于 MySQL 自己本身不能更改存储系统的功能,所以镜像功能需要自己控制实现,所有的数据同步都要过 MySQL 这个用户层程序去控制,而这些同步的功能又会占用大量的资源开销影响正常的读写操作,相当于每写一次数据,资源开销是原来的多倍。此外在故障恢复的时候 MySQL 在启动时需要话大量的时间对比日志和实际数据状态的差异,就行数据修复,这个过程也会导致故障恢复时间的延长。
AWS 的解决方式就是既然 WAL 里已经包含了所有的数据,那么我就不同步其他的东西了,只同步 log 就可以了。MySQL 去控制同步过程效率低,我底层的 EBS 自己就带同步功能啊,根本不需要 MySQL 再去控制,MySQL 基本只需要老老实实的做 SQL 相关的工作就可以了。数据库的故障恢复,存储系统可以自己进行数据恢复,MySQL 只需要把引擎启动起来就可以了。总结来说就是 MySQL 之前之所以慢是因为文件系统有很多缺陷需要很多额外的功夫来保证数据写入能成功,现在 AWS 给了一个及其完善的文件系统,数据写入了就能成功,断电了也能自己恢复,还能自己做多副本,那么 MySQL 自己要做的事情就少了,性能自然也就上去了。现在的流程就简化成了:
所有的 Replica 节点不需要将数据写入磁盘了,数据同步由底层的 EBS 系统来做。Replica 节点只需要接收 Primay 的 Log 信息来更新本地的缓存和一些全局的配置。Primary 的写入操作也不再需要各种各样的 log 写入,只需要把 WAL 给底层存储系统,存储系统会自动的写入到一定的 quorum 节点上。而有了这些 log,存储系统会自动的去做 checkpoint, 备份和故障恢复,而且这些耗时的操作都是在后台默默的异步执行的,不需要前台的 MySQL 引擎。这样写入操作的 IO 量和操作路径都显著变少了,AWS 才能把 MySQL 的性能在云环境里提升那么多。
再加上分片的存储将磁盘 IO 的压力分散,读写性能都得到了显著提升,放两张图来感受一下: