原理篇:Seata TCC模式中BusinessActionContext是如何传递的

简介: 原理篇:Seata TCC模式中BusinessActionContext是如何传递的

前言

在前面的博客中我们已经学会了如何在Spring Cloud项目中集成Seata TCC模式,在这个项目中,我们创建的TCC Action需要传入一个BusinessActionContext对象参数,但是我们在Service调用TCC Action时,传入的是一个null,那么我们的BusinessActionContext对象是怎么创建出来的,在Seata中又是如何传递的呢?

创建BusinessActionContext对象

因为我们的TCC Action是通过拦截器原理进行了一层包装,如果被Service调用,最终会进入ActionInterceptorHandler.proceed():

public Object proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction,
                                       Callback<Object> targetCallback) throws Throwable {
        // 从参数中获取BusinessActionContext,如果没有就创建一个BusinessActionContext对象
        BusinessActionContext actionContext = getOrCreateActionContextAndResetToArguments(method.getParameterTypes(), arguments);
  ......
}
复制代码

所以,最关键的代码在getOrCreateActionContextAndResetToArguments()方法中:

protected BusinessActionContext getOrCreateActionContextAndResetToArguments(Class<?>[] parameterTypes, Object[] arguments) {
        BusinessActionContext actionContext = null;
        // 从参数中获取BusinessActionContext对象
        int argIndex = 0;
        for (Class<?> parameterType : parameterTypes) {
            if (BusinessActionContext.class.isAssignableFrom(parameterType)) {
                actionContext = (BusinessActionContext)arguments[argIndex];
                if (actionContext == null) {
                    // 如果从参数中拿到的是一个BusinessActionContext类型的参数,但是是一个null,那么就创建一个BusinessActionContext对象
                    actionContext = new BusinessActionContext();
                    arguments[argIndex] = actionContext;
                } else {
                    // 重置updated的值,避免不必要的上报,updated=true会触发分支上报
                    actionContext.setUpdated(null);
                }
                break;
            }
            argIndex++;
        }
        // 如果参数列表中没有BusinessActionContext类型参数,那么Seata自己创建一个BusinessActionContext对象
        if (actionContext == null) {
            actionContext = new BusinessActionContext();
        }
        return actionContext;
    }
复制代码

1.Seata会遍历所有的参数类型,如果没有找到BusinessActionContext类型参数,Seata会自己创建一个BusinessActionContext对象;

2.如果在参数列表中找到了BusinessActionContext类型参数,但是该参数值为null,那么Seata会给该参数创建一个BusinessActionContext对象;

3.如果找到的BusinessActionContext类型参数值不为null,那么Seata会重置其中的updated参数值为null,以避免不必要的上报;

也就是说,我们在资源预留的方法中,其实是可以不写BusinessActionContext类型的参数的,这样在Service中也就不会很奇怪地传递null了;另外,如果有需要的话,我们自己也可以通过new BusinessActionContext()方式自己创建BusinessActionContext对象;

所以,通过上述源码解析后可得出以下结论:

  • 如果在Service调用TCC Action预留资源的时候不需要使用到BusinessActionContext对象,那么我们就可以不在参数列表中写上BusinessActionContext类型参数;
  • 如果我们需要使用到BusinessActionContext参数,那么在Service传递参数时,可以通过new BusinessActionContext()方式创建,或者直接传递null;

BusinessActionContext如何保存

在Seata TCC模式中,如果所有的一阶段全部成功后,就需要执行二阶段的提交逻辑,在提交逻辑中,可以拿到一个BusinessActionContext对象;同样的,在二阶段的回滚逻辑中,也可以拿到一个BusinessActionContext对象;因为一阶段和二阶段并不是同一个线程连续执行的,那么这个BusinessActionContext对象是如何保存的呢?

我们可以查看ActionInterceptorHandler.doTccActionLogStore()方法:

protected String doTccActionLogStore(Method method, Object[] arguments, TwoPhaseBusinessAction businessAction,
                                         BusinessActionContext actionContext) {
      ......
      ......     
        // 从BusinessActionContext中取出ActionContext
        Map<String, Object> originContext = actionContext.getActionContext();
        if (CollectionUtils.isNotEmpty(originContext)) {
            //如果有数据,就全部放到context中
            originContext.putAll(context);
            context = originContext;
        } else {
            actionContext.setActionContext(context);
        }
      ......
      // 包装context
      Map<String, Object> applicationContext = Collections.singletonMap(Constants.TCC_ACTION_CONTEXT, context);
       // 转换成json字符串
        String applicationContextStr = JSON.toJSONString(applicationContext);
        try {
            // 注册分支,把json字符串一起提交给TC服务
            Long branchId = DefaultResourceManager.get().branchRegister(BranchType.TCC, actionName, null, xid,
                    applicationContextStr, null);
            return String.valueOf(branchId);
        } catch (Throwable t) {
            String msg = String.format("TCC branch Register error, xid: %s", xid);
            LOGGER.error(msg, t);
            throw new FrameworkException(t, msg);
        }
}
复制代码

1.Seata会把BusinessActionContext对象中的数据全部转换成Map键值对;

2.为了能够将Map键值对传递给TC服务,中间会进行json转换,最终变成json字符串,这样就可以在分支事务注册的时候,把BusinessActionContext对象的数据给到TC服务保存;

结论:Seata会通过分支事务注册的时候,把BusinessActionContext对象数据通过json字符串的方式保存在TC服务中;

提交和回滚逻辑中的BusinessActionContext对象从哪儿来

BusinessActionContext对象的保存疑问解决了,那么在调用提交或回滚逻辑时,我们拿到的BusinessActionContext对象又是从哪儿来的呢?

我们通过前几篇文章的源码分析知道,提交或回滚逻辑的调用是通过TC服务发出来的指令实现的,那么我们查看一下提交或回滚逻辑的入口:

@Override
    public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,
                                     String applicationData) throws TransactionException {
}
@Override
    public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId,
                                       String applicationData) throws TransactionException {
    }
复制代码

我们可以观察到,TC服务除了最重要的xidbranchId传递过来了,另外还传递了applicationData数据,这就是我们在分支注册的时候保存的BusinessActionContext对象里面的数据;我们继续沿着调用栈查看源码,最终可以找到TCCResourceManager.getBusinessActionContext()方法:

protected BusinessActionContext getBusinessActionContext(String xid, long branchId, String resourceId,
                                                             String applicationData) {
        Map actionContextMap = null;
        // 如果applicationData不为空
        if (StringUtils.isNotBlank(applicationData)) {
            // 把applicationData转换成Map对象
            Map tccContext = JSON.parseObject(applicationData, Map.class);
            // 取出其中Constants.TCC_ACTION_CONTEXT对应的值
            actionContextMap = (Map)tccContext.get(Constants.TCC_ACTION_CONTEXT);
        }
        // 如果没有数据,那么创建一个空的HashMap对象
        if (actionContextMap == null) {
            actionContextMap = new HashMap<>(2);
        }
        // 通过Map对象创建BusinessActionContext对象
        BusinessActionContext businessActionContext = new BusinessActionContext(
            xid, String.valueOf(branchId), actionContextMap);
        businessActionContext.setActionName(resourceId);
        // 返回BusinessActionContext对象
        return businessActionContext;
    }
复制代码

也就是说,Seata最终还是通过TC服务传递回来的applicationData重新创建了一个新的BusinessActionContext对象,这样的话,我们的提交或回滚逻辑中,就可以顺其自然地使用到之前在资源预留逻辑中的BusinessActionContext对象了,虽然这两个对象并不是同一个对象,但是里面保存的元数据是一致的,所以不影响我们使用;

小结

通过以上源码分析,我们可以归纳出以下几点:

1.资源预留方法中,如果不需要使用BusinessActionContext对象,可以不添加BusinessActionContext类型参数,Seata会自己创建;

2.Service在调用资源预留方法是,参数列表中有BusinessActionContext类型参数时,既可以传null,也可以new BusinessActionContext()传递进去;

3.BusinessActionContext参数的保存是以json字符串的形式保存在TC服务中的;

4.我们在提交或回滚逻辑中使用到的BusinessActionContext对象和资源预留中使用到的BusinessActionContext对象并不是同一个对象,但是它们的元数据是一致的,所以不会影响使用效果;


相关文章
|
6月前
|
自然语言处理 监控 Dubbo
Seata常见问题之使用tcc模式配置yml如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
201 4
|
6月前
|
数据库
|
15天前
|
SQL JavaScript 数据库连接
Seata的工作原理
【10月更文挑战第30天】
20 3
|
5月前
|
Apache 开发者
Apache Seata 如何解决 TCC 模式的幂等、悬挂和空回滚问题
【6月更文挑战第8天】Apache Seata 是一款分布式事务框架,解决TCC模式下的幂等、悬挂和空回滚问题。通过记录事务状态处理幂等,设置超时机制避免悬挂,明确标记Try操作成功来处理空回滚。Seata 提供丰富配置和管理功能,确保分布式事务的可靠性和效率,支持复杂事务处理场景,为企业业务发展提供支持。
202 7
|
6月前
|
存储 Java Nacos
Seata常见问题之xa模式出现错误xid is not valid如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
190 4
|
6月前
|
NoSQL Java 数据库
Seata常见问题之xa模式下插入一条数据再更新这条数据会报错如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
188 2
|
6月前
|
SpringCloudAlibaba Java 数据库
SpringCloud Alibaba微服务 -- Seata的原理和使用
SpringCloud Alibaba微服务 -- Seata的原理和使用
|
6月前
|
数据库 开发者
Seata的 TCC 模式
Seata的 TCC 模式
|
6月前
|
Java 关系型数据库 微服务
Seata常见问题之项目一直启动不成功如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
515 0
|
2月前
|
SQL NoSQL 数据库
SpringCloud基础6——分布式事务,Seata
分布式事务、ACID原则、CAP定理、Seata、Seata的四种分布式方案:XA、AT、TCC、SAGA模式
SpringCloud基础6——分布式事务,Seata