分布式事物的常见实现方式

分布式事物在水平方式扩展的系统中随处可见,两阶段提交(2PC)、三阶段提交Three-phase commit(3PC)、补偿事务(TCC)、本地消息表(异步确保)都是常用的解决方案。

两阶段提交(2PC)

两阶段提交(2PC)是一种标准化协议,将提交操作分为两个独立单元,进行数据库提交。这种分布式事务需要一个称为协调器的对象,协调器处理分布式服务器之间的活动和同步。

两阶段提交实现

阶段1

每个需要提交数据的服务器将其数据记录写入日志(undolog)。如果服务器不成功,它将返回“失败”消息。如果成功,服务器将返回“成功”消息。

阶段2

所有参与者回复“成功”消息后,协调器向每个服务器发出“提交”指令,参与者节点正式完成操作,并释放在整个事务期间占用的资源。成功则发送“完成”消息,协调者收到所有参与者节点反馈的"完成"消息后,结束事务。

在一阶段任何参与者返回"终止"的消息,或协调者在一阶段的询问超时,协调者会向所有参与者节点发出"回滚"请求。

两阶段提交的缺点

二阶段提交执行过程中间,参与的节点都处于阻塞状态。任意一个节点超时都需要“回滚”其它节点,在策略上显得比较保守。

三阶段提交Three-phase commit (3PC)

三阶段提交是2PC的改进版,2PC有很明显的缺点,在协调者发出“提交”指令后,某个参与者突然崩溃,在崩溃恢复后,这个参与者跟其他参与者的数据是不一致的。

因此,3PC将2PC的commit的过程1分为2,分成preCommit及commit。如果协调者发送preCommit,参与者收到preCommit,但因等待commit超时,默认也执行commit。

如果整个过程有节点发生崩溃,会有一个watchdog出现通过检查所有节点,如果有任一节点收到commit,或者全部节点都收到preCommit,  则进行commit,否则“回滚"。

三阶段提交优点

允许单点故障后仍能实现数据一致性。

三阶段提交缺点

网络分区问题。preCommit发出后,网络出现问题,协调者发出”撤销“操作, 而参与者默认执行了commit。

补偿事务(TCC)

TCC编程模式本质也是一个二阶段协议,不同之处在于TCC编程模式需要与具体业务耦合,下面是实现TCC编程模式步骤:

1、所有事务参与者都需要实现try、confirm、cancle接口。

2、事务发起者向事务协调器发起事务请求,事务协调器调用所有事务参与者的try方法完成资源的预留。

3、如果有参与者的try方法预留资源失败,则调用所有参与者的cancle方法回滚,cancle方法需要实现幂等,以便应对网络原因引发的重试。

4、如果所有参与者的try方法返回都“OK”,则调用所有参与者的confirm方法,进行业务操作。

5、所有参与者的confirm方法都返回“OK",事务协调器将结束分布式事务。

6、如果有参与者的confirm方法返回”失败“,或网络原因没有收到回执,协调器会进行重试,如果重试失败需要事务补偿机制进行数据处理。

以用户转账为例,try阶段操作的是账户冻结金额,而非账户余额,只有两个账户的冻结金额操作成功,才认为try阶段是成功的。confirm阶级直接用冻结记录更新账户余额。如果处理不成功,需要有事务补偿机制。

本地消息表(异步确保)

本地消息表只对当前系统的本地数据库事务负责,对业务所涉及到的子系统,通过消息队列的方式,通知子系统对所管理的数据库事务进行操作。

消息发起方需要建一张消息表,记录发送的消息及状态,消息表和本地业务数据表处在同一个事务里,然后通过MQ把消息传递给子系统,消息接收方要能够接受重试。

子系统(消息消费端),根据消息处理业务,处理成功或失败都需要通知消息生产方,以便进行消息确认、补偿或回滚等操作。

生产方和消费方需要定时查看本地消息表,处理尚未完成或者失败的消息。

这种方案遵循了BASE理论所提到最终一致性,既不会像2PC那样复杂(调用链越长2PC的可用性越低),也不会像TCC那样出现确认或者回滚不了的情况。

本地消息表优点

通过最终一致性避免了分布式事务。

本地消息表缺点

消息表会与到业务表处在同一个数据库中的一个事物中。