分布式事务及其常用解决方案
分布式数据源事务
多数据源事务机制
在一个分布式系统中,一个功能可能由多个服务来共同完成。例如常见的电商系统,用户在购物下单时,可能同时由购物服务,订单服务,库存服务共同完成。购物服务调用库存服务,来判断是否存在库存,减少库存等。调用订单服务插入订单记录。每个服务可能都有自己不同的数据源,在单个服务操作库时,数据库只能保证自己这边的事务机制对本库的操作,无法干涉其他数据库。基于此,针对多数据源提出了分布式事务机制。
分布式事务模型
名词
事务参与者:例如每个数据库就是一个事务参与者
事务协调者:访问多个数据源的服务程序
资源管理器(Resource Manager,RM):通常与事务参与者同义
事务管理器(Transaction Manager,TM):通常与事务协调者同义
在分布式事务模型中,一个 TM 管理多个 RM,即一个服务程序访问多个数据源;TM 是一个全局事务管理器,协调多方本地事务的进度,使其共同提交或回滚,最终达成一种全局的 ACID 特性
拜占庭将军问题和两军问题
这里推荐一篇讲述拜占庭将军问题的文章:https://www.8btc.com/article/70370,在查阅资料时会发现很多博主把拜占庭将军问题和两军问题混淆了,这里把两个问题都记录一下,敢兴趣的可以阅读上面推荐这篇文章。
拜占庭将军问题:
在分布式计算中,不同的计算机通过通讯交换信息达成共识而按照同一套协作策略行动。但有时候,系统中的成员计算机可能出错而发送错误的信息,用于传递信息的通讯网络也可能导致信息损坏,使得网络中不同的成员关于全体协作的策略得出不同结论,从而破坏系统一致性。由莱斯利兰波特在其同名论文中提出的分布式对等通信容错问题。
问题描述: 一组拜占庭将军进攻一个城市。他们必须统一进攻或者撤退,如果发生一组进攻,一组撤退,就会导致战败。所以,将军们必须通过投票达到一致目的。将军们处于城市不同方向,他们之间使用信使通信,每一位将军通过投票告诉其他将军自己决定,并通过其他将军的投票来判断是进攻还是撤退,假设这个过程中不存在信使无法传递信息或者被截杀的可能性,即是在保证信道的可靠性的前提下研究此问题。
问题在于,如果将军中出现叛徒,就会导致信息出错。假如5个将军投票,前两个投票决定进攻,后两个投票决定撤退,当第五个将军是叛徒时,他告诉前两个将军进攻,告诉后两个将军撤退,就会导致战局的失利。
两军问题:
这是和拜占庭将军问题相似的问题,但实质上是不同的。假如甲军将乙军围在山谷中,甲军分别在山谷的两侧,任何一侧的兵力都不如乙军。甲军只有同时进攻时才能获取胜利。
这个问题映射到计算机网络中,将山谷两次的甲军看成计算机,信使就是报文,服务器A发送报文给服务器B,但是A没有得到请求,它不知道自己的报文是否会因为是网络问题没有到B服务器,或者到了B服务器,但是在B返回响应时,确认报文因为网络问题没有到A服务器。在A看来,都是B没有收到它的请求,为了确保消息传到,A会重新发送请求,直到收到B的响应。但是A重复发送请求,在一些情景中,是无法接受的。例如电商系统中,A重复给B发送扣款请求,那将是致命的错误。
分布式事务解决方案
分布式事务解决方案推荐看阿里的seata,网址:http://seata.io/zh-cn
2PC和3PC:
2PC: 即2段提交,第一阶段:perpare准备阶段,第二阶段:commit提交阶段。
准备阶段:TM给所有RM发送指令,让RM做好提交前的所有准备,然后给TM发送yes或no指令或者超时。
提交阶段:如果TM收到了所有RM返回的yes指令,则TM给所有RM发送提交指令,每个RM收到指令后提交事务,给RM返回ACK。如果收到超时指令或者其中一个RM给TM发送了no,则TM给所有RM发送回滚指令,所有TM开始回滚刚才提交的事务,然后发送ACK。
2PC 简明易懂,但存在如下的问题:
-
性能差,在第一阶段,要等待所有的参与者返回,才能进入阶段二,在这期间,各个参与者上面的相关资源被排他地锁住,参与者上意图使用这些资源的本地事务只能等待。因为存在这种同步阻塞问题,所以影响了各个参与者的本地事务并发度。
-
如果第一阶段完成后,RM突然宕机,所有的参与者都收不到提交或回滚指令,就会导致资源堵塞,数据库无法使用。
-
丢失消失导致数据不一致,在第二阶段,如果因为网络问题,导致一部分TM没有收到commit消息,就会出现一部分事务参与者提交了事务,一部分没有提交,导致节点间的数据不一致。
3PC:
3PC的阶段在2PC的基础上新增的CanCommit阶段,并且新增了超时机制。在3PC在准备阶段前,资源调度者会事先询问各个事务参与者的状况,得到yes的回答后,才会执行percommit。具体就不叙述了。3PC解决了超时问题,但是性能差和数据不统一的问题依旧没有解决。
2PC 除了性能和可靠性上存在问题,它的适用场景也很局限,它要求参与者实现了 XA 协议,例如使用实现了 XA 协议的数据库作为参与者可以完成 2PC 过程。但是在多个系统服务利用 api 接口相互调用的时候,就不遵守 XA 协议了,这时候 2PC 就不适用了。所以 2PC 在分布式应用场景中很少使用。
XA协议由Tuxedo首先提出的,并交给X/Open组织,作为资源管理器(数据库)与事务管理器的接口标准。目前,Oracle、Informix、DB2和Sybase等各大数据库厂家都提供对XA的支持。XA协议采用两阶段提交方式来管理分布式事务。XA接口提供资源管理器与事务管理器之间进行通信的标准接口。
TCC
TCC(补偿事务,Try-Confirm-Cancel)。TCC相比与2PC解决了几个问题
- 解决了事务协调者单点的问题,事务管理器又单点变成了多点,引入集群
- 引入超时,超时后进行补偿,不会锁住整个资源
TCC的相比2PC的欲提交,实际提交,变成了预留资源,确认资源。分为三个阶段,try-confrim-cancel。
预留资源:举个例子,正常在下单中,要对库存表中库存字段进行减一操作。但是在TCC方案里面,欸,我们多了个冻结库存字段,在下单后,我们实际上先不对库存字段减一,我们对冻结库存进行加一操作,然后两个都给前端,前端就用库存减去冻结库存,显示给用户,用户看到的是库存减一的数据。
-
try阶段:每个事务参与者都要实现try接口,预留(锁住)资源,确保一致性。事务管理器要检查所有事务参与者是否完成预留资源。
-
confirm阶段:事务参与者实现confirm接口。当事务协调者收到所有事务参与者预留资源的回答后,调用所有事务参与者的confirm接口,完成事务提交。需要解决幂等性问题,保证每个事务参与者只实现了一次confirm。
-
cancel阶段:在try阶段时,如果事务协调者收到了任何一个事务参与者的no或者超时,则调用所有事务参数者的cancel接口,进行回滚,释放资源。需要实现幂等性。
这里提供一种幂等性解决方法,我们用消息表记录下每次confirm和cancel,然后操作前去查表,看我们是否执行过这条命令了。
本地消息表
该方案通过我们又两个角色,消息生产者和消费者。生产者将通过消息日志(例如:数据库),记录消息发送状态。消息通过MQ发送到消费者,消费者处理这条消息,如果本地事务处理失败,则重新执行。如果业务上失败,则给生产者发送条消息,通过生产者回滚。
消息一致性
这也是常用的一种方案。
- A系统先向mq发送perpare消息,如果发送失败,则取消操作。
- A发送成功,则执行本地事务。
- A执行本地事务成功,向mq发送confirm消息,失败则回滚。
- B定时轮询mq中confirm消息,执行本地事务,成功发送ack。如果业务失败,则告诉A系统回滚。
- A系统(消息主动方)提供一个接口查询消息的处理状态,mq定时轮询调用此接口。如果perpare消息本地事务处理成功,则发送confirm消息,否则回滚。
m消息,执行本地事务,成功发送ack。如果业务失败,则告诉A系统回滚。
5. A系统(消息主动方)提供一个接口查询消息的处理状态,mq定时轮询调用此接口。如果perpare消息本地事务处理成功,则发送confirm消息,否则回滚。