当Seata遇上Orcal,TCC解决方案及问题汇总

一、什么是Seata?

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

PS:各事务模式介绍可参考:http://seata.io/zh-cn/docs/overview/what-is-seata.html

 

二、AT

Seata的优势在于提供了多种事务模式,用户可以根据需要进行选择,Seata目前复合主键仅支持mysql,如果项目使用mysql数据库,AT模式基本能满足你的全部需求,seata配置好后,在接口方法入口处加上@GlobalTransactional注解,Seata会自动完成一阶段SQL解析、执行、将日志记录插入到UNDO_log表,二阶段提交/回滚等操作,无需自己代码实现,基本能实现对原本业务代码零侵入。

 

三、TCC

本次公司项目使用的是Oracle,Seata不支持Oracle多主键,所以采用的是TCC模式,接下来对TCC模式进行较为详细的介绍:

TCC模式和AT模式一样,也是采用两阶段提交协议:

  1. 一阶段 prepare 行为
  2. 二阶段 commit 或 rollback 行为

 

与AT模式不相同的是,TCC模式下的prepare 、commit和rollback 都需要自己代码实现,例如本次项目的签名接口,涉及七个服务的调用,其中有3个服务涉及对复合主键表的操作,这就导致对原有业务代码的侵入性较强,涉及到有多主键表的增删改操作都需进行TCC改造,需自己代码实现其prepare 、commit和rollback逻辑。

 

四、DEMO FOR TCC

以下是项目某接口TCC改造后的部分伪代码

package说明

com.demo.tcc      -----用于放tcc接口定义

com.demo.tcc.impl -----用于放tcc接口实现

(1)定义TCC接口

package com.demo.tcc;
@LocalTCC
public interface DemoTccAction {
    @TwoPhaseBusinessAction(name = "saveAll" , commitMethod = "saveAllCommit", rollbackMethod = "saveAllRollback")
    List<User> saveAll(@BusinessActionContextParameter (paramName = "UserList")  List<User> UserList);

    boolean saveAllCommit(BusinessActionContext actionContext);

    TwoPhaseResult saveAllRollback(BusinessActionContext actionContext);
}

 

  • @LocalTCC 适用于SpringCloud+Feign模式下的TCC
  • @TwoPhaseBusinessAction 注解try方法,其中name为当前tcc方法的bean名称,写方法名便可,commitMethod指向提交方法,rollbackMethod指向事务回滚方法。指定好三个方法之后,seata会根据事务的成功或失败,通过动态代理去帮我们自动调用提交或者回滚。
  • @BusinessActionContextParameter 注解可以将参数传递到二阶段(commitMethod/rollbackMethod)的方法。
  • BusinessActionContext 是指TCC事务上下文

 

(2)tcc接口两阶段实现

package com.demo.tcc.impl;
@Service
@Slf4j
public class DemoTccActionImpl extends BaseLogicImpl<UserDao, User> implements DemoTccAction {

    @Override
    public List<User> saveAll(List<User> UserList) {

        List<User> users= dao.saveAll(UserList);
        dao.flush();
        return users;
    }


    @Override
    public boolean saveAllCommit(BusinessActionContext actionContext) {
        log.info("xid = {}, branchId = {},保存用户提交成功", actionContext.getXid(), actionContext.getBranchId());

        return true;
    }

    @Override
    public TwoPhaseResult saveAllRollback(BusinessActionContext actionContext) {
        log.info("xid = {}, branchId = {},发生异常,保存用户开始回滚...", actionContext.getXid(), actionContext.getBranchId());

        TwoPhaseResult result = new TwoPhaseResult(true, "");
        // 空回滚
        if (actionContext.getActionContext("UserList") == null) {
            log.warn("BusinessActionContext 不存在记录", actionContext.getXid(), actionContext.getBranchId());
            return result;
        }
        String json = actionContext.getActionContext("UserList").toString();
        List<User> UserList= JSON.parseArray(json,User.class);

        for (User user : UserList) {
            dao.deleteByUserIdAndUserNo(user.getUserId(),user.getUserNo());
        }

        log.info("xid = {}, branchId = {},保存用户回滚成功", actionContext.getXid(), actionContext.getBranchId());

        return result;
    }

 

 

踩坑汇总:

1.Caused by: io.seata.common.exception.NotSupportYetException: MZSF.MZYS_TBS_DPRESP needs to contain the primary key. 或者 TCC模式 下报io.seata.common.exception.NotSupportYetException: multi pk only support mysql !

原因:@GlobalTransactional和@Transactional不一起使用,解决方案:去除@Transactional 或者 使用 saveAndFlush

 

2.ArrayIndexOutOfBoundsException: 22/4001

原因:AT模式Jpa saveAll报数组越界, 改成循环saveAndFlush

 

3.@BusinessActionContextParameter会将参数存在branch表,不必担心数据丢失

 

4.复合主键进行删除操作时先将删除的对象查询出来,可存在@BusinessActionContextParameter,用于二阶段回滚时使用

 

5.seata无法调用存储过程,原因:seata无法判断存储过程进行了什么操作

 

 

 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值