文章目录
- 1 Seata
- 1.1 简介
- 1.2 架构
- 1.3 四种事务模式
- 1.3.1 XA
- 1.3.1.1 定义
- 1.3.1.2 优缺点
- 1.3.1.3 代码中实现
- 1.3.2 AT
- 1.3.2.1 定义
- 1.3.2.2 全局锁
- 1.3.2.2.1 AT模式脏写问题
- 1.3.2.2.2 全局锁
- 1.3.2.3 AT模式优缺点
- 1.3.2.4 与XA模式区别
- 1.3.2.5 代码中实现
- 1.3.3 TCC模式
- 1.3.3.1 定义
- 1.3.3.2 TCC模式优缺点
- 1.3.3.3 代码中实现
- 1.3.3.3.1 业务需求
- 1.3.3.3.2 增加表
- 1.3.3.3.3 新建接口编写try、confirm、cancel接口
- 1.3.4 Saga模式
- 1.3.4.1 定义
- 1.3.4.2 Saga模型优缺点
- 1.4 模式对比
- 1.4.1 4种模式对比
- 1.4.2 AT和TCC模式区别
1 Seata
学习此文章前需要先 点击了解CAP,2PC,3PC,TCC
1.1 简介
Seata
是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata
将为用户提供了 AT
、TCC
、SAGA
和 XA
事务模式,为用户打造一站式的分布式解决方案。
点击了解Seata搭建
1.2 架构
seata
事务管理中有三个重要的角色:
TC(Transaction Coordinator)
:事务协调者:维护全局和分支事务状态,协调全局事务提交或回滚。TM(Transaction Manager)
:事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。RM(Resource Manager)
:资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
1.3 四种事务模式
在继续学习使用SEATA之前,对seata介绍中提到的分布式事务 AT、TCC、SAGA 和 XA 事务模式这些名词有必要介绍一下
1.3.1 XA
1.3.1.1 定义
XA
规范是X/open
组织定义的分布式事务处理(DTP
)标准,XA
规范描述了全局的TM
与局部之间的接口,几乎所有的主流的数据库都对XA
规范提供了支持。
seata的XA模式
RM
一阶段工作:
- 注册分支事务到
TC
- 执行分支业务sql但不提交
- 报告执行状态到
TC
TC
二阶段的工作:
TC
检测各分支事务执行状态
- 如果都成功,通知所有RM提交事务
- 如果有失败,通知所有RM回滚事务
RM
二阶段的工作:
接收 TC
的指令,提交或回滚事务
1.3.1.2 优缺点
优点:
- 事务的强一致性,满足ACID原则。
- 常用的数据库都支持,实现简单,并且没有代码侵入
缺点:
- 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能极差。
- 依赖关系型数据库实现事务
1.3.1.3 代码中实现
seata
的starter
已经完成了XA模式的自动装配,实现非常简单,步骤如下
修改yml文件(每个参与事务的微服务)
seata:
data-source-proxy-mode: XA # 开启数据库源代理的XA模式
application
启动类上添加: @EnableAutoDataSourceProxy
需要事务的方法或类上添加:@GlobalTransactional(rollbackFor = Exception.class)
@GlobalTransactional
public void saveAll(){
User user=new User();
user.setName("test");
user.setAge(18);
baseMapper.insert(user);
//feign接口
orderFeignService.saveFeigh();
//手动控制异常回滚
//throw new RuntimeException("测试回滚");
}
1.3.2 AT
1.3.2.1 定义
AT
模式是一种无侵入的分布式事务解决方案。在 AT
模式下,用户只需关注自己的 业务 SQL
,用户的 业务 SQL
作为一阶段,Seata
框架会自动生成事务的二阶段提交和回滚操作
AT
模式同样是分阶段提交的事务模型,不过弥补了XA
模型中资源锁定周期过长的缺陷
阶段一RM的工作:
- 注册分支事务
- 记录undo-log(数据快照)
- 执行业务sql并提交
- 报告事务状态
阶段二提交时RM的工作:
- 删除undo-log即可
阶段二回滚时RM的工作:
- 根据undo-log恢复数据到更新前
1.3.2.2 全局锁
1.3.2.2.1 AT模式脏写问题
两个事务同时进行时,没有做到事务隔离性,如下事务1已经减10,事务二也减10了,但是事务1异常回滚了,恢复快照,也只能恢复事务1的恢复成100。
1.3.2.2.2 全局锁
AT
模式用全局锁来解决上方问题。两个事务同时处理时,先获取全局锁的事务1
会等待事务2
释放DB锁
,事务2获取不到全局锁,会有300ms
的时间重试,一直获取不到就释放DB锁,事务1就能获取到DB锁,恢复数据了。
极端模式:
保存快照时会保存修改前和修改后的快照,万一事务一恢复数据时,发现现在的值和快照不一样,已经被动过,就会记录异常发送警告,由人工来处理
1.3.2.3 AT模式优缺点
AT模式的优点:
- 一阶段完成直接提交事务,释放资源数据库资源,性能比较好
- 利用全局锁实现读写隔离
- 没有代码侵入,框架自动完成回滚和提交
AT模式缺点:
- 两阶段之间属于软状态,属于最终一致
- 框架的快照功能会影响性能,但比
XA
模式要好很多
1.3.2.4 与XA模式区别
与XA模式的最大区别:
XA
模式一阶段不提交事务,锁定资源;AT
模式一阶段直接提交,不锁定资源。XA
模式依赖数据库机制实现回滚;AT
模式利用数据快照实现数据回滚。XA
模式强一致;AT
模式最终一致
1.3.2.5 代码中实现
application
启动类上添加: @EnableAutoDataSourceProxy
需要事务的方法或类上添加:@GlobalTransactional(rollbackFor = Exception.class)
在XA模式配置基础下,只需修改data-source-proxy-mode: AT
seata:
data-source-proxy-mode: AT# 开启数据库源代理的XA模式
业务表中增加
undo_log` 表
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`branch_id` bigint(20) NOT NULL COMMENT '分支事务ID',
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '全局事务ID',
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '上下文',
`rollback_info` longblob NOT NULL COMMENT '回滚信息',
`log_status` int(11) NOT NULL COMMENT '状态,0正常,1全局已完成',
`log_created` datetime(6) NOT NULL COMMENT '创建时间',
`log_modified` datetime(6) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
1.3.3 TCC模式
1.3.3.1 定义
TCC
模式与 AC
模式非常相似,每阶段都是独立事务,不同的是 TCC
通过人工编码来实现数据恢复。需要实现三个方法:
Try
:资源的检测和预留Confirm
:完成资源操作业务;要求Try
成功Confirm
一定要能成功。Cancel
:预留资源释放,可以理解为try
的反向操作
seata中TCC工作模型:
1.3.3.2 TCC模式优缺点
TCC
模式优点:
- 一阶段完成直接提交事务,释放数据库资源,性能好
- 相比
AT
模型,无需生产快照,无需使用全局锁,性能最强 - 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
TCC
模式缺点:
- 有代码侵入,需要人为编写
try
、Confirm
和Cancel
接口,太麻烦 - 软状态,事务是最终一致
- 需要考虑
Confirm
和Cancel
的失败情况,失败seata
会重试,可能重复处理,需要做好幂等处理
1.3.3.3 代码中实现
1.3.3.3.1 业务需求
需修改如下:
- 修改方法,编写
try、confim、cancel
逻辑 - 保证
confirm、cancel
接口的幂等性,幂等处理就是防止重复处理 - 允许
空回滚
当某分支事务的try
阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel
操作。在未执行try
操作时先先执行了cancel
操作,这时cancel
不能做回滚,这就是空回滚
。
- 拒绝业务悬挂
对于已空回滚的业务,如果以后继续执行try
,就永远不可能confirm
和cancel
,这就是业务悬挂。应当阻止执行空回滚后的try
操作,避免悬挂。
1.3.3.3.2 增加表
为了实现空回滚,防止业务悬挂,以及幂等性要求。我们必须在数据库操作的同时,记录当前事务id
和执行状态,为此我们设计了一张表(自己根据业务设计,主要为了存事务状态,和业务表相关字段)
DROP TABLE IF EXISTS `tcc`;
CREATE TABLE `tcc` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`business_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '业务id',
`state` tinyint(1) NULL DEFAULT NULL COMMENT '事务状态,0:try , 1:confirm , 2:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
1.3.3.3.3 新建接口编写try、confirm、cancel接口
@LocalTCC
public interface InsertTCCService {
/**
* try逻辑,@TwoPhaseBusinessAction中的name属性要与当前方法名一致,用于指定try逻辑对应的方法
*/
@TwoPhaseBusinessAction(name = "insert",commitMethod = "confirm",rollbackMethod = "cancel")
void insert(@BusinessActionContextParameter(paramName = "name") String name,
@BusinessActionContextParameter(paramName = "age")Integer age);
/**
* 二阶段confirm确认方法,可以另命名,倒要保证与commitMethod一致
* @param context 上下文,可与i传递try方法的参数
* @return
*/
boolean confirm(BusinessActionContext context);
/**
* 二阶段cancel回滚方法,可以另命名,倒要保证与rollbackMethod一致
* @param context
* @return
*/
boolean cancel(BusinessActionContext context);
}
新建实现类,实现上方接口,编写业务
@Service
public class InsertTCCServiceImpl extends ServiceImpl<InsertTCCMapper, Tcc> implements InsertTCCService {
@Autowired
CouponService couponService;
@Override
public void insert(String name, Integer age) {
// 防止业务悬挂,先判断有没有tcc表中有没有记录,如果有,一定是cancel执行过,要拒绝业务
// 1、获取事务id
String xid = RootContext.getXID();
// 2、判断是否有记录
Tcc byId = this.getById(xid);
if (byId!=null){
// cancel执行过,拒绝执行业务
return;
}
// 业务
TCoupon tCoupon = new TCoupon();
tCoupon.setName(name);
tCoupon.setAge(age);
couponService.save(tCoupon);
// 记录事务状态和业务id
Tcc tcc = new Tcc();
tcc.setBusinessId(tCoupon.getId());
tcc.setState(0); // 0是try
this.save(tcc);
}
@Override // 成功的方法
public boolean confirm(BusinessActionContext context) {
// 获取事务id
String xid = context.getXid();
// 根据id删除tcc记录
boolean b = this.removeById(xid);
return b;
}
@Override // try的反向,恢复业务修改的数据
public boolean cancel(BusinessActionContext context) {
// 获取事务id
String xid = context.getXid();
// 查询tcc表,获取我们寸的businessId
Tcc tcc = this.getById(xid);
// -----空回滚的判断,为null证明try没执行,需要空回滚,就是再添加一条tcc记录,state存cancel-----
if (tcc == null){
// 记录事务状态和业务id
tcc = new Tcc();
tcc.setBusinessId(context.getActionContext("name").toString()); // 从try传的参数获取,这个字段值要唯一
tcc.setState(2); // 2是cancel
this.save(tcc);
return true;
}
// ----幂等处理,状态判断,防止重复处理-------
if (tcc.getState() == 2){ // 已经提交过了
return true;
}
// 根据businessId删除我们业务里新增的数据
boolean b = couponService.removeById(tcc.getBusinessId());
return b;
}
}
需要事务的方法或类上(事务发起方TM)添加 @GlobalTransactional
注解
在接口
上添加 @LocalTCC
注解,注:该注解添加在接口
上,而不是实现类上
方法上添加@TwoPhaseBusinessAction
定义两阶段提交
该注解属性:
name
:定义一个全局唯一值,一般为该方法名commitMethod
:自定义提交方法名(二阶段提交),需和提交方法名保持一致rollbackMethod
:自定义回滚方法名(二阶段回滚),需和
@BusinessActionContextParameter
加上该注解可传递参数到二阶段方法BusinessActionContext
上下文 可取到@BusinessActionContextParameter
定义的参数
1.3.4 Saga模式
1.3.4.1 定义
Saga
模式是 SEATA
提供的长事务解决方案,在 Saga
模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
Saga
模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga
模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。
事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC
要求的接口,可以使用 Saga 模式。
saga
模式是 seata
提供的长事务解决方案,也分为两个阶段:
- 一阶段:直接提交本地事务
- 二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚。
1.3.4.2 Saga模型优缺点
Saga模式优点:
- 事务参与者可以基于事件驱动实现异步调用,吞吐高
- 一阶段可以直接提交事务,无锁,性能好
- 不用编写
TCC
中的三个阶段,实现简单 - 补偿服务即正向服务的“反向”,易于理解,易于实现;
Saga
缺点:
- 软状态持续时间不长,失效性差
- 没有锁,没有事务隔离,会有脏写
Saga
模式由于一阶段已经提交本地数据库事务,且没有进行预留
动作,所以不能保证隔离性
1.4 模式对比
1.4.1 4种模式对比
XA | AT | TCC | SAGA | |
---|---|---|---|---|
一致性 | 强一致性 | 弱一致(也是最终一致) | 弱一致(也是最终一致) | 最终一致 |
隔离性 | 完全隔离 | 基于全局锁隔离 | 基于资源预留隔离 | 无隔离 |
代码侵入 | 无 | 无 | 有,要编写三个接口 | 有,要编写状态机和补偿业务 |
性能 | 差 | 好 | 非常好 | 非常好 |
场景 | 对一致性、隔离性有高业务的需求 | 基于关系型数据库的大多数分布式事务场景都可以 | 1、对性能要求较高的业务。2、有非关系型数据库要参与的业务 | 1、业务流程长、业务流程多。2、参与者包含其他公司或遗留系统服务,无法提供TCC模式要求的三个接口 |
1.4.2 AT和TCC模式区别
一个分布式的全局事务,整体式两阶段提交(Try-[Comfirm/Cancel])的模型,在seata
中,AT
模式与TCC
模式事实上都是基于两阶段提交实现的,它们的区别在于:
- AT模式基于支持本地
ACID
事务的关系型数据库:- 一阶段
prepare
行为:在本地事务中,一并提交“业务数据更新”和“相应回滚日志记录”; - 二阶段
commit
行为:马上成功结束,自动异步批量清理回滚日志; - 二阶段
rollback
行为:通过回滚日志,自动生成补偿操作,完成数据回滚;
- 一阶段
- 相应的,
TCC
模式需要我们认为编写代码实现提交和回滚:- 一键端
prepare
行为:调用自定义的prepare
逻辑(如插入、删除、修改等操作); - 二阶段
commit
行为:调用自定义的commit
逻辑; - 二阶段
rollback
行为:调用自定义的rollback
逻辑;
- 一键端
所以TCC
模式就是把自定义的分支事务(提交
和回滚
)纳入到全局事务管理中,seata
中的TCC
模式就是手动版的AT
模式,它允许自定义两阶段的处理逻辑而不需要依赖AT
模式的undo_log
回滚表
参考链接:
https://blog.csdn.net/qq_48721706/article/details/122656490
https://blog.csdn.net/weixin_44152047/article/details/118110391
https://baijiahao.baidu.com/s?id=1712163514749816361&wfr=spider&for=pc