从银行转账失败到分布式事务:总结与思考 - xybaby - …

2019-05-25 10:01 admin
思考这个问题的初衷,是有一次给朋友转账,结果我的钱被扣了,朋友没收到钱。而我之前一直认为银行转账一定是由事务保证强一致性的,于是学习、总结了一下分布式事务的各种理论、方法。原子性很好理解,这两个操作要么都成功,要么都不执行(更准确的是从效果上来看等价于都没有执行)。不可能出现用户A的钱减少了而用户B的钱没增加的情况,用户是不允许的;更不可能出现用户B的钱增加 而 用户A的钱没有减少的情况,银行是绝对不干的。因此,用户A,B在这次事务操作前后,账户的总和一定,是应用层面的一致性,而不是数据库保证的一致性,应用层面的一致性事实上是由原子性来保证的。在第二阶段,协调者根据所有参与者的投票结果做出是否事务可以全局提交的决定,并通知所有的参与者执行该决定。在一个两阶段提交流程中,参与者不能改变自己的投票结果。两阶段提交协议的可以全局提交的前提是所有的参与者都同意提交事务,只要有一个参与者投票选择放弃(abort)事务,则事务必须被放弃。注意,上图中最下面一行也表明,两阶段提交协议也依赖与日志,只要存储介质不出问题,两阶段协议就能最终达到一致的状态(成功或者回滚)缺点:两阶段提交协议的容错能力较差,比如在节点宕机或者超时的情况下,无法确定流程的状态,只能不断重试;两阶段提交协议的性能较差, 消息交互多,且受最慢节点影响系统水平伸缩的死敌。基于两阶段提交的分布式事务在提交事务时需要在多个节点之间进行协调,最大限度地推后了提交事务的时间点,客观上延长了事务的执行时间,这会导致事务在访问共享资源时发生冲突和死锁的概率增高,随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面上水平伸缩的"枷锁", 这是很多Sharding系统不采用分布式事务的主要原因。这类事务机制将分布式事务分成多个本地事务,这里称之为主事务与从事务。首先主事务本地先行提交,然后通过消息通知从事务,从事务从消息中获取信息进行本地提交。可以看出这是一种异步事务机制、只能保证最终一致性;但可用性非常高,不会因为故障而发生阻塞。另外,主事务已经先行提交,如果因为从事务无法提交,要回滚主事务还是比较麻烦,所以这种模式只适用于理论上大概率等成功的业务情况,即从事务的提交失败可能是由于故障,而不大可能是逻辑错误。如果用异步消息实现转账的例子,那么操作分为四部:用户A扣钱,发消息,用户B收消息,用户B扣钱。前两步必须保证原子性,如果A扣钱成功但是没有发出消息,那么用户A损失了;如果发消息成功,但是没有扣钱,那么用户B就多得了一笔钱,银行肯定不干。事务消息依赖于支持事务消息的消息队列,其基本思想是 利用消息中间间实施两阶段提交,将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败。流程如下:不难看到,相比本地消息表的方式,事务消息由消息中间件保证本地事务与消息的原子性,不依赖于本地数据库存储消息。但实现了事务消息的消息队列比较少,还不够通用。很多场景,比如电商、网络购票,首先要保证的是高可用,不大可能采用强一致性,因此我们也会看到正在处理中...这种中间状态,后台很可能是异步处理的,在12306买过票的话都知道,下单成功到最后是否能出票由很长一段时间。之前一直以为像银行转账这种场景,一定是强一致性的。后来自己遇到这么一回事,我给朋友转账,我这边显示转账成功,但朋友并没有收到钱。我以为是需要一定时间,结果24小时之后还没有收到。我自己重新比对转账单,才发现是把对方的开户银行写错了。因此可见,转账这个操作肯定不是强一致性,具体怎么搞的在网上也没有查到。更坑爹的是,转账失败,我的钱被扣了,朋友也没有收到钱,但是我没有收到任何消息,也没有给我把钱退回来,在我打电话到银行去咨询之后才退回来。这个体验真的很差,但银行是大爷,没办法!