Seata源码——TCC模式使用01

什么是TCC

TCC 是分布式事务中的二阶段提交协议,它的全称为 Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),他们的具体含义如下:
Try:对业务资源的检查并预留。
Confirm:对业务处理进行提交,即 commit 操作,只要 Try 成功,那么该步骤一定成功。
Cancel:对业务处理进行取消,即回滚操作,该步骤回对 Try 预留的资源进行释放。
TCC 是一种侵入式的分布式事务解决方案,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但优点是 TCC 完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。
Seata源码——TCC模式使用01_第1张图片

Seata官网介绍

https://seata.io/zh-cn/docs/dev/mode/tcc-mode

特点

侵入性比较强,并且需要自己实现相关事务控制逻辑。
在整个过程基本没有锁,性能较强。

案例

order 服务

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;
    }
}

你可能感兴趣的:(Seata,java)