〇、前提
不到走投无路的时候不要轻易启用分布式事务。在工程应用上的坑还是挺大的。
是否真的有跨应用业务操作的原子性需求(不管是跨库还是跨微服务)?
研发上是否能投入足够的资源进行系统改造(这不是CRUD,拉个人来就能干)?
一、所谓分布式事务
当事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上时,
每台资源服务器只能保证自身服务的数据一致性,无法保证调用其他服务的操作是否成功,此时必须有分布式事务介入。
分布式事务的主要实现手段有:
- 分阶段提交
- TCC
- 分开提交(非强一致)
二、分阶段提交
2-1、XA协议
XA协议是一个基于数据库的分布式事务协议,分为两部分:(全局)事务管理器(TM)和(局部)本地资源管理器(RM)。
事务管理器作为一个全局的调度者,负责对各个本地资源管理器统一号令提交或者回滚。
二阶提交协议(2PC)和三阶提交协议(3PC)都是根据此协议衍生而来。
Oracle、Mysql等数据库均已实现了XA接口。
2-2、2PC
启用面向服务架构(SOA)后,一个下单流程就会用到多个服务,各个服务都无法保证调用的其他服务的成功与否,这个时候就需要一个全局的 协调者 对各个服务(参与者)进行协调。
所谓的2PC,指的是两阶段提交:第一阶段,准备阶段(投票阶段) ;第二阶段,提交阶段(执行阶段)。
1. 准备阶段。协调者向每一个参与者发送Prepare消息,询问是否可以提交事务,并等待所有参与者答复。各参与者执行事务操作,但不提交事务,向协调者反馈YES/NO。
2. 提交阶段。协调者收到参与者的失败消息后,向每个参与者发送回滚(Rollback)命令,所有参与者执行阶段一中的UNDO信息执行回滚,释放所有资源,并做ack反馈;否则,发送提交(Commit)命令,所有参与者提交事务,并做ack反馈。
这样,参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中被占用的资源。可以做到事务管理要求的:所有操作要么全部成功、要么全部失败。
2PC流程图2PC可以解决分布式事务的基本问题,但是自身存在些许缺陷:
- 同步阻塞。2PC中所有参与者都采用同步阻塞方式进行操作,哪怕是单一节点发生故障,都有可能造成整个体系出现同步阻塞问题。
- 单点故障。协调者没有集群。
- 网络抖动造成参与者数据状态不一致(脑裂)。未正常接收提交命令的参与者,没有执行commit/rollback命令,会造成系统整体数据不一致。
2-3、3PC
3PC,即三阶段提交,是对2PC的一种优化升级,主要表现在:
- 在中间插入了一个阶段,在最终提交之前,各参与者节点状态可以保持一致。
- 引入超时机制,当参与者各种原因未收到协调者的commit请求后,会对本地事务进行commit,不会一直阻塞。但是同时也增大了数据不一致的风险。
不过3PC仍然无法解决数据状态不一致的问题。
3PC的三阶段如下:
1. CanCommit。协调者向所有参与者发送CanCommit消息,询问是否可以执行事务提交操作。如果全部响应正常则进入下一个阶段。
2. PreCommit。协调者向所有参与者发送PreCommit命令,所有参与者收到PreCommit请求后,执行事务操作,但不提交事务。一旦参与者中有向协调者发送了No响应,或因网络造成超时,协调者没有接到参与者的响应,协调者向所有参与者发送abort命令,参与者接受abort命令执行事务的中断,释放资源。
3. DoCommit。在前两个阶段中所有参与者的响应反馈均是YES后,协调者向参与者发送DoCommit命令正式提交事务。
4. 关于超时。进入阶段3后,无论协调者出现问题,或者协调者与参与者网络出现问题,都有可能导致参与者无法接收到协调者发出的Do Commit命令或abort命令。此时,参与者都会在等待超时之后,会直接执行事务提交。
3PC流程图四、TCC
TCC 是 Try-Confirm-Cancel 的缩写。
TCC与2PC的思想很相似,事务处理流程也很相似,但2PC是应用于在DB层面的,可以将TCC理解为在应用层面的2PC,需要编写业务逻辑来实现。
TCC它的核心思想是:针对每个操作都要注册一个与其对应的确认(Try)和补偿(Cancel)操作。
1. Try阶段:锁定执行操作需要的所有资源,比如为了防止超卖,要先锁定一部分商品数量。
- 完成所有业务检查(一致性)
- 预留必须业务资源(准隔离性)
2. Confirm阶段:在已经锁定的资源基础上,确认正式执行业务操作。
- 真正执行业务
- 不做任何业务检查
- 只使用Try阶段预留的业务资源
3. Cancel阶段:在所有的参与者中,只要有一个业务方锁定资源未成功,或者提交失败,则取消所有业务资源的锁定请求,进行回滚。
应用TCC的系统还应该准备一个专门事务管理服务,相当于2PC中的协调者,负责统一调用confirm和cancel、记录TCC全局事务状态、监控、补偿等等的工作。
TCC的缺点:
- 应用侵入性高、开发难度大:TCC是在应用层面实现的,每个操作都需要有 try、confirm、cancel 三个接口。其中的后两个接口还必须保证幂等性。
五、分阶段提交与 TCC 的小结
-
分阶段提交:先锁定资源(锁定+有效性验证),再一起提交,如果有参与者锁定、提交失败,则全部回滚。需要资源管理器支持这种功能(XA协议),是通过数据库层面进行实现,不需要手动写代码。
-
TCC:锁定或者预留资源;提交;锁定或者提交失败则回滚。这是从业务层面上实现的,优点是不依赖RM,每一步都是独立的事务,缺点是代码量巨大,每个事务参与方都要实现3个接口:try、confirm、cancel。
-
一般采取微服务治理的架构,不建议使用分阶段提交方案,因为各个微服务之间的数据库要保持独立,XA协议下,一个TM可以直接操作N个RM的情况有安全性风险。而且分阶段提交的方案,在并发性能上也不尽人意。
六、分开提交
6-1、所谓的分开提交
分段提交和TCC都在努力保证强一致性。
如果对数据一致性的要求没有那么严格,允许保证最终一致性即可的话,那么分布式事务的实现方法还是很多的。
下面的这种方案建立在 确保操作的幂等性 + 重试 + 事务日志 + 人工干预 的基础上。
其核心思路是:
1. 事务的发起方先本地提交事务;
2. 发起方调用远程事务(远程接口保证幂等性);
3. 调用失败则重试,调用成功则事务成功;
4. 调用失败次数过多则停止,记录日志、报警、等待人工干预。
6-2、详细过程
假设一个2个参与者的分布式事务操作:A自身事务执行完毕后调用B。
首先明确A调用B有3种结果:
- B执行成功;
- B执行失败;
- 调用B反馈超时(B可能未接收到消息、可能执行失败、可能执行成功)。
具体的实施步骤:
1. A开启一个新的全局事务。需要做的操作为生成一个全局唯一的事务ID(TXID);该事务状态置为“已开启”;记录分布式事务日志。
2. A执行自身的局部事务处理;成功后将TXID状态变更为“A成功”,开始调用B系统。
3. 调用B系统时传递TXID,B借助TXID和相应状态为依据保证自身的幂等性;B开始执行/重试自身的局部事务处理。
4. B的局部事务提交成功后,更新分布式事务状态为“成功”,并将结果返回给A。
5. B的重试失败次数超过阈值以后,更新分布式事务状态为“重试失败”,并将结果返回给A,A自身进行回滚处理。
6. 在A调用B超时的情况下,A可以先查看事务状态,如果状态未改变,说明B未收到通讯,则发起重试;如果状态为“重试失败”,则A直接回滚自身的业务处理,返回“失败”给调用层;如果状态为“成功”,说明分布式事务执行完毕,返回“成功”给调用层。
7. 定时轮询分布式事务管理数据,排查状态为“重试失败”,或者从事务开启经过了一段时间但是状态没有变为最终态的事务。通过邮件、短信报警,进行人工干预。
网友评论