目录
前言
在分布式系统中,实现一个功能可能需要由几个不同的服务来共同实现。这就会带来一个问题,不同的服务之间无法做到使用同一个事务,这就无法保证数据的一致性了。在一些对数据一致性要求不要的业务场景中,可以基于本地消息表来实现分布式事务,只需要保证最终一致性就行了。
基于本地消息表实现分布式事务(最终一致性)_本地消息表处理分布式事务-CSDN博客文章浏览阅读1k次,点赞33次,收藏11次。传统单体架构下,所有的功能模块都在一个应用下,所有的代码和业务逻辑都在同一个应用下实现,所以保证数据的一致性就很简单,保证相关操作都在同一个本地事务下就可以了。但是在微服务架构下,将一个应用拆分成了多个独立的服务,每个服务都能有自己的数据库,服务间通信都是通过远程调用实现,实现一个功能可能需要由几个不同的服务来共同实现。这就会带来一个问题,不同的服务之间无法做到使用同一个事务,这就无法保证数据的一致性了。_本地消息表处理分布式事务https://blog.csdn.net/typeracer/article/details/140998899但是也存在一些需要强一致性的业务场景,实际工作中开发过一个事件设计器,设计器中支持各种业务节点,对节点进行编排从而来自定义业务逻辑。不同的节点的实现需要调用不同的服务来实现,故存在分布式事务的问题。对各个节点的服务调用,需要保证所有事务的原子性,要么一起提交,要么一起回滚,所以最后通过引入Seata来解决
在学习 Seata 时,对各种事务模式都做了了解,对比后才能为系统选择最合适的事务模式。在事件设计器中,由于业务对实时性要求不高,所以直接使用XA模式,集成最简单,没有代码侵入。
Seata架构
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
分布式事务基本流程
事务模式
- XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
- AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
- TCC模式:最终一致的分阶段事务模式,有业务侵入
- SAGA模式:长事务模式,有业务侵入
XA
plantuml
@startuml 'https://plantuml.com/sequence-diagram autonumber 1.1 participant TM participant RM participant TC TM -> TC: 开启全局事务 TM <-- TC: XID TM -> RM: 远程调用 XID RM -> TC: 注册分支事务 RM -> RM: 执行业务sql RM -> TC: 报告分支事务状态 autonumber inc A TM -> TC: 提交/回滚全局事务 TC -> TC: 检查分支事务状态 RM <- TC: 提交/回滚 @enduml
优点
- 事务的强一致性,满足ACID原则。
- 常用数据库都支持,实现简单,并且没有代码侵入
缺点
- 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
- 依赖关系型数据库实现事务
AT
AT模式是Seata的默认事务模式
在使用前,需要在对应的业务库中创建一张 undo_log 表,用来保存快照
建表语句(MySQL)
CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
流程图
plantuml
@startuml 'https://plantuml.com/sequence-diagram autonumber 1.1 database undo_log participant TM participant RM participant TC TM -> TC: 开启全局事务 TM <-- TC: XID TM -> RM: 远程调用 XID RM -> TC: 注册分支事务 RM -> RM: 执行业务sql并提交 undo_log <- RM: 记录更新前后快照 RM -> TC: 报告分支事务状态 autonumber inc A TM -> TC: 提交/回滚全局事务 TC -> TC: 检查分支事务状态 RM <- TC: 提交/回滚 undo_log <- RM: 删除log/回复log数据 @enduml
优点
- 一阶段完成直接提交事务,释放数据库资源,性能比较好
- 没有代码侵入,框架自动完成回滚和提交
缺点
- 软状态,事务是最终一致
- 框架的快照功能会影响性能,但比XA模式要好很多
TCC
TCC模式将事务拆成了三个接口实现(try,commit,cancel),同时也带来了以下三个问题
- 幂等:在 commit/cancel 阶段,因为 TC 没有收到分支事务的响应,需要进行重试,这就要分支事务支持幂等
- 空回滚:在 try 阶段发生了故障,try 阶段在不考虑重试的情况下,全局事务必须要走向结束状态,这样就需要执行一次 cancel 操作
- 悬挂:因为网络问题,RM 开始没有收到 try 指令,但是执行了 cancel 后 RM 又收到了 try 指令并且预留资源成功,这时全局事务已经结束,最终导致预留的资源不能释放
在1.5.1以前,这些问题需要自己在代码中解决,而1.5.1版本以后(包含1.5.1),Seata就自己解决这些问题了
需要在对应的业务库中创建一张 tcc_fence_log 表
建表语句(MySQL)
CREATE TABLE IF NOT EXISTS `tcc_fence_log` ( `xid` VARCHAR(128) NOT NULL COMMENT 'global id', `branch_id` BIGINT NOT NULL COMMENT 'branch id', `action_name` VARCHAR(64) NOT NULL COMMENT 'action name', `status` TINYINT NOT NULL COMMENT 'status(tried:1;committed:2;rollbacked:3;suspended:4)', `gmt_create` DATETIME(3) NOT NULL COMMENT 'create time', `gmt_modified` DATETIME(3) NOT NULL COMMENT 'update time', PRIMARY KEY (`xid`, `branch_id`), KEY `idx_gmt_modified` (`gmt_modified`), KEY `idx_status` (`status`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
流程图
plantuml
@startuml 'https://plantuml.com/sequence-diagram autonumber 1.1 database tcc_fence_log participant TM participant RM participant TC TM -> TC: 开启全局事务 TM <-- TC: XID TM -> RM: 远程调用 XID RM -> TC: 注册分支事务 RM -> RM: 锁定资源(try) tcc_fence_log <- RM: 插入防空回滚记录 note left 插入一条记录 status值为STATUS_TRIED 在 Rollback 阶段判断记录是否存在 如果不存在,则不执行回滚操作 end note RM -> TC: 报告分支事务状态 autonumber inc A TM -> TC: 提交/回滚全局事务 TC -> TC: 检查分支事务状态 RM <- TC: 提交/回滚 tcc_fence_log <- RM: 幂等判断 note left 判断 tcc_fence_log 表中是否已经有记录 如果有记录,则判断事务执行状态是否为 STATUS_COMMITTED 是的话就不会再次提交,保证了幂等 如果 tcc_fence_log 表中没有记录 则插入一条记录,供后面重试时判断。 end note tcc_fence_log <- RM: 插入防悬挂记录 note left 执行 cancel 方法时 判断 tcc_fence_log 是否存在当前 xid 的记录 如果没有则向 tcc_fence_log 表插入一条记录 状态是 STATUS_SUSPENDED,并且不再执行回滚操作 后面执行 try 阶段方法时首先会向 tcc_fence_log 表插入一条当前 xid 的记 这样就造成了主键冲突 end note RM <- RM: 提交(commit)/回滚(cancel) @enduml
优点
- 一阶段完成直接提交事务,释放数据库资源,性能好
- 相比AT模型,无需生成快照,无需使用全局锁,性能最强
- 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
缺点
- 有代码侵入,需要人为编写try、Confirm和Cancel接口
- 软状态,事务是最终一致
区别
XA | AT | TCC | SAGA | |
一致性 | 强一致 | 弱一致 | 弱一致 | 最终一致 |
隔离性 | 完全隔离 | 基于全局锁隔离 | 基于资源预留隔离 | 无隔离 |
代码侵入 | 无 | 无 | 有,要编写三个接口 | 有,要编写状态机和补偿业务 |
性能 | 差 | 好 | 非常好 | 非常好 |
场景 | 对一致性、隔离性有高要求的业务 | 基于关系型数据库的大多数分布式事务场景 对性能要求较高的事务 | 有非关系型数据库要参与的事务 | 业务流程长、业务流程多 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口 |