PolarDB技术深度剖析

阿凡达2018-07-04 11:09
前言
9月21日,阿里云PolarDB发布会如约而至,这次的发布会透露了更多的PolarDB细节,使我们可以从技术细节上对PolarDB有个全面的分析,下面就让我们来慢慢揭开PolarDB神秘的面纱,来看看这款被阿里云寄予厚望的产品的内在到底包含了多少值得我们学习的高新技术。
整个PolarDB从组织架构上来分,大体可分为4层:数据库层、驱动层、网络层和存储层。分别对应:DBServer、PolarFS + PolarSwitch、RDMA、PolarStore。
PolarDB节点种类
根据官方给出的信息,PolarDB节点共分为3中不同类型的节点,分别为:Primary、Replica、StandBy,3种节点根据不同的角色,内部功能也有所差异。Primary节点为数据读写节点,在整个PolarDB集群中,只能有一个Primary节点,所有数据写入都在Primary节点进行操作;Replica节点为只读节点,在PolarDB集群中,Replica节点可以有多个,Replica节点不允许数据写入,只能同步Primary的数据,Replica节点内部禁止数据写到底层存储设备;StandBy节点功能与Replica类似,只是用于同步Primary节点的数据,但区别于Replica节点的是,StandBy节点允许把数据写入底层存储,一般StandBy节点用于做远程多机房备份等等。
数据库层
PolarDB的数据库层主要是通过定制MySQL实现,PolarDB多个计算节点底层共享一份数据,在MySQL中,多个实例共享一份数据是不被允许的,所以PolarDB在实现的时候必然对MySQL有所内核层面的改动。下面我们来着重分析下PolarDB的数据库内核改动。
先把问题简化下,我们不考虑远程存储,在一台机器上面,我们怎么来实现多个MySQL实例共享一份数据?最简单的想法就是把多个MySQL的data文件夹指向一份数据(假设不考虑MySQL一份数据对应一个实例限制),
如上图所示,3个节点同时指向同一份数据,因为PolarDB自身没有解决多个节点同时对同一行数据修改的问题,所以PolarDB只允许一个节点可以写入。对于上述1主2备的集群,还存在如下一些问题。
Buffer Pool缓存一致性
MySQL通过Buffer Pool缓存数据,对于写入的数据,在写完Redo log以后,缓存在Buffer Pool中,然后通过一个异步线程刷新到磁盘。在上述3个节点的集群这个,A上进行更新的数据可能被缓存在A的Buffer Pool中,如果在数据刷到磁盘之前,在C上读取这些数据是不可见的。
事务数据一致性
除了Buffer Pool缓存数据造成Replica数据读取延迟外,Partial Transaction也是一个比较严重的问题,对于MySQL中的数据,被更改的数据(脏页)会被加入到队列(Flush List),并没有区分操作该数据页的事务是否已经结束,这样的话,可能会造成一个事务修改了部分数据页,然后这些数据页被刷到底层存储上,Replica直接读取这些数据页,然而Primary上的事务可能没有结束,Replica相当于读取了部分事务修改,这个不符合MVCC特性,即:Partial Transaction可能破坏了MySQL本身的MVCC机制。
上图中,A上执行数据插入修改事务,当执行到第二个insert时,A上的Buffer Pool由于Free Page不足导致数据页刷盘,部分当前事务修改的数据页有可能被刷入底层存储中,这个时候,如果C上进行查询,很可能查到被未提交事务修改的数据页。
Redo复制
PolarDB为了解决上述问题,在引擎层引入了物理复制(InnoDB redo log复制),通过物理复制把Primary上的更新直接通过redo log传递到Replica,然后Replica通过apply redo进行更新数据的重放。因为Redo在事务开始时就被写入,不会受Buffer Pool异步刷盘的影响。而且由于undo log的更新也需要记录redo,所以通过redo复制可以把Primary上整个undo段也复制过来,也不会破坏本身的MVCC特性。Redo复制唯一的问题就是会不会像Binlog复制一样存在延迟?对于Binlog复制,由于Slave端Apply log的速度跟不上Binlog产生速度,可能会出现Binlog堆积。而Redo由于是物理日志,不需要通过SQL解析和大量的锁等待,对于相同页面的更新可以batch进行,对于不同页面也不必像Binlog一样考虑事务的互斥性,不同的页面的更新都可以并行执行,所以一般情况下,Redo复制很少有堆积,即使有堆积也可以选择不同的策略(强同步、允许延迟)进行处理。
DDL问题
上面介绍了,物理复制也有可能存在延迟,对于普通的DML来说,极短时间的延迟有时是可以被接受的,但如果对于DDL来说,Primary与Replica之间的延迟,很可能会造成Replica意外宕机,举个例子:在Primary上某个用户执行了drop table tbl操作,这个时候Primary会把整个tbl表锁住来进行相应的数据清理,并且在底层存储上删除该表的物理信息,而如果这个时候在任意一个Replica节点上有对该表的访问,由于数据文件都已经不存在,很可能造成Replica crash。目前官方实现比较简单,在执行DDL之时,需要通知集群中所有的Replica节点(standby节点自身有数据不受影响),Replica节点在接收到DDL执行指令后,会禁止该表上所有操作,只有当所有Replica节点都返回成功时,Primary节点才可以进行后续的操作。所以DDL是一个影响非常大的操作,目前需要整个集群进行同步。
Undo Purge问题
MySQL的MVCC要求能被Purge线程清理的Undo log必须是没有被使用的:
根据上图中相关信息,对于一个RR隔离级别的MySQL来说,transaction1产生的undo log可以被清理的,而transaction3产生的undo,由于transaction2还没有结束,并不能马上被purge线程清理。那么问题来了:前面也说过,对于MySQL来说,Undo log的改变也会被记录到Redo中,考虑一个问题,当Primary上某条undo信息可以被purge线程清理时,如果Replica上该Undo还在被使用会出现什么问题?Replica需要读取的Undo页面可能会被Redo复制清理掉!!细节问题还真多~~~,怎么防止,两种办法:
  1. Replica隔段时间上报自身能清理的Undo位置,然后Primary统一根据所有上报的位置选取最小一个进行清理
  2. Undo清理时Replica如果发现清理了正在被使用的Undo,Block整个Primary的Undo Purge过程。
其他
除了上述一些问题外,其他还有很多小问题,比如:StandBy如何复制物理文件(ibd、opt文件);各种不同角色节点如何进行切换等等一系列问题;当然还有部分优化措施,比如:更多Lock Free数据结构的引入;Redo log写入块从512B改成4KB,分别对应HDD一个扇区大小和SSD一个块大小;latch层面优化等等。
驱动层
相对于DBServer层,驱动层透露的信息就相对比较少,有两块组成,分别为PolarFS和PolarSwitch,PolarFS可以看作是一个存储层的客户端,嵌入在MySQL代码内部,应该是替换了MySQL/InnoDB操作文件系统的I/O模块,PolarFS支持InnoDB数据页的原子写(16KB),所以可以直接关闭MySQL的DoubleWrite功能;而PolarSwitch则是一个主机级别的进程,一台服务器部署一个PolarSwitch即可,不用关心该台服务器上部署了多少个MySQL实例。PolarSwitch响应PolarFS的读写请求,然后反馈到底层存储。
网络层
RDMA网络解决了网络通讯中高延迟的问题,使得远程存储响应时间能控制在10us以下,远程数据的访问速度与本地磁盘访问几乎没有多大差别。目前所知,底层网络都是通过Infiniband设备来组建。
存储层
存储层提供了分chunk、多副本高可用,采用分chunk技术保证单个chunk大小控制在10GB,采用并行Raft实现chunk级别的高可用。chunk的大小设定为10GB主要权衡了Raft副本缺失后恢复的速度,10GB的chunk在RDMA网络下,能在极短的时间内进行恢复。
PolarDB与Aurora区别
通过上文中的分析,我们可以知道,即使PolarDB与Aurora的架构比较类似,但是还是有些区别的,Aurora设计的主要思想是日志即数据,在保证日志落盘的前提下,尽量减少磁盘I/O,所以整个Aurora只写一份Redo log到底层存储,由底层存储来实现把redo log‘恢复’成数据页的行为。而PolarDB与之不同的是,PolarDB写入底层存储的数据包含两部分:数据页和redo log,而PolarDB底层的存储PolarStore更像是一个通用的高可用存储,不需要进行类似Aurora一样把Redo日志恢复出数据文件的操作。
PolarDB技术要点一览

本文来自网易实践者社区,经作者蒋鸿翔授权发布。