最新Seata集成了RocketMQ事务消息yyds
一:回顾Seat AT 模式
Seata AT模式是基于XA事务演进而来的一个分布式事务中间件,
XA是一个基于数据库实现的分布式事务协议,本质上和两阶段提交一样,需要数据库支持,Mysql5.6以上版本支持XA协议,其他数据库如Oracle,DB2也实现了XA接口
AT模式角色如下
1、Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
2、Transaction Manager ™:
控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
3、Resource Manager (RM):
控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
来自官方的图:
分支事务 处理逻辑如下
Branch就是指的分布式事务中每个独立的本地局部事务
AT模式第一阶段 Prepare 阶段
看看Prepare 阶段 简单的图
Seata 的 JDBC 数据源代理通过对业务 SQL 的解析,把业务数据在更新前后的数据镜像组织成回滚日志,利用 本地事务 的 ACID 特性,将业务数据的更新和回滚日志的写入在同一个 本地事务 中提交。
这样,可以保证:任何提交的业务数据的更新一定有相应的回滚日志存在
基于这样的机制,分支的本地事务便可以在全局事务的第一阶段提交,并马上释放本地事务锁定的资源
这也是Seata和XA事务的不同之处:
经典的2PC两阶段提交(XA)往往对资源的锁定需要持续到第二阶段实际的提交或者回滚操作,
AT模式,可以在第一阶段释放对资源的锁定,降低了锁范围
谁的功劳:回滚日志
AT模式第二阶段
看看 commit/rollback 阶段 简单的图
两种情况
场景一:提交,全局提交
如果决议是全局提交,此时分支事务此时已经完成提交,不需要同步协调处理(只需要异步清理回滚日志),Phase2 可以非常快速地完成
场景二:回滚,全局回滚
如果决议是全局回滚,RM 收到协调器发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚
AT模式相对于XA模式的优势
提高效率,即使第二阶段发生异常需要回滚,只需找对undolog中对应数据并反解析成sql来达到回滚目的
同时Seata无入侵,通过代理数据源将业务sql的执行解析成undolog来与业务数据的更新同时入库,达到了对业务无侵入的效果
秒杀实操的AT分布式事务架构
Seat AT 模式的不足
AT模式的依赖的还是依赖本地数据源的事务控制能力,完成分支事务的管理。
AT模式 采用的是wal的思想,提交事务的时候同时记录undolog,如果全局事务成功,则删除undolog,如果失败,则使用undolog的数据回滚分支事务,最后删除undolog。
Seat AT 模式的性能低,而且非常低。
二:回顾Seat TCC 模式
Seat TCC 模式 的性能会高些, 脱离了本地数据库事务, 需要业务控制 两个阶段操作的 原子性。
Seata TCC基本原理
TCC模式的特点是不再依赖于undolog,
Seata TCC模式的流程图
TCC模式还是采用2阶段提交的方式:
第一阶段使用prepare尝试事务提交,
第二阶段使用commit或者rollback让事务提交或者回滚。
引用网上一张TCC原理的参考图片
来自官方的图:
Seata TCC 事务的3个操作
TCC 将事务提交分为 Try - Confirm - Cancel 3个操作。
其和两阶段提交有点类似,Try为第一阶段,Confirm - Cancel为第二阶段,是一种应用层面侵入业务的两阶段提交。
操作方法 | 含义 |
---|---|
Try | 预留业务资源/数据效验 |
Confirm | 确认执行业务操作,实际提交数据,不做任何业务检查,try成功,confirm必定成功,需保证幂等 |
Cancel | 取消执行业务操作,实际回滚数据,需保证幂等 |
其核心在于将业务分为两个操作步骤完成。不依赖 RM 对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
下面还以银行转账例子来说明
假设用户user表中有两个字段:可用余额(available_money)、冻结余额(frozen_money)
A扣钱对应服务A(ServiceA)
B加钱对应服务B(ServiceB)
转账订单服务(OrderService)
业务转账方法服务(BusinessService)
ServiceA,ServiceB,OrderService都需分别实现try(),confirm(),cancle()方法,方法对应业务逻辑如下
ServiceA | ServiceB | OrderService | |
---|---|---|---|
try() | 校验余额(并发控制) 冻结余额+1000 余额-1000 | 冻结余额+1000 | 创建转账订单,状态待转账 |
confirm() | 冻结余额-1000 | 余额+1000 冻结余额-1000 | 状态变为转账成功 |
cancle() | 冻结余额-1000 余额+1000 | 冻结余额-1000 | 状态变为转账失败 |
其中业务调用方BusinessService中就需要调用
ServiceA.try()
ServiceB.try()
OrderService.try()
1、当所有try()方法均执行成功时,对全局事物进行提交,即由事物管理器调用每个微服务的confirm()方法 2、 当任意一个方法try()失败(预留资源不足,抑或网络异常,代码异常等任何异常),由事物管理器调用每个微服务的cancle()方法对全局事务进行回滚
TCC 模式第一阶段 try 阶段
看看给大家画的 try 阶段 简单的图
try 阶段 没有本地事务了。需要业务维护 本地操作的 原子性,一致性。
TCC 模式第二阶段
看看给大家画的第二阶段 简单的图
TCC 模式 第二 阶段 也没有本地的undo 日志支持。需要业务维护 完成 本地 的提交操作, 或者 回滚操作。
而 AT模式的话, 本地 的提交操作, 或者 回滚操作 是 proxy 代理结合 undo 日志 完成的, 是业务无感知的, 无入侵的。
TCC版本的秒杀的分布式事务架构
Seata TCC 事务的弱点
Seata TCC 事务脱离了对数据库的强依赖, 很明确来说,性能会高些。
但是 Seata TCC 事务的弱点不少,大致如下:
需要自定义 提交操作 + 回滚操作, 存在业务入侵。
需要处理 悬挂 问题
需要处理 空回滚问题
三:新功能 Seata + Rocketmq 事务消息实现 强弱结合型事务
首先看看 RocketMQ 的事务消息, 能够保证本地操作 + 消息发送的原子性。
具体来说, 主要是保证了本地方法执行和消息发送在一个分布式事务中,要不全部成功,要不全部失败。
见下图:
RocketMQ 通过发送 half 消息来实现,下面详细说明一下:
消息发送方 向 Broker 发送一条 half 消息;
half 消息发送成功后,消息发送方 执行本地事务;
如果 消息发送方 执行本地事务成功,则向 Broker 发送 commit 请求,否则发送 rollback 请求;
如果 Broker 收到的是 rollback 请求,则删除保存的 half 消息;
如果 Broker 收到的是 commit 请求,则把 half 消息投递到 真实 队列, 等待消费服务来拉取,然后删除保存的 half 消息;
如果 Broker 没有收到 rollback/commit 请求,则会发送请求到 Producer 查询本地事务状态,然后根据 Producer 返回的本地状态做 commit/rollback 相关处理。
Seata + Rocketmq 事务消息 结合
Seata + Rocketmq 事务消息 结合的目标:
一 是保证 分布式事务 + 消息 发送的原子性。
二 是 再通过mq的重试机制,去保证订阅者的最终一致性,
Seata 的改进主要在 prepare 阶段。
Seata 提供了一个 SeataMQProducer 类,把 RocketMQ 中 TransactionListener 的方法加入到全局事务。
见下面代码:
|
在 prepare 阶段,SeataMQProducer 向 RocketMQ Broker 发送 half 消息,执行本地事务,如果执行成功,则强行把 LocalTransactionState 改回 UNKNOW,等待 TC 发送指令,决定是 commit 或 rollback。
如果执行失败,返回 ROLLBACK_MESSAGE,TC 下发指令,回滚全局事务。
Seata + Rocketmq 事务消息 结合的使用场景
如果 MQ Broker 没有收到 commit/rollback 消息,则会回查 Producer 本地事务状态,也就是上面代码中的 checkLocalTransaction。
checkLocalTransaction 检查 全局事务状态,使用 XID 去查询 全局事务,去决定 half 消息 是 抛弃还是 投递 。
集成 RocketMQ 之后,Seata 的分布式事务调用流程, 下面以 订单服务、库存服务两个服务为例:
Apache Seata 引入 RocketMQ 后,支持的分布式事务场景更加丰富,使得 Seata 可以用于 强一致性 + 弱一致性 结合的场景。