TCC 是分布式事务中的二阶段提交协议,它的全称为 Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),他们的具体含义如下:
Try:对业务资源的检查并预留。
Confirm:对业务处理进行提交,即 commit 操作,只要 Try 成功,那么该步骤一定成功。
Cancel:对业务处理进行取消,即回滚操作,该步骤回对 Try 预留的资源进行释放。
TCC 是一种侵入式的分布式事务解决方案,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但优点是 TCC 完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。
https://seata.io/zh-cn/docs/dev/mode/tcc-mode
侵入性比较强,并且需要自己实现相关事务控制逻辑。
在整个过程基本没有锁,性能较强。
Order服务同时担任TM和RM角色。
TM角色下@GlobalTransactional负责管理全局事务。
@GlobalTransactional //开启全局事务
@Override
public void create(Order order) {
orderService.prepareCreateOrder(null,
order.getId(),
order.getUserId(),
order.getProductId(),
order.getCount(),
order.getMoney());
}
Seata 实现 TCC 操作需要定义一个接口,在接口中添加以下方法:
Try - prepareCreateOrder() --方法的名称可以根据实际业务指定
Confirm - commit()
Cancel - rollback()
RM角色下LocalTccAction被@LocalTCC+@TwoPhaseBusinessAction标注,作为一个TCC资源向TC注册,管理分支事务注册、提交和回滚
@LocalTCC
public interface OrderService {
/*
第一阶段的方法
通过注解指定第二阶段的两个方法名
BusinessActionContext 上下文对象,用来在两个阶段之间传递数据
@BusinessActionContextParameter 注解的参数数据会被存入 BusinessActionContext
*/
@TwoPhaseBusinessAction(name = "orderTccService", commitMethod = "commit", rollbackMethod = "rollback")
boolean prepareCreateOrder(BusinessActionContext businessActionContext,
@BusinessActionContextParameter(paramName = "orderId") Long orderId,
@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "productId") Long productId,
@BusinessActionContextParameter(paramName = "count") Integer count,
@BusinessActionContextParameter(paramName = "money") BigDecimal money);
// 第二阶段 - 提交
boolean commit(BusinessActionContext businessActionContext);
// 第二阶段 - 回滚
boolean rollback(BusinessActionContext businessActionContext);
}
@Component
@Slf4j
public class OrderTccServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Transactional
@Override
public boolean prepareCreateOrder(BusinessActionContext businessActionContext, Long orderId, Long userId, Long productId, Integer count, BigDecimal money) {
//插入订单 设置状态为0 --冻结
orderMapper.create(new Order(orderId,userId,productId,count,money,0));
log.info("创建订单第一阶段 冻结订单成功");
//第一阶段成功 添加一个标识
ResultHolder.setResult(OrderTccAction.class,businessActionContext.getXid(),"p");
return true;
}
@Transactional
@Override
public boolean commit(BusinessActionContext businessActionContext) {
//判断第一阶段的成功标记,没有标记则不执行提交操作
if (ResultHolder.getResult(OrderTccAction.class,businessActionContext.getXid())==null){
return true;
}
//修改订单状态 0---》1 正常状态的转变
//通过订单id
Long orderId = Long.parseLong(businessActionContext.getActionContext("orderId").toString());
orderMapper.updateStatus(orderId,1);
log.info("创建订单第二阶段 提交订单 解冻成功");
//删除标识 防止一直重复提交
ResultHolder.removeResult(OrderTccAction.class,businessActionContext.getXid());
return true;
}
@Transactional
@Override
public boolean rollback(BusinessActionContext businessActionContext) {
//第一阶段没有完成的情况下,不必执行回滚
//因为第一阶段有本地事务,事务失败时已经进行了回滚。
//如果这里第一阶段成功,而其他全局事务参与者失败,这里会执行回滚
//幂等性控制:如果重复执行回滚则直接返回
log.info("创建 order 第二阶段回滚,删除订单 - "+businessActionContext.getXid());
//判断第一阶段的成功标记,没有标记则不执行提交操作
if (ResultHolder.getResult(OrderTccAction.class,businessActionContext.getXid())==null){
return true;
}
//通过订单id
Long orderId = Long.parseLong(businessActionContext.getActionContext("orderId").toString());
orderMapper.deleteById(orderId);
log.info("创建订单第二阶段 回滚订单 删除成功");
//删除标识 防止一直重复提交
ResultHolder.removeResult(OrderTccAction.class,businessActionContext.getXid());
return true;
}
}
Account库存服务是纯粹的一个RM,负责管理分支事务的提交和回滚。
@Override
public void decrease(Long userId, BigDecimal money) {
//调用TCC 第一阶段的方法
accountTccService.prepareDecreaseAccount(null,userId,money);
}
@LocalTCC
public interface AccountTccService {
@TwoPhaseBusinessAction(name = "accountTccService ")
boolean prepareDecreaseAccount(BusinessActionContext businessActionContext,
@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "money")BigDecimal money
);
boolean commit(BusinessActionContext businessActionContext);
boolean rollback(BusinessActionContext businessActionContext);
}
@Component
@Slf4j
public class AccountTccServiceImpl implements AccountTccService {
@Autowired
private AccountMapper accountMapper;
@Transactional
@Override
public boolean prepareDecreaseAccount(BusinessActionContext businessActionContext, Long userId, BigDecimal money) {
log.info("扣减金额第一阶段 ,开始执行冻结金额操作");
Account account = accountMapper.selectById(userId);
if (account.getResidue().compareTo(money)<0){
throw new RuntimeException("可用金额不足,金额冻结失败");
}
//执行冻结操作
accountMapper.updateFrozen(userId,account.getResidue().subtract(money),account.getFrozen().add(money));
if (Math.random()<0.5){
throw new RuntimeException("模拟异常");
}
//创建标识
ResultHolder.setResult(getClass(),businessActionContext.getXid(),"p");
log.info("扣减金额第一阶段 ,执行冻结金额完成");
return false;
}
@Transactional
@Override
public boolean commit(BusinessActionContext businessActionContext) {
log.info("扣减金额第二阶段 ,开始执行提交操作");
//防止重复提交
if (ResultHolder.getResult(getClass(),businessActionContext.getXid())==null){
return true;
}
long userId = Long.parseLong(businessActionContext.getActionContext("userId").toString());
BigDecimal money = new BigDecimal(businessActionContext.getActionContext("money").toString());
accountMapper.updateFrozenToUsed(userId,money);
//删除标识
ResultHolder.removeResult(getClass(),businessActionContext.getXid());
return false;
}
@Transactional
@Override
public boolean rollback(BusinessActionContext businessActionContext) {
log.info("扣减金额第二阶段 ,开始执行回滚操作");
//防止重复回滚
if (ResultHolder.getResult(getClass(),businessActionContext.getXid())==null){
return true;
}
long userId = Long.parseLong(businessActionContext.getActionContext("userId").toString());
BigDecimal money = new BigDecimal(businessActionContext.getActionContext("money").toString());
accountMapper.updateFrozenToResidue(userId,money);
//删除标识
ResultHolder.removeResult(getClass(),businessActionContext.getXid());
log.info("扣减金额第二阶段 ,执行回滚操作完成");
return false;
}
}