0%

Seata

Seata是什么?

Seata是2019年1月蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。

Seata架构

Seata事务管理中有三个重要角色
  • TC - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚
  • TM - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务
  • RM - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
Seata提供了四种不同的分布式事务解决方案
  • XA模式:强一致分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
  • SAGA模式:长事务模式,有业务侵入

XA模式

原理

XA规范是X/Open组织定义的分布式事务处理(DTP)标准,XA规范描述了全局的TM与局部RM之间的接口,几乎所有主流的数据库都对XA规范提供了支持

  • RM一阶段工作:
    • 注册分支事务到TC
    • 执行分支事务sql但不提交
    • 报告执行状态到TC
  • TC二阶段工作:
    • TC检测各分支事务执行状态
    • 判断提交或回滚
  • RM二阶段工作:
    • 接收TC指令,提交或回滚事务
实现XA模式

Seata的starter已经完成了XA模式的自动装配

  • 修改配置文件
1
2
seata:
data-source-proxy-mode: XA
  • 发起全局事务的入口添加@GlobalTransactional注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
@GlobalTransactional
public Long create(Order order) {
// 创建订单
orderMapper.insert(order);
try {
// 扣用户余额
accountClient.deduct(order.getUserId(), order.getMoney());
// 扣库存
storageClient.deduct(order.getCommodityCode(), order.getCount());

} catch (FeignException e) {
log.error("下单失败,原因:{}", e.contentUTF8(), e);
throw new RuntimeException(e.contentUTF8(), e);
}
return order.getId();
}

AT模式

原理

AT模式同样是分阶段提交的事务模型,不过弥补了XA模型中资源锁定周期过长的缺陷

  • RM一阶段工作:
    • 注册分支事务到TC
    • 记录undo-log(数据快照)
    • 执行分支事务sql并提交
    • 报告执行状态到TC
  • TC二阶段工作:
    • TC检测各分支事务执行状态
    • 判断提交或回滚,提交删除快照,回滚读取快照
  • RM二阶段回滚工作:
    • 根据undo-log恢复数据
AT模式的写隔离

全局锁:由TC(在数据库中)记录当前正在操作某行数据的事务,该事务持有全局锁,具备执行权

  • 事务1获取了全局锁,准备回滚,等待事务2释放DB锁(读写隔离)
  • 事务2持有DB锁,等待全局锁,重试默认30次,间隔10毫秒,超时释放(避免死锁)
  • 事务1获取DB锁,将数据库当前数据和更新后undo-log数据对比,判断是否一致

TCC模式

与AT模式相似,每阶段都是独立事务,但不加锁。不同的是TCC通过人工编码来实现数据恢复,需要实现三个方法

  • Try:资源的检测和预留,比如可用余额预留为冻结余额,不同的操作冻结的余额相互隔离
  • Confirm:完成资源操作业务;要求Try成功Confirm一定要成功
  • Cancel:预留资源释放,try的反向操作
优点
  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用非事务数据库
缺点
  • 有代码侵入,需要人为编写try、Confirm和Cancel接口
  • 软状态,最终一致
  • 需要考虑Confirm和Cancel失败情况,做好幂等处理
空回滚

当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作,在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚

业务悬挂

对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel,这就是业务悬挂。应当组织执行空回滚后try操作,避免悬挂

业务分析

为了实现空回滚,防止业务悬挂,以及幂等性要求。我们必须在数据库记录预留资源信息同时,记录当前事务id和执行状态

声明TCC接口,并实现三个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@Service
public class AccountTCCServiceImpl implements AccountTCCService {

@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper accountFreezeMapper;

@Override
@Transactional
public void deduct(String userId, int money) {
//获取事务id
String xid = RootContext.getXID();

//业务悬挂判断
AccountFreeze oldFreeze = accountFreezeMapper.selectById(xid);
if(oldFreeze != null) {
return;
}

//扣减可用余额
accountMapper.deduct(userId, money);
//记录冻结余额与事务状态
AccountFreeze freeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freeze.setXid(xid);
accountFreezeMapper.insert(freeze);
}

@Override
public boolean confirm(BusinessActionContext ctx) {
//获取事务id
String xid = ctx.getXid();
//根据id删除冻结记录
int count = accountFreezeMapper.deleteById(xid);

return count == 1;
}

@Override
public boolean cancel(BusinessActionContext ctx) {
//获取事务id,查询freeze对象
String xid = ctx.getXid();
String userId = ctx.getActionContext("userId").toString();
AccountFreeze freeze = accountFreezeMapper.selectById(xid);

//空回滚判断,判断freeze是否为null
if(freeze == null) {
AccountFreeze newFreeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
freeze.setXid(xid);
int count = accountFreezeMapper.insert(newFreeze);

return count == 1;
}

//幂等判断
if(freeze.getState().equals(AccountFreeze.State.CANCEL)) {
return true;
}

//恢复可用余额
accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());

//将冻结金额清零,状态改为CANCEL
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);

int count = accountFreezeMapper.updateById(freeze);

return count == 1;
}
}

Saga模式

Saga模式时SEATA提供的长事务解决方案。也分为两个阶段

  • 一阶段:直接提交事务,与TCC不同的是不做预留操作
  • 二阶段:失败则通过编写补偿业务来回滚
优点
  • 事务参与者可以基于事件驱动实现异步调用,吞吐高
  • 一阶段直接提交事务,无锁,性能好
  • 不用编写TCC中三个阶段,实现简单
缺点
  • 软状态持续时间不确定,时效性差
  • 无锁,无事务隔离,会有脏写
------ THEEND ------

欢迎关注我的其它发布渠道