首先抛出一个问题,如果在一台机器上,数据库是如何解决事务问题的?很容易想到,数据库的ACID四个特性来保证的,原子性、一致性、隔离性和持久性。
拿mysql为例,数据先写日志文件,后写数据文件,如果写日志文件成功,并提交,发现数据文件没有,就做redo log,随后做redo操作让数据刷盘;如果日志文件没提交,需要写undo log,用于回滚,AD特性是日志文件保证的,CI特性是锁保证的。
而在分布式系统中,事务是由多个系统对应的多个数据库组成,涉及跨系统与跨库操作,本地数据库事务无能为力,需要引入分布式事务来保持数据的一致性。
下面给出本篇要讨论的分布式事务的概览,会重点分析几种常见的实现方案与原理。
引入原因
两阶段也叫做2pc
,在分布式系统中,每个系统节点能感知自己的服务成功与失败,但是无法感知其他节点的服务是否成功,就需要引入一个协调者
来掌控所有节点的操作结果,这些节点叫做参与者
,协调者控制所有节点的逻辑满足ACID特性。
举个例子来说,电商场景中,支付系统支付完成后,调用订单系统更新订单状态。事务管理器Transaction Manager
简称TM
,充当协调者角色,Resource Manager
简称RS
,充当参与者角色。
流程
第一阶段:预提交阶段
第二阶段:执行事务提交
以上是两阶段的正常流程,如果参与者在第一阶段返回no,或者TM在第一阶段询问请求超时,无法获得响应结果,事务就会中断事务,并向所有参与者节点发起回滚请求,参与者节点利用之前写的undo日志进行回滚,并释放所占资源;TM收到所有回滚完成结果后,取消事务。
二阶段思想采用的是先投票(vote),后执行(do commit),典型的例子就是西式教堂里的结婚场景,牧师询问新郎新娘,你是否愿意.....,当各自回答愿意后(锁定一生资源),牧师会宣布:宣布你们正式成为夫妻,......,正式结婚(事务提交)。
二阶段的问题
seata实现
seata阿里开源的分布式事务,默认方式实现两阶段提交,流程如下:
为了降低二阶段问题发生的概率,引入了三阶段模型,也叫3PC
。三阶段在二阶段之前加入了询问环节,询问不锁定资源
。3PC
包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英文就是:CanCommit、PreCommit 和 DoCommit。
三阶段的改善
在分布式系统中,数据的状态在多个系统中流转,通过消息队列、定时任务与本地事件表可以有效的处理分布式事务的数据一致性。假设有如下场景,有支付系统与订单系统两个子系统,具体流程如下:
step1
: 支付系统通过第三方回调完成本地支付流水状态更新,同时在事件表中新增一条记录;step2
: 定时任务扫描事件表里的新增记录,将事件表里的支付流水状态更新为已发送,然后发送一条消息到消息中间件,如果发送失败,本地事务可以回滚;step3
: 订单系统中的消费者监听消息,将消息与对应的状态插入事件表;step4
:定时任务读取本地事务表,执行本地业务,更新订单表状态,最后将事件表的状态再更新为终态。
优点
缺点
注意事项
生产环境中,如果多台机器部署的话,需要考虑分布式定时任务,或者定时任务配合分布式锁来操作,保证同一时刻只有一条记录被定时扫描并执行。
背景
LCN框架在2017年6月份发布第一个版本,从开始的1.0,已经发展到了5.0版本。
LCN名称是由早期版本的LCN框架命名,在设计框架之初的1.0 ~ 2.0的版本时框架设计的步骤是如下,各取其首字母得来的LCN命名。
LCN全称分别对应如下解释:
锁定事务单元(lock)
确认事务模块状态(confirm)
通知事务(notify)
5.0以后由于框架兼容了LCN、TCC、TXC三种事务模式,为了避免区分LCN模式,特此将LCN分布式事务改名为TX-LCN分布式事务框架。
TX-LCN由两大模块组成, TxClient、TxManager,TxClient作为模块的依赖框架,提供TX-LCN的标准支持,TxManager作为分布式事务的控制方。事务发起方或者参与反都由TxClient端来控制。
核心步骤
是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给TxManager的操作。
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager,TxManager将根据事务最终状态和事务组的信息来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。
LCN事务模式
LCN模式是通过代理Connection的方式实现对本地事务的操作,然后在由TxManager统一协调控制事务。当本地事务提交回滚或者关闭连接时将会执行假操作,该代理的连接将由LCN连接池管理。
所以该模式的本质是:TM代理了数据源机制,保持了请求与连接的对应关系。RM假释放资源
,LCN并不生产事务,LCN只是本地事务的协调工。
如下图:
假设服务已经执行到关闭事务组的过程,那么接下来作为一个模块执行通知给TxManager,然后告诉他本次事务已经完成。
那么如图中Txmanager 下一个动作就是通过事务组的id,获取到本次事务组的事务信息;然后查看一下对应有那几个模块参与,如果是有A/B/C 三个模块;
那么对应的对三个模块做通知:提交、回滚。
那么提交的时候是提交给谁呢?
是提交给了我们的TxClient 模块。然后TxCliient 模块下有一个连接池,就是框架自定义的一个连接池(如图DB 连接池);这个连接池其实就是在没有通知事务之前一直占有着这次事务的连接资源,就是没有释放。但是他在切面里面执行了close 方法。在执行close的时候。
如果需要(TxManager)分布式事务框架的连接。他被叫做假关闭
,也就是没有关闭,只是在执行了一次关闭方法。实际的资源是没有释放的。这个资源是掌握在LCN 的连接池里的。
当TxManager 通知提交或事务回滚的时候呢?
TxManager 会通知我们的TxClient 端。然后TxClient 会去执行相应的提交或回滚。
提交或回滚之后再去关闭连接。这就是LCN 的事务协调机制。说白了就是代理DataSource 的机制;相当于是拦截了一下连接池,控制了连接池的事务提交。
特点:
TCC事务模式
TCC事务机制相对于传统事务机制(X/Open XA Two-Phase-Commit),其特征在于它不依赖资源管理器(RM)对XA的支持,而是通过对(由业务系统提供的)业务逻辑的调度来实现分布式事务。主要由三步操作,Try: 尝试执行业务、 Confirm:确认执行业务、 Cancel: 取消执行业务。
如图所示:
特点:
TCC方案分为Try、Confirm、Cancel三个阶段,属于补偿性分布式事务。
Try:尝试待执行的业务
这个过程并未执行业务,只是完成所有业务的一致性检查,并预留好执行所需的全部资源;
Confirm:执行业务
这个过程真正开始执行业务,由于Try阶段已经完成了一致性检查,因此本过程直接执行,而不做任何检查。并且在执行的过程中,会使用到Try阶段预留的业务资源。
Cancel:取消执行的业务,如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿。
这种TCC方案适用于一致性要求极高的系统中,比如金钱交易相关的系统中,不过可以看出,其基于补偿的原理,因此,需要编写大量的补偿事务的代码,比较冗余。不过现有开源的TCC框架,比如TCC-transaction。一般来说跟钱相关的,跟钱打交道的,支付、交易相关的场景,我们会用TCC,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题。
本方案,干脆不用本地的消息表了,直接基于MQ 来实现事务。比如阿里的RocketMQ 就支持消息事务。
流程如下:
上图是早期RocketMQ的实现,依赖zookeeper,因为RocketMQ想追求AP模型,后期版本因为想更轻量化,将zookeeper去掉了。
1.系统 A 本地事务执行完之后,发送个消息到 MQ;
2.这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口;
3.要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。