以银行转账为例分析分布式事务的解决方案
提起分布式系统,就会涉及分布式事务,本文就以金融项目的转账业务为例,分析各种业务场景下的转账业务的事物问题。
一、业务场景
以工商银行转账业务为例,那么项目的分布式架构大致如下,一个银行的一个支行部署一个节点,那么相同节点之间的业务就是本地事务、不同节点之间的就是分布式事务
转账业务包括以下三种情况
支行内转账:同为工行的相同支行内转账(本地事务)
行内转账:同为工行的非同支行内转账 (分布式事务)
跨行转账:和其他银行的系统进行转账 (分布式事务)
1.1、支行内转账业务
如用户A和用户B都是工行-杭州支行的用户,A向B转账10000元,那么就需要保证事务,从而达到A的账户-10000,而B的账户+10000的效果
由于是本地事务,所以A账户的扣减和B账户的增加就可以放在一个事务中实现,基本上没有太大的问题,不管哪一步异常了都可以实现事务回滚。
1.2、行内转账
如用户A是杭州支行,用户B是北京支行,A向B转账10000元,那么虽然都是工行用户,但是是分布式部署的,就会涉及到跨库的分布式事务问题,一般解决方案有同步和异步两种方式:
同步方式可以如下:
1、创建转账订单,订单状态为待成功
2、用户A扣减10000元
3、发起转账请求到北京支行
4、北京支行创建转账订单,订单状态为待成功
5、用户B增加10000元
6、北京支行订单状态改为成功并返回结果
7、杭州支行接收响应结果,如果为成功则提交事务表示转账成功,如果为失败则更新订单状态为转账失败
8、定时任务根据查询转账失败订单在北京支行的订单订单状态,如果失败,则回滚转账事务;如果成功则提交事务
异步方式如下:
1、创建转账订单
2、用户A冻结10000元
3、提交事务
4、异步发起转账请求,判断结果
结果可以为:
1、转账成功:确认成功
2、转账失败:确认失败
3、请求异常:结果不确认
对于结果不确定的情况就采用:查询的方式查询结果,查询结果还是不确定的话
就采用定时任务查询异常订单查询
1.3、跨行转账
如用户A是工行用户,用户B是建行用户,A向B转账10000元,这里两个用户不是同一个行的用户,基本上就不会再采用同步的方式进行转账了。
异步方式如下:
1、创建转账订单,订单状态为待成功
2、用户A冻结10000元
3、提交事务
这里需要保证的是用户资金和订单状态的事务
异步发送http请求到其他行进行转账业务,
结果可以为:
1、转账成功:确认成功
2、转账失败:确认失败
3、请求异常:结果不确认
对于结果不确定的情况就采用:查询的方式查询结果,查询结果还是不确定的话
就采用定时任务查询异常订单查询
金融项目一般不会太重视转账的实时性,而是重视转账的一致性,比较涉及到的是金钱,所以必须要确保转账的事务性。
总体来说,解决转账的分布式事务还是以异步为主,采用的是最终一致性来解决分布式事务问题。大致流程如下:
1、事务1执行事务,并创建订单,确保订单和金额变得的一致性
2、异步发起转账请求
3、接收异步回调或同步响应
4、如果是成功则更新订单状态和金额、如果是失败则回滚、如果是不确定则通过查询的方式来确认订单的结果
5、根据订单的最终结果来更新数据
二、其他分布式事务解决方案
2.1、基于XA的二阶段提交协议
基于二阶段提交的方案主要是将事务分成了两个阶段,一个是事务准备阶段,一个是事务提交阶段,并且需要一个事务管理者的角色,也就是事务管理器
![](https://img2018.cnblogs.com/blog/822135/201908/822135-20190816164602697-1117541602.png)
第一阶段:
1、事务管理器通知所有参与事务的各个本地资源管理器,通知他们准备事务
2、各个本地资源管理器进行事务准备,写好事务日志并执行事务,但是不提交,然后将本地事务执行的结果上报给事务管理器
第二阶段:
1、事务管理器接收各个本地资源管理器执行的事务结果,如果全部成功则表示事务成功,需要提交;如果有一个失败则表示事务失败,需要回滚
2、事务管理器向各个资源管理器发生提交或回滚请求,各个资源管理器分别进行提交或回滚(提交或回滚的耗时很短,失败的概率相对很低)
2.2、TCC方案
TCC方案是将事务分成了三个阶段,分别是try、commit、callback阶段。
![](https://img2018.cnblogs.com/blog/822135/201908/822135-20190816164625424-1785038193.png)
事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的try接口,完成一阶段准备。之后事务协调器会根据try接口返回情况,决定调用confirm接口或者cancel接口。如果接口调用失败,会进行重试。
TCC方案让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。 当然TCC方案也有不足之处,集中表现在以下两个方面:
- 对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高。
- 实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口必须实现幂等。
上述原因导致TCC方案大多被研发实力较强、有迫切需求的大公司所采用。微服务倡导服务的轻量化、易部署,而TCC方案中很多事务的处理逻辑需要应用自己编码实现,复杂且开发量大。
2.3、基于MQ的最终一致性
![](https://img2018.cnblogs.com/blog/822135/201908/822135-20190816164717809-1155071094.png)
基于MQ的一致性相当于是异步实现的分布式事务,将事务分成了两个不关联的本地事务,基于MQ进行同步,根据最终结果实现一致性从而达到分布式事务
2.4、基于fescar实现的分布式事务
![](https://img2018.cnblogs.com/blog/822135/201908/822135-20190816164916573-1017329851.png)
XA的第一阶段是执行sql,但是不提交,第二阶段一起提交 (会导致锁的时间是整个事务的时间)
Fescar的第一阶段是各个分支事务执行并提交事务,只是在提交之前持久化了回滚日志(undo log),第二阶段如果是提交的话就只需要删除持久化日志即可;否则才需要执行回滚操作
主要是对XA的优化,第一阶段的区别