2PC(Two Phase Commitment Protocol)

读TiDB原理部分,知道其分布式事务是参考的Google percolator。而percolator是一种2PC的优化。数据库

分布式事务解决的是什么问题呢?网络

假设一个场景,一个电商网站,用户在购买商品时,须要两步操做1)建立订单,2)扣减库存。咱们一般但愿这两步是事务的,要么同时成功,要么同时失败。若是订单建立成功,库存扣减失败,会致使超卖。若是订单建立失败但扣减了库存呢,会致使少卖。分布式

怎么解决这个问题呢?性能

若是订单表和商品表在MySQL同一个逻辑DB里面,可使用MySQL的单机事务来保证。优化

若是在不一样DB呢?网站

一种折中的作法,分别在两张表上开事务A、B,两事务都执行完后,同时commit,咱们将重要的事务放在前面commit;这样若是A事务失败,则AB同时回滚。但若是A成功commit,B失败,就存在数据不一致的问题。日志

上面咱们让AB等待同时完成后提升,必定程度下降了这种不一致发生的几率。但不是个完善的解决方案。事务

下面介绍的2PC,为解决这个问题。it

首先2PC中有一个协调者节点,多个参与者节点(不一样的数据库表)。io

【一】协调者协议流程以下:

1. 开启一个事务,写本地日志begin_transaction,进行wait状态

2. 向全部参与者节点发送prepare消息,并等待参与者节点对prepare消息的响应

    a. 若任何一个节点返回vote-abort消息,则写本地日志global-abort,  行向全部参与者节点发送global-abort消息,进入ABORT状态

   b.若收到全部参与者节点返回的vote-commit消息,本地日志写global-commit日志,并向全部参与者节点发送golobal-commit消息,进入COMMIT状态】

3.等待参与者节点的global-commit或global-abort消息的回复,若全部节点都回复,则写日志end_transication,并完成事务。

【二】参与者协议流程以下:

1.开启事物,写本地日志init,进入INIT状态

2.等待协调者的prepare消息

  a.若能够进行本次事务,则写本地日志ready,进入READY状态,并向协调者节点发送vote-commit消息等待回复

  b.若收到回复global-abort,写本地日志abort,进入ABORT状态,并向协调者节点发送回复

  c.若收到global-commit消息,写本地日志commit,进入COMMIT状态,向协调者发送回复

3.若参与者没法进行本次事务

    a.写本地日志abort,进入abort状态,并向协调节点发送vote-abort消息。能够对后序收到的global-abort消息进行响应

4.即便流程结束,但任什么时候候收到协调者发送的global-abort或global-commit消息,也发送一个相应的回复。

 

** 以上操做都是先写日志,再进行处理

 

接下来讨论一下异常处理:

【协调者节点宕机恢复】

先看一下协调节点几种可能日志记录:begin_transiaction, global-commit或global-abort, end_transication

协调者宕机恢复后,先让到事务其最新日志,如果begin_transiaction,表示协调者处于WAIT状态,此时或者已经发送过prepare消息,也可能没有发过。但能够确认,必定没有发送过global-commit或global-abort消息。此时只须要重发prepare消息,即便参与者已经收到并回复过prepare消息,此时只需从新发一条便可。不影响一致性。

若是日志中最后是global-commit或global-abort日志。说明宕机前处于COMMIT或ABORT状态,此时协调者只需向参与者再发一次global-commit或global-abort消息,继续2PC流程。

【参与者节点宕机恢复】

若是日志处于init状态,表示还未对本事务作出选择,继续等待prepare消息便可。

若是处于ready状态,说明已经收到了prepare消息,可是否已经作出回复 不消息可知;因此重发vote-commit消息便可。注意这里是发送的vote-commit而不是vote-abort,由于只有本次事务能够提交,才会到ready状态。

若是日志最后是commit或abort状态,则表示已经收到了global-commit或global-abort消息,但不能肯定是否已经发送过了确认消息。这时候只须要等待新的 global-commit或global-abort消息,并进行回复便可。由于协调者节点会不断重发消息。

 

分布式系统中,错误通常分为两块,超时和其它错误,其中超时是最难处理的错误。接下来讨论超时的问题。

 

协议的异常,体如今等待消息的超时上面。

【一,协调者在WAIT状态超时】

通常有两种缘由,1.协调者与某个参与者之间的网络断开。2.某个参与者宕机,这种超时,能够选择放弃整个事务。由于WAIT状态下,协调者必定未发送来global-abort或global-commit消息,所以只要向全部参与者发送global-abort中止事务就能够,不影响协议正确性。

【二,协调者在COMMIT或ABORT状态超时】

此时,等待参与者对global-commit或global-abort的响应消息超时。这种状况下协调者只能不断重发global-commit或global-abort消息,直到全部参与者都响应。

2PC对这种状况没有很好的容错,只能阻塞在这里不断重试。其中任何结点的超时,或者协调者自己的网络问题,都会致使2PC完成不了。

【三,参与者INIT状态超时】

此时还没收到prepare消息,直接abort便可。但可能致使原先能够提交的事务不能成功完成。

【三,参与者READY状态超时】

READY状态,表明参与者收到prepare消息,并回复了vote-commit消息。此时参与者不能再改变本身的选择,只能不断重发vote-commit,直到收到global-abort或global-commit消息,继续下面流程。这里能够对应到协调者不断重发global-commit或global-abort消息,一样没有很好的容错机制。整个流程阻塞在这里,对于参与者而言,协议状态处于未知,即不能提交本节点事务,也不能放弃本节点事务。(若是提交了,实际协调者发送了global-abort,则在本节点提交,其它节点未提交,致使数据不一致。若是放弃了,则实际协调者发送了global-commit,则本节点放弃,其它节点提交了,一样致使数据不一致。)

 

实际上,2PC的有不少缺点:

1)容错不好,以上超时分析可知。

2)性能不好,一次通讯涉及到4次消息交互,慢节点对整个协议性能影响很大。

3)可用性差,协调者单点

相关文章
相关标签/搜索