分布式事务一直是微服务等分布式系统不得不面对的难题。目前主流的解决方案有以下几种。
- 基于阿里巴巴开源的seata AT模式分布式事务框架
- TCC 三段式提交事务方案(业界一般使用ByteTCC框架)
- 基于RocketMq 消息中间件实现最终一致性事务
下面基于这三种方式进行原理剖析对比选型以及各自的优缺点比较
一、Seata
- TC:事务协调器。控制全局事务的开启,提交回滚。
- TM:事务管理器,负责定义事务的边界,负责发起开启一个全局事务、提交事务,回滚事务。例如上图中。server-A调用server-B和server-C。则在server-A开启全局事务。
- RM:资源管理器。事务执行者,控制本分支事务的提交和回滚。
原理过程:
- TM像TC发起全局事务,TC生成一个全局的唯一事务XID,返回给TM,
- xid可以在服务调用的上下文中进行传播。
- RM 向TC注册分支事务,并且纳入该xid全局事务的范围内
- TM向TC发起全局事务的提交和回滚
- TC控制各个RM事务的提交和回滚
AT 两段式提交
- 上左图:阶段一提交成功,释放本地事务锁,阶段二什么也不做,因此seata比传统的两段式XA模式性能要好很多,真实业务中大部分事务也是成功的,没必要全程占有锁。
- 上右图:只有当第二阶段全局回滚时才两个阶段都占有锁。
提交和回滚
阶段一:
备注:这里的undo_log是一张表,不是mysql 的undo log日志文件
- 本地服务开始解析sql语句
- 开始查询即将要更新的数据,备份为数据镜像
- 开始执行业务sql
- 查询更新后的数据镜像
- 将更新前的数据镜像转成sql,插入到分布式事务数据库undo_log 表,这是重点,当全局事务失败后,事务协调器TC将会通知该RM,利用undo_log对应的数据进行回滚。
- 开启提交本地事务前,向TC注册本分支事务
- 提交分支事务
- 事务提交后,向TC报告该分支事务的状态
阶段一是每个RM都会提交的分支事务,但是有可能某个RM会执行失败。所以在第二阶段全局提交事务,有可能会成功,也有可能会失败,只要有一个RM失败,全局事务都会失败。
所以第二阶段分为2种情况:
全局事务成功:
第二阶段,如果全局事务成功,则直接删除undo_log对应的回滚数据日志即可,不需要再持有本地事务锁,所以Seata性能相对较高全局事务失败:
如果全局事务失败,则查找对应的undo_log日志回滚数据,然后删除undo_log日志优点:
- 实现简单,基本0代码耦合,只需要在TM的地方加一个全局事务注解即可。
- 性能优秀,第一阶段则释放本地锁
- TC可单独集群部署,架构清晰,高可用保证
- 使用简单,学习成本低,微服务可直接集成进来。
二、TCC
TCC 事务分为3个阶段,也称为三阶段提交
- T:try
- C:confirm
- C:cancle
一般会引入ByteTcc之类的框架,然后针对同一个接口的,写出对应上述的三种实现方式。
例如扣减库存业务,按照正常只直接扣除。
做了TCC后,需要拆解为3个步骤:
- try : 先不要直接减库存,可以用某个字段表示先冻结调需要扣减的部分
- confirm:如果try成功,则执行confirm逻辑,confirm里面则开始真正的扣减库存
- cancle: 如果try失败,则执行cancle实现代码,回滚之前的冻结数据。
这3个步骤均在ByteTcc 框架的协调下执行。
优点:严格保证分布式事务的执行,严格保证数据的准确性。当然,前提是三个阶段的的代码实现你得写对。适合绝对不能出错的金融业务。
缺点:方案落地实现麻烦,业务代码耦合超高。每个需要加事务的地方都都手写这3段代码。
二、基于RocketMq实现最终一致性
- half message: 半消息,也就是消息发送到rocket mq broker后,这条消息暂时不允许给消费者消费,需要再次对该消息发送comfirm 确认后才可消费
- service_A生产者发送一条半消息到mq,
- 如果mq返回成功,则证明通信正常,service_A 可以继续执行本地事务,如果发送半消息失败,则service_A 什么也不干
- 本地执行业务代码,提交本地事务。
- 提交事务有可能成功或者失败
- 如果本地事务失败,则发送fail信息到mq,删除前面发送的half massage,comsumer 消费不到该消息;如果本地事务成功,则发送success消息到mq,mq 则发送该消息给comsumer service_B 进行消费。
- 第5步有可能因为通信等原因没有发送success或者fail给mq, mq会定时扫描broker里面的half message,如果超时没有受到确认消息,则回回调Service_A 接口, 接口需要根据本地数据库实际情况,确认事务是否成功,然后在发送确认消息到mq。
- 如果消息发送成功,mq将尽最大努力将该消息发送给service_B 进行消费。
优点:实现简单,容易落地。
缺点:代码耦合度高,侵入性大,需要些回调代码。同时需要考虑mq的消息积压问题。
总结
综上,目前分布式事务中,seata是最合适的选择,实现简单,代码无侵入,学习成本低,性能高,可直接内嵌到微服务架构里面来
版权声明:本文为sishenhzy原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。