一、Seata架构
1、Seata事务管理中有三个重要的角色:
-
TC (Transaction Coordinator) –
事务协调者:
维护全局和分支事务的状态,协调全局事务提交或回滚。 -
TM (Transaction Manager) –
事务管理器:
定义全局事务的范围、开始全局事务、提交或回滚全局事务。 -
RM (Resource Manager) –
资源管理器:
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
2、Seata基于上述架构提供了四种不同的分布式事务解决方案:
-
XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
-
TCC模式:最终一致的分阶段事务模式,有业务侵入
-
AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
-
SAGA模式:长事务模式,有业务侵入
无论哪种方案,都离不开TC,也就是事务的协调者。
三、部署集成
四、四种模式实践
如图所示为项目演示结构
一、XA模式
1、XA模式的优点是什么?
-
事务的强一致性,满足ACID原则。
-
常用数据库都支持,实现简单,并且没有代码侵入
2、XA模式的缺点是什么?
-
因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
-
依赖关系型数据库实现事务
3、实现XA模式
Seata的starter已经完成了XA模式的自动装配,实现非常简单,步骤如下:
1、修改application.yml文件(每个参与事务的微服务),开启XA模式:
seata:
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-server
username: nacos
password: nacos
tx-service-group: seata-demo # 事务组名称
service:
vgroup-mapping: # 事务组与cluster的映射关系
seata-demo: SH
data-source-proxy-mode: XA #开启XA模式
2、全局事务的入口方法添加@GlobalTransactional注解:
二、AT模式
1、AT模式的优点:
-
一阶段完成直接提交事务,释放数据库资源,性能比较好
-
利用全局锁实现读写隔离
-
没有代码侵入,框架自动完成回滚和提交
2、AT模式的缺点:
-
两阶段之间属于软状态,属于最终一致
-
框架的快照功能会影响性能,但比XA模式要好很多
3、实现AT模式
AT模式中的快照生成、回滚等动作都是由框架自动完成,没有任何代码侵入,因此实现非常简单。只不过,AT模式需要一个表来记录全局锁、另一张表来记录数据快照undo_log。
修改application.yml文件,将事务模式修改为AT模式即可:
seata:
data-source-proxy-mode: AT # 默认就是AT
三、TCC模式
1、TCC模式的每个阶段是做什么的?
-
Try:资源检查和预留
-
Confirm:业务执行和提交
-
Cancel:预留资源的释放
2、TCC的优点是什么?
-
一阶段完成直接提交事务,释放数据库资源,性能好
-
相比AT模型,无需生成快照,无需使用全局锁,性能最强
-
不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
3、TCC的缺点是什么?
-
有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
-
软状态,事务是最终一致
-
需要考虑Confirm和Cancel的失败情况,做好幂等处理
4、事务悬挂和空回滚
空回滚:
当某分支事务的try阶段
阻塞
时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是
空回滚
。
执行cancel操作时,应当判断try是否已经执行,如果尚未执行,则应该空回滚。
业务悬挂:
对于已经空回滚的业务,之前被阻塞的try操作恢复,继续执行try,就永远不可能confirm或cancel ,事务一直处于中间状态,这就是
业务悬挂
。
执行try操作时,应当判断cancel是否已经执行过了,如果已经执行,应当阻止空回滚后的try操作,避免悬挂
5、实现TCC模式
解决空回滚和业务悬挂问题,必须要记录当前事务状态,是在try、还是cancel?
第一步:定义表
CREATE TABLE `account_freeze_tbl` (
`xid` varchar(128) NOT NULL,
`user_id` varchar(255) DEFAULT NULL COMMENT '用户id',
`freeze_money` int(11) unsigned DEFAULT '0' COMMENT '冻结金额',
`state` int(1) DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
第二步:业务分析
-
Try业务:
-
记录冻结金额和事务状态到account_freeze表
-
扣减account表可用金额
-
-
Confirm业务
-
根据xid删除account_freeze表的冻结记录
-
-
Cancel业务
-
修改account_freeze表,冻结金额为0,state为2
-
修改account表,恢复可用金额
-
-
如何判断是否空回滚?
-
cancel业务中,根据xid查询account_freeze,如果为null则说明try还没做,需要空回滚
-
-
如何避免业务悬挂?
-
try业务中,根据xid查询account_freeze ,如果已经存在则证明Cancel已经执行,拒绝执行try业务
-
接下来,我们改造account-service,利用TCC实现余额扣减功能。
第三步:声明TCC接口
TCC的Try、Confirm、Cancel方法都需要在接口中基于注解来声明。
接口:
@LocalTCC
public interface AccountTCCService {
@TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money")int money);
boolean confirm(BusinessActionContext ctx);
boolean cancel(BusinessActionContext ctx);
}
实现类:
@Service
@Slf4j
public class AccountTCCServiceImpl implements AccountTCCService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper;
@Override
@Transactional
public void deduct(String userId, int money) {
// 0.获取事务id
String xid = RootContext.getXID();
// 1.扣减可用余额
accountMapper.deduct(userId, money);
// 2.记录冻结金额,事务状态
AccountFreeze freeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freeze.setXid(xid);
freezeMapper.insert(freeze);
}
@Override
public boolean confirm(BusinessActionContext ctx) {
// 1.获取事务id
String xid = ctx.getXid();
// 2.根据id删除冻结记录
int count = freezeMapper.deleteById(xid);
return count == 1;
}
@Override
public boolean cancel(BusinessActionContext ctx) {
// 0.查询冻结记录
String xid = ctx.getXid();
AccountFreeze freeze = freezeMapper.selectById(xid);
// 1.恢复可用余额
accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
// 2.将冻结金额清零,状态改为CANCEL
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
int count = freezeMapper.updateById(freeze);
return count == 1;
}
}
四、SAGA模式
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
Seata官网对于Saga的指南:
Seata SAGA 模式
1、优点:
-
事务参与者可以基于事件驱动实现异步调用,吞吐高
-
一阶段直接提交事务,无锁,性能好
-
不用编写TCC中的三个阶段,实现简单
2、缺点:
-
软状态持续时间不确定,时效性差
-
没有锁,没有事务隔离,会有脏写
五、四种模式对比
有不懂,需要完整代码可以私信我…………