一、2PC(两阶段提交)/XA
2PC(Two-phase commit protocol),中文叫二阶段提交。
二阶段提交是一种强一致性设计,2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票)和提交两个阶段。
XA 是由 X/Open 组织提出的分布式事务的规范,XA 规范主要定义了(全局)事务管理器(TM)和(局部)资源管理器(RM)之间的接口。本地的数据库如 MySQL 在 XA 中扮演的是 RM 角色。
XA 一共分为两阶段:
第一阶段(prepare):即所有的参与者 RM 准备执行事务并锁住需要的资源。参与者 ready 时,向 TM 报告已准备就绪。
第二阶段 (commit/rollback):当事务管理者(TM)确认所有参与者(RM)都 ready 后,向所有参与者发送 commit 命令。
目前主流的数据库基本都支持 XA 事务,包括 MySQL、Oracle、SQL Server、Postgre。
XA 事务由一个或多个资源管理器(RM)、一个事务管理器(TM)和一个应用程序(ApplicationProgram)组成。
XA 事务的特点是:
- 简单易理解,开发较容易
- 对资源进行了长时间的锁定,并发度低
二、三阶段提交(3PC)
三阶段提交又称 3PC,相对于 2PC 来说增加了 CanCommit 阶段和超时机制。
如果段时间内没有收到协调者的 commit 请求,那么就会自动进行 commit,解决了 2PC 单点故障的问题。但是性能问题和不一致问题仍然没有根本解决。
下面我们还是一起看下三阶段流程的是什么样的?
第一阶段:CanCommit 阶段。这个阶段所做的事很简单,就是协调者询问事务参与者,你是否有能力完成此次事务。
如果都返回 yes,则进入第二阶段。有一个返回 no 或等待响应超时,则中断事务,并向所有参与者发送 abort 请求。
第二阶段:PreCommit 阶段。此时协调者会向所有的参与者发送 PreCommit 请求,参与者收到后开始执行事务操作,并将 Undo 和 Redo 信息记录到事务日志中。
参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。
第三阶段:DoCommit 阶段。在阶段二中如果所有的参与者节点都可以进行 PreCommit 提交,那么协调者就会从“预提交状态”转变为“提交状态”。
然后向所有的参与者节点发送"doCommit"请求,参与者节点在收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的 Ack 消息后完成事务。
相反,如果有一个参与者节点未完成 PreCommit 的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送 abort 请求,从而中断事务。
三、SAGA
Saga 是这一篇数据库论文 saga 提到的一个方案。其核心思想是将长事务拆分为多个本地短事务,由 Saga 事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。
把上面的转账作为例子,一个成功完成的 SAGA 事务时序图如下:
SAGA 事务的特点:
- 并发度高,不用像 XA 事务那样长期锁定资源
- 需要定义正常操作以及补偿操作,开发量比 XA 大
- 一致性较弱,对于转账,可能发生 A 用户已扣款,最后转账又失败的情况
四、TCC
2PC 是数据库层面的,而 TCC 是业务层面的分布式事务,就像我前面说的分布式事务不仅仅包括数据库的操作,还包括发送短信等,这时候 TCC 就派上用场了!
TCC(Try Confirm Cancel) ,它是属于补偿型分布式事务。它的核心思想是 针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC 实现分布式事务一共有三个步骤:
- Try:尝试待执行的业务
这个过程并未执行业务,只是完成所有业务的一致性检查,并预留好执行所需的所有资源 - Confirm:确认执行业务
确认执行业务的操作,不做任何业务检查,只使用Try阶段预留的业务资源。通常情况下,采用TCC则会认为 Confirm 阶段是不会出错的。只要 Try 成功,则 Confirm 一定成功。如果 Confirm 出错了,则需要引入重试机制或人工处理 - Cancel:取消待执行的业务
取消 Try 阶段预留的业务资源。通常情况下,采用 TCC 则认为 Cancel 阶段也是一定能成功的,若 Cancel 阶段真的出错了,也要引入重试机制或人工处理
TCC 是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。它的优缺点如下:
优点: 吧数据库层的二阶段提交上提到了应用层来实现,规避了数据库的 2PC 性能低下问题
缺点:TCC 的 Try、Confirm 和 Cancel 操作功能需业务提供,开发成本高。TCC 对业务的侵入较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作
五、本地消息表
本地消息表其实就是利用了 各系统本地的事务来实现分布式事务。
本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。
如果调用失败也没事,会有后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。
可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。
六、最大努力通知
最大努力通知也成为定期校对,是对可靠消息服务的进一步优化。它引入了本地消息表来记录错误消息,然后加入失败消息的定期校对功能,来进一步保证消息会被下游服务消费。
同样的这个跟消息事务一样可以分为两步:
步骤一: 服务A向消息中间件发送消息
- 在处理业务的同一个事务中,向本地消息表写入一条记录
- 消息发送者不断取出本地消息表中的消息发送到消息中间件,如果发送失败则进行重试
步骤二: 消息中间件向服务B投递消息
- 消息中间件收到消息后便会将消息投递到下游服务B,服务B收到消息后便会执行自己的业务
- 当服务B业务处理成功后,便会向消息中间件返回反馈应答,消息中间件便可将该消息删除,该流程结束
- 如果消息中间件向服务B投递消息失败,便会尝试重试,如果重试失败,便会将该消息接入失败消息表中
- 消息中间件同样需要提供查询失败消息的接口,服务B 定期查询失败信息,并进行消费
最大努力通知的方案实现比较简单,适用于一些最终一致性要求比较低的业务。
七、消息事务
RocketMQ 就很好的支持了消息事务,让我们来看一下如何通过消息实现事务。
第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务。再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令。
并且 RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。
如果是 Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。
如果是 RollBack 那么订阅方收不到这条消息,等于事务就没执行过。可以看到通过 RocketMQ 还是比较容易实现的,RocketMQ 提供了事务消息的功能,我们只需要定义好事务反查接口即可。
八、AT 事务模式
这是阿里开源项目 seata 中的一种事务模式,在蚂蚁金服也被称为 FMT。优点是该事务模式使用方式,类似 XA 模式,业务无需编写各类补偿操作,回滚由框架自动完成,缺点也类似 AT,存在较长时间的锁,不满足高并发的场景。
在 Seata 项目中,最早由阿里巴巴中间件开源出的 AT 模式(Automatic Transaction) 是一套创新的、业务无侵入的分布式事务解决方案。
截止 Seata 的 GA 版本发布,AT 模式 已经在开源社区引起了广泛关注,很多家企业用户已经将 Seata 的 AT 模式应用于生产。
AT 模式一阶段:首先,在 Seata 的组件中,如果你想开启分布式事务,那么就应该在你的业务入口或者事务发起入口加上 @GlobalTransactional 注解。
如果你是 AT 模式就要做好数据源代理(seata1.0 后全面支持自动代理),并被 sqlsessionfactroy 使用(或者直接 jdbc 操作使用被代理数据源)。
可以发现比较关键的一步,与其他模式的区别便是代理数据源,而代理数据源又有什么奥秘呢?
AT 模式二阶段提交:二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库,所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
AT 模式二阶段回滚:二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”。
如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写, 出现脏写就需要转人工处理。