分布式初探—分布式事务与两阶段提交协议

分布式系统当中的一致性简单可以分为强一致性和弱一致性

强一致性很好理解,简单可以理解成主节点每次更新都通过同步的方式同步更新所有副本
而弱一致性则是统称所有不满足强一致性的模型,可以简单理解成通过异步更新的方式实现的一致性模型。

想象一下更新的时候,有节点出错的情况。如果是强一致性,很好办,因为我们采用同步更新,所以更新失败的话,主节点立刻就能感知。要么重试这次的更新,要么回滚放弃,或者是判断该从库是否已经宕机,将它移除资源池
如果是异步的更新机制就麻烦了,因为没有一个统筹大局的主库了。没有节点知道其他节点更新成功了没有,如果部分成功了,部分失败了,那么数据的一致性就完全没办法保障了,脏数据到处都是,这个系统也就没法用了。所以必须要保证即使是异步更新,也要做到原子性,要么所有节点一起更新成功,要么一起失败回滚,不允许出现一部分成功了,另一部分没有的情况。

那么怎么解决这个问题呢?这就需要用到两阶段提交协议了。


一、两阶段提交协议

两阶段提交协议的算法思路其实不难,非常直观,很好理解。前文当中说了,之所以会出现部分节点更新成功,而部分节点更新失败的情况,主要原因就是因为没有一个节点统筹全局,所以没办法做出整体决策导致的。既然如此,那么解决的策略也很简单,我们加上一个整体协调的节点即可。整个协调节点会首先分发任务到各个节点,各个节点收到任务成功之后,由协调节点统一指挥,决定执行任务还是回滚。这个思路和战争非常相似,一个将军坐镇中央,各个小分队散落四方。将军统一指挥,各分队进退有素。

我们整理一下整个过程,可以将它分成两个阶段,分别是表决阶段和提交阶段

在这里插入图片描述
首先1号节点是协调节点,可以理解成将军节点,其他节点都是小兵节点。首先表决阶段也可以理解成任务分发阶段,由将军节点将本次更新的内容分发给各个小兵节点。小兵节点收到任务之后,如果当前情况允许执行该任务,则回复可以执行,否则回复无法执行。

第二个阶段是提交阶段,也就是执行阶段。由将军节点搜集各个小兵节点的回复消息,只要有一个小兵节点回复无法执行,那么说明这次的任务无法执行,所以将军节点会再次发送消息给各个小兵节点,告知任务取消,此时各个小兵节点删除此次任务。否则,将军节点会发送执行任务的命令,各个小兵节点各自执行任务。

这个就是整个两阶段提交协议的内容。
我们接着深入其中的细节,试着画出将军节点和小兵节点状态的状态机

在这里插入图片描述
这是将军节点的状态机,一共只有四种状态。一开始是init状态,表示初始化,也就是分发任务之前的状态。当它给各个小兵节点分发任务之后,转变到等待状态,线程挂起等待,等待各个小兵节点的回复。等所有回复到齐了之后,决定是否执行任务,如果执行则转移到执行状态,否则转移到取消状态
在这里插入图片描述
小兵节点的状态机也类似,小兵节点初始化之后等待将军发放任务的消息。如果小兵判断当前任务无法执行,那么会直接报告将军节点并跳转到取消状态。否则会进入就绪状态,表示当前节点可以执行任务,但是要等将军的指挥。接着,根据将军的命令决定执行任务或者是取消回滚

上面这两个状态机应该也很好理解,但是这当中有一个小问题。我们观察一下将军节点和小兵节点的状态机,会发现两者当中都存在挂起等待的状态。将军节点做决策之前需要等待所有小兵的回复,而小兵节点执行之前也同样需要等待将军的命令。我们不禁想问一个问题,如果消息传递的过程当中存在丢包应该怎么办?如果更严重一些,某个小兵节点或者是将军节点宕机了呢,应该怎么办?

以现在的模式显然是无法解决的,我们需要加入优化。

这里的优化针对将军节点和小兵节点需要做区分,两者的策略是不同的。我们一个一个来看。

  1. 首先是将军节点,如果小兵节点宕机或者是发送的消息丢包,那么将军节点将会无法决策,或者会长久等待。为了解决这个问题,我们需要加上超时机制。如果超时还没有搜集齐应答,那么可以判定是有小兵发生故障或者是网络出现问题,则自动视作任务失败,广播任务回滚的消息。
  2. 其次是小兵节点,如果小兵节点在初始状态超时,则自动在本地终止任务,并且发放任务无法执行的消息给将军节点。如果是在等待将军决定的时候超时,这个时候不能简单的终止任务,因为无法判断其他节点有没有执行任务,如果简单地终止,那么就会引起数据不一致。针对这个问题的策略稍稍不同,既不是继续等待,也不是去询问将军节点,而是引入互询机制,也就是说当前小兵去询问其他小兵。

因为小兵的状态只有那么几种,为了方便区分,我们给小兵起个名字。发起询问的小兵称为A,被询问的小兵称为B。我们列举一下,总共只有四种可能。

  1. B的状态是success,说明B已经收到将军节点的命令执行了任务。那么很简单,不管是什么原因导致A没有收到消息都没有关系,A直接也执行任务即可。
  2. B的状态是fail,说明将军已经取消了任务,但是A没有收到。那么也很上面一样,A直接放弃任务执行即可。
  3. B的状态是init,说明B一直还在等待任务发放,而A已经领到了任务并且在等待将军决策了。一般来说这种情况由两种可能,一种可能是B和将军的通信出现了问题,所以迟迟收不到将军的消息,还有一种可能是将军自己崩溃了。如果将军崩溃了,那当然没的说,任务肯定不能继续了,如果将军还在,那么它肯定还在等待B的消息,由于将军有超时机制,所以最终仍然会超时导致取消任务。所以A把任务置为失败是正确的。
  4. B的状态也是等待,说明B这个节点也不清楚状况,这种情况比较麻烦,只能再换一个节点询问。那问题来了,如果所有节点都处于这个状态呢,应该怎么办?显然在当前的设计里,无法避免这个问题,会导致长久的等待

二、三阶段提交协议

针对上文当中说的二阶段提交的那个问题,大数据专家提出了解决方案,就是在执行阶段再细分成两个阶段,也就是预执行状态和执行状态。因为多了一个阶段,所以也称为三阶段提交。

我们来看三阶段提交中将军节点的状态机:
在这里插入图片描述
和之前的相比差别不大,只不过多了一个预执行的节点。我们再看小兵节点
在这里插入图片描述
小兵节点也一样,相当于多接受将军节点一次消息第一次收到执行消息之后转到预执行状态之后,其实节点就会执行任务了,只是不会提交任务和释放资源,只有进入执行结束的状态之后才会进行这一操作。

数据库里的事务打个比方,在进入预执行状态之后,节点就会执行事务,但是不会提交事务已经执行完成的状态,也就是说依然可以回滚。所以在预执行之后,如果收到将军的消息需要取消任务,那么小兵节点依然会回滚,进入状态失败。

和两阶段提交相比,在三阶段提交当中,如果小兵在等待的状态超时,那么会直接进入任务取消状态,不会再询问其他节点。如果小兵执行成功会返回将军ACK,如果失败则不会返回。将军如果没有收到所有小兵的成功消息,则会通知所有小兵回滚。如果在预执行状态等待将军消息超时,那么会直接进入执行成功状态,只有收到将军消息取消任务,才会回滚

显然三阶段提交也不是完美的,虽然降低了阻塞等待的可能性,但是如果小兵节点没有收到将军发来的回滚消息,那么也会产生数据不一致。并且三阶段提交的时间开销要比二阶段提交大得多,加上二阶段提交出现阻塞的概率非常低,因此绝大多数分布式协议当中还是用的二阶段提交

二阶段提交的协议在分布式系统当中广泛使用,并且它非常直观,推导的过程也很有意思,状态机的应用也很巧妙。


参考文章:TechFlow公众号的文章

原创文章 626 获赞 104 访问量 32万+

猜你喜欢

转载自blog.csdn.net/gx17864373822/article/details/105284109