为什么需要分布式事务

先摆一个例子: A账户要向B账户转账,100元, 操作的步骤是

A账户先减掉100元
B账户再加上100元

非常简单。。在单个数据库实例的情况下,开启事务,先执行A-100 ,再执行B+100, 事务提交,结束。 然而,由于业务增长, 导致一个数据库实例已经不能满足所有数据存储,此时A与B存在于不同的数据库实例上,这个时候执行这两部操作就要每个实例分别启动一个事务,A减掉后提交,B增加时,其所在机器挂掉了。。 那么A就损失了100元,这显然是不可接受的。

那么这个问题怎么解?

二阶段提交

解这个问题,需要让跨越多个数据源的一次业务行为,还能保持事务的ACID(原子、一致、隔离、持久)。 如果只是各个参与方各自为战,显然不行,因为缺少一个可以观看全局的组织者, 每个参与者都不知道其他参与者的情况,也就无法判断当前的全局情况,自己是不是要提交。有一个组织者出现后,每个参与者在准备提交前都可以问一下组织者,其他人好了没有,如果得到回答说,都好了,那么各自就开始最终提交。 这个过程其实就是二阶段提交(2PC)的主要思想。 关于二阶段协议,左耳朵耗子的博客( 分布式系统的事务处理 | | 酷 壳 - CoolShell )讲了一个绝妙的例子:

一对准夫妻在教堂举办婚礼,
神父通常会先问新娘:你愿意爱你面前的这个人,生老病死。。 , 新娘回答:我愿意。
然后神父再问新郎:你愿意。。。 新郎回答:我愿意。
最后神父宣布新郎新娘成为一对夫妻。

跟上述描述的二阶段提交是相同的过程, 双方都同意了,神父才会宣布婚姻成立,不可以说只有一个人同意,神父就宣布,你已经结了一半婚,没有意义。 这个过程,画出来流程图是这样的:

pic1

分布式事务的问题

二阶段提交过程是分布式事务的一个比较通用的解决方案了, 然而也存在它的局限。由于一次操作可能涉及到几个参与者,那么一次操作所需要花费的时间,就是多个参与者prepare+commit时间的总和。这可能带来两个方面的问题,一是总体耗时的增加,二是由第一个问题带来的资源损耗。

第一个问题,带来的是用户体验上的下降,设想你去便利店消费,要转账给店员10元, 等了好几分钟才转成功,这时后面排队都十几人了。

第二个问题,则是给系统带来的服务容量的挑战,设想淘宝双十一这种场景,每秒钟上万笔交易,涉及大量转账划拨操作,如果全部由二阶段的分布式事务实现,延迟不说,光是机器性能就会很容易到极限。

另一种方案

那么有没有替代方案?

先回归到问题的本质,分布式事务解决的其实是数据的一致性问题,A给B转账100元, 效果是A减了100,B要增加100, 一增一减要相等,至于这个相等是A操作完之后等着B加了100才结束,还是A操作完之后,有另外一种机制,保证B在后面某个时刻不多不少只增加100。

后一种解法,就是通过引入异步消息的方式,来替换第一种分布式事务的方案。 简单示意图如下;

pic2

可以看到,整个过程分为三步:

  1. 发起转账一方,记录一条扣减流水。
  2. 转账发起方发送消息给转账接收方(接收方也可能是自己)。
  3. 接收方写入一条增加流水。

总体思路是先暂存转账记录凭证,利用消息系统的可靠性,保证整个转账操作达到最终一致。

上面只是简略步骤,如果要达到替代两阶段的效果,还需要一些支撑,比如:

  • 消息可能发送失败,需要发送端有恢复机制,定时重试失败流水。
  • 消息真实发送后,A的本地事务不可以失败,这要求要么把消息发送放到最后,要么需要发送消息本身要跟A处于同一个事务,所谓事务消息(这又是一个很大的话题)
  • 消息可能重复发送,但是B不可以重复处理, 需要B保持一个接收消息的记录,每次比对排除重复。

这样看来,使用异步消息的方案,也并不简单,或许一致性问题本身就是一个复杂的课题。