TDSQL 金融级特性之:数据强一致性保障
TDSQL 的整体架构和核心特性作为金融场景下不可或缺的数据强一致性的保障。我们将从四个方面来聊一聊数据一致性的保障:
1.主备数据复制方式
2.数据复制比较:TDSQL 主备数据复制方案 VS MySQL 原生方案
3.核心功能:容灾切换,数据强一致、0 丢失 0 出错
4.数据强一致性
TDSQL 主备数据复制:高性能强同步
首先在讲数据一致性之前,我们先了解一下 MySQL 原生的数据复制的方式。
首先第一种是异步复制:主机在不等从机应答直接返回客户端成功。这个在金融场景是不能接受的,这样的话相当于数据是没有多副本保障。
第二种是半同步:主机在一定条件下等备机应答,如果等不到备机应答,它还是会返回业务成功,也就是说它最终还会退化成一个异步的方式,这同样也是金融场景所不能接受的。
**除此之外,原生半同步其实是有一个性能方面的缺陷,即在跨 IDC 网络抖动的场景下,请求毛刺现象很严重。**所以原生的异步复制和半同步复制都存在一些问题,并不能完全适应金融场景。
**TDSQL 引入了基于 raft 协议的强同步复制,主机接收到业务请求后,等待其中一个备机应答成功后才返回客户端成功。**我们一主两备下一条业务请求到达了主机之后必须等其中一个备机应答成功,才能返回客户端成功,否则这个请求是不会应答的。所以说,强同步是 TDSQL 最基础的一个特性,是 TDSQL 保证数据不会丢、不会错的关键。
讲到这里的话,可能有些同学会问,你们这个强同步其实也不复杂,不就是在半同步的基础上把这个超时时间改成无限大同时应答的备机设置为 1。并不是这样的,TDSQL 强同步这里的关键不是在解决备机应答的问题,而是要解决这种增加了等待备机的机制之后,如何能保证高性能、高可靠性。
换句话说,如果在原生半同步的基础上不改造性能,仅把超时时间改成无限大的时候,其实跑出来的性能和异步比甚至连异步的一半都达不到。这个在我们看来也是无法接受的。相当于为了数据的一致性牺牲了很大一部分性能。
TDSQL 强同步复制的性能是在原生半同步的基础上做了大量的优化和改进,使得性能基本接近于异步。
所以这里强同步强调的是,实现强同步的同时还具备高性能特性,所以确切地说是一个高性能的强同步。
那么我们如何实现高性能的强同步?我们继续往下看。这里 TDSQL 对 MySQL 主备复制的机制做了改造。首先,我们先引入了一个线程池的模型。
原生的 MySQL 是——一个互联请求是一个线程,这样对操作系统的资源消耗还是很大的:5 千个连接,5 千个线程。那 1 万个连接,1 万个线程,操作系统能扛得住吗?肯定扛不住。而且原生的数据复制的方式,其实串行化比较高,比如说一个用户请求发过来后,等备机应答;这个过程中用户线程在这里是完全不能做事的,只有等备机应答之后,它才能够返回前端。也就是说大量的线程都处于一个等待的状态,这是半同步效率低的根本原因。
**引入了线程池模型之后,我们还需要考虑怎么调度线程池。**我们希望 MySQL 维护一小部分工作的线程,比如说有 1 万个用户连接,真正干活的可能就那么 100 个、50 个 MySQL 的工作线程。如果是非工作的线程,他在等 IO 应答时可以先去睡眠,并不让它去影响我们的吞吐量。所以 TDSQL 对原生的复制做了改造,除了引入线程池模型,还增加了几组自定义的线程。
这个线程是做什么呢?当一笔用户请求过来之后完成了写操作以及刷新了 binlog,第二步他应该要等备机应答。这个时候被我们新引入的线程组所接管,把这个用户对话保留下来,释放了工作线程,工作线程该干什么继续干什么,处理其他的用户连接请求。真正备机给了应答之后,再由另外一组线程将它唤醒,回吐到客户端,告诉客户端这个应答成功了。
所以经过这样一个异步化的过程,相当于把之前串行的流程异步化了,这样就达到了接近于异步复制的性能,这就是 TDSQL 在强同步的改造的核心。所以我们这里强调不单单是一个实现强同步的过程,更是一个接近异步性能的强同步。
再看一下改造之后的性能对比:异步 TPS 大概是 6 万左右,平均时耗小于等于 10 毫秒。再看半同步,明显有三分之二的性能损耗,并且这个时耗波动还是比较大的,比如说 IDC 网络抖动一下。强同步一主两备模式下,首先性能已经接近于异步的性能,此外时耗并没有额外增加。
因为一主两备,除非两个机房网络同时抖动,否则的话强同步的时耗不会有明显波动。所以我们看到基于 TDSQL 的强同步实现了数据的多副本又保证了性能。
有了这个多副本保障,又怎么如何实现故障自动切换,数据不丢不错呢?这其实还是需要一套切换选举的流程。这就引出了 TDSQL 的容灾切换功能。
自动容灾切换:数据强一致、0 丢失 0 出错
自动容灾切换在有了强同步特性的基础,就变得非常容易实现了。我们先看一下这个结构图:
SQL 引擎将请求发给主节点,主节点被两个备机所同步,每个节点上都有对应的 Agent 上报当前节点的状态。这时,主节点发生了故障被 Agent 觉察,上报到 zk 被 Scheduler 捕获,然后 Scheduler 首先会把这个主节点进行降级,把它变成 Slave。也就是说此时其实整个集群里面全是 Slave,没有主节点。这个时候另外两个存活的备机上报自己最新的 binlog 点,因为有了强同步的保障,另外两个备机其中之一一定有最新的 binlog,那么两个备机分别上报自己最新的点后,Schedule 就可以清楚的知道哪个节点的数据是最新的,并将这个最新的节点提升成主节点。
比如说发现 Slave1 的数据是最新的,这个时候 Schedule 修改路由,把主节点设置为 Slave1,完成了主备的切换。有了前述这个切换机制,保证了整个切换无需人为干预,并且切换前与切换后的数据是完全一致的。这里做一个总结,正是由于强同步的保证,所以当主机发生故障的时候,我们一定是可以从一个备节点上找到最新数据,把它提成主节点。这就是 TDSQL 容灾切换的整个过程,容灾切换需要建立在强同步的基础上。
极端场景下的数据一致性保障
聊完了容灾切换,我们再聊一聊故障节点的后续处理事宜。故障处理可能有几种情况:一种是故障以后,它还能活过来。比如说像机房可能突然掉电了,掉完电之后它马上又恢复。或者机器因为硬件原因不小心发生了重启,重启完之后节点是可以被拉起,被拉起之后,我们希望它能够迅速的再加入到集群中,这时数据其实是没有丢没有错的,我们不希望把节点故障之后又重新建一份数据,把之前的数据全抹掉。而是从它最后一次同步的数据为断点,继续续传后面的数据。带着上述问题,我们看一下节点的故障后恢复过程。
首先我们考虑一种场景,比如说 A 节点作为主节点,B、C 是从,正常去同步 A 节点的数据,A+1、A+2,接下来该同步 A+3。当 A+3 还没有同步到从节点的时候发生了故障,这个时候根据 B、C 节点的数据情况,C 的数据是最新的,因此 C 被选成了主节点,进而 C 继续同步数据到 B。过了一阵,A 节点拉起了,可以重新加入集群。重新加入集群之后,发现它有一笔请求 A+3 还没有来得及被 B、C 节点应答,但已经写入到日志。这个时候其实 A 节点的数据是有问题的,我们需要把这个没有被备机确认的 A+3 的回滚掉,防止它将来再同步给其他的节点。
所以这里强调的是一个数据的回退,相当于每一个 Slave 新加入节点的时候,我们都会对它的数据进行检验,将多写的数据回滚。当然刚刚的假设是运气比较好,A 节点还能重启。有些时候 A 节点可能就挂了,这个机器就再也起不来了,我们需要把这个节点换掉,即换一个新机器,比如:加一个 D 节点。我们希望它快速重建数据并且对当前线上业务尽可能无影响。如何让它快速去重建呢?当然是基于物理拷贝,而且它是从备机上去拉数据,这样它既不会对主节点产生影响,又能够快速把数据重建好。
评论