在之前发表的一篇文章中,介绍了TIKV分布式事务 ,这篇文章会接着上一篇文章,介绍一下分布式事务在节点出现异常时候的处理逻辑,与上一篇文档的目的一样,依然是希望分享出来让大家给看看我对这些逻辑的理解是不是存在偏差。
分布式事务的基本要素里,事务的原子性和一致性,在分布式集群中存在节点宕机的情况时,就无法保证,因此TIKV的分布式事务实现,使用了2PC。也就是之前介绍的prewrite和commit两个步骤。实际生产中,可能会出现下表所示的这些异常情况。
逐条介绍一下这些异常,首先是prewrite阶段下的异常,prewrite阶段,首先会对primary key进行操作,当操作失败,失败原因可能是锁被占用或者超时或者是leader节点挂死,此时secondary key还未进行,那么所有的事务会直接进行回滚,因为所有操作都没有进行,这种异常对整体的影响比较小。
场景二中,当primary key写入成功后,secondary key执行命令也未发出,此时leader节点挂死,集群会等待节点恢复,集群中会存在两个计时器,一个是follow节点的leader节点心跳计时器,还一个是锁的超时计时器,因为随机时间的存在,这两个超时器无法确定谁先到达阀值,因此接下来会出现以下几种情况:
①节点正常恢复,恢复时不存在定时器超时,则继续后续的流程
②follow节点的leader心跳计时器超时,触发新一任leader选举,此时可以认为primary key写入失败,所有节点回滚;
③节点正常恢复,未触发选举超时,但是锁的超时计时器已到达阀值,primary key已超时被释放了,也就是写入失败了,所有节点回滚;
场景三的情况与场景二类似,只不过primary key已经写入成功,并且secondary key也开始进行上锁的过程,但是此时primary key挂死了,最终的异常判断逻辑还是会和场景二一样。
场景四是primary key写入成功,且节点后续无异常,异常出现在secondary key写入的过程,因为事务的原子性,只要存在失败项,则会触发所有节点的回滚。
场景五开始,prewrite阶段已完成,开始进入commit阶段的异常情况。primary key提交失败,会直接触发所有节点的回滚,secondary key涉及到的行commit会在primary key提交完毕后才会进行,但是Lock CF与Default CF中的内容其实全部都已经完成了,commit阶段的回滚,对集群整体的影响是很大的。
场景六,primary key提交成功了,但是在对primary key进行锁释放操作时节点挂死,导致primary key无法及时释放,此时后续的流程会阻塞,类似于场景二,需要等待节点的恢复,再根据是否恢复以及恢复时的计时器是否存在超时,判断primary key事务的提交及锁释放是否成功,若成功,则意味着整体事务已完成,secondary key涉及的行的commit操作会异步开始进行。
场景七,该场景是实际生产中出现的比较多的异常场景,因为只要primary key提交成功,且锁已经释放了,就认为此次的写入成功了,其余的锁其实是以指针的形式指向主锁,主锁释放了,其余的副锁相当于也释放了。只不过需要先对数据写入到write CF中,然后再释放掉锁,只要主锁完成了释放,剩下的过程都是异步进行的,如果涉及到的数据很大,那这个过程可能需要较长的时间,在这过程中,不管是leader节点还是follow节点,都可能会出现异常。
场景七中,当leader节点发生异常,后续恢复时,如果集群中存在新的leader节点,那么会转变自己的角色,然后根据leader节点发送心跳中的lastIndex,commitIndex等信息来继续执行之前的事务,follow节点发生异常,逻辑与leader节点相同,只不过不需要转变角色。下图是一个示例
TIKV Node1在事务进行到绿色箭头所示的位置时,节点异常,恢复时,会接收到leader节点的心跳,发现自己的状态与leader节点不同步了,处理也是按照顺序进行,绿色箭头处可以看到key=1且start_ta=110处的Lock CF中有一把指向主锁的副锁,此时根据这个指向,去查看这把主锁的状态,红色箭头处可以看到这把主锁已经释放了,那副锁就知道此时自己应该完成commit操作,然后根据主锁的信息,更新自己的commit_ts信息,后面的行也是相同的逻辑,最终会同步到与Node0一样的状态,实现事务的一致性,如下图所示
至此结束!