发现Seata TCC模式的一个BUG,顺手给社区提了一个issue

简介: 发现Seata TCC模式的一个BUG,顺手给社区提了一个issue

前言

在之前的文章手把手教你Spring Cloud集成Seata TCC模式中,实现了TCC方式完成购物车下单的分布式事务;在该案例中,我无意间发现了一个小BUG,下面我带大家通过源码分析来看一下为啥会出现这个BUG;

BUG复现

TCC Action中,我们在预扣款接口的请求参数中有一个Long类型的参数amount,示例如下:

/**
   * 预扣款
   *
   * @param businessActionContext
   * @param userId
   * @param amount
   * @return
   */
  @TwoPhaseBusinessAction(name = "prepareDeductMoney", commitMethod = "commitDeductMoney", rollbackMethod = "rollbackDeductMoney")
  boolean prepareDeductMoney(BusinessActionContext businessActionContext,
                             @BusinessActionContextParameter(paramName = "userId") String userId,
                             @BusinessActionContextParameter(paramName = "amount") Long amount);
复制代码

然后我们在提交或回滚逻辑中,需要拿到amount这个参数值,并通过sql语句实现真实扣款或者预扣款解除,这个时候我们就需要从BusinessActionContext中取出amount参数值:

Map<String, Object> actionContext = businessActionContext.getActionContext();
    Long amount = (Long) actionContext.get("amount");
复制代码

但是如果我们按照上面的代码这样取值,就会立马报ClassCastException异常:

Cannot cast 'java.lang.Integer' to 'java.lang.Long'
复制代码

根据以上异常,也就是说,我们直接从BusinessActionContext中取出来的amount参数值是java.lang.Integer类型,是无法直接强转为java.lang.Long的;可是我们明明在prepareDeductMoney()方法中的amount参数类型就是Long类型,为啥取出来的时候变成了java.lang.Integer类型呢?

BUG出现的原因

为了解答上面的疑问,我们来看一下Seata源码;我们的思路主要看两个点:

  • Seata序列化BusinessActionContext对象

ActionInterceptorHandler中,需要注册分支事务,这个时候会同时携带BusinessActionContext对象数据一起提交;我们知道java对象是无法直接在网络中传输的,所以在提交前做了一步序列化的操作:

Map<String, Object> applicationContext = Collections.singletonMap(Constants.TCC_ACTION_CONTEXT, context);
// 执行JSON序列化操作
String applicationContextStr = JSON.toJSONString(applicationContext);
复制代码

在上述代码中,假如amount=1000,那么amount参数会被序列化成如下json字符串{"amount":1000}

  • Seata反序列化BusinessActionContext对象

当我们的预扣款完成后,TM会决议出提交或回滚,此时TC服务会下发指令给到对应的RM执行提交或回滚逻辑,并携带注册分支事务时提交的json字符串给到RM,此时RM将该json字符串进行反序列化并创建新的BusinessActionContext对象;请查看TCCResourceManager:

protected BusinessActionContext getBusinessActionContext(String xid, long branchId, String resourceId,
                                                             String applicationData) {
        Map actionContextMap = null;
        if (StringUtils.isNotBlank(applicationData)) {
            // 反序列化成Map对象
            Map tccContext = JSON.parseObject(applicationData, Map.class);
            actionContextMap = (Map)tccContext.get(Constants.TCC_ACTION_CONTEXT);
        }
        if (actionContextMap == null) {
            actionContextMap = new HashMap<>(2);
        }
        //instance the action context
        BusinessActionContext businessActionContext = new BusinessActionContext(
            xid, String.valueOf(branchId), actionContextMap);
        businessActionContext.setActionName(resourceId);
        return businessActionContext;
}
复制代码

也就是说,Seata之前把amount参数序列化成{"amount":1000},然后又通过反序列化把{"amount":1000}转成Map对象,在反序列化过程中,fastjson并不知道1000这个数值到底是java.lang.Integer类型还是java.lang.Long类型,所以默认转换成了java.lang.Integer类型,也就造成了我们从BusinessActionContext取出来的amountjava.lang.Integer类型。

如何应对

按照上面的源码分析,不仅仅是Long类型会在反序列化的时候默认为java.lang.Integer类型,其他需要注意的类型还包括charbytefloatdoubleBigDecimal等序列化后不能明确类型的数据;那么针对这种情况,我们在日常开发中应该如何应对呢?

根据我自己的使用体验,建议大家在commitMethodrollbackMethod中通过BusinessActionContext取值之前,先通过Debug方式把需要的数据类型打印出来,这样我们编码的时候就可以根据打印出来的数据类型灵活地进行处理了,千万不能先入为主地认为Seata会自动帮我们把数据类型转成了和参数列表中的类型一致。

最后,为了让Seata变得更好,昨天我已经顺便把这个Issue给提交上去了,静候Seata社区早日解决这个问题,让大家能够使用更加方便。

Issue如下:TCC模式中,BusinessActionContext中反序列化出来的数据类型有问题


相关文章
|
4天前
|
自然语言处理 监控 Dubbo
Seata常见问题之使用tcc模式配置yml如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
83 4
|
4天前
|
数据库
|
4天前
|
NoSQL Java 数据库
Seata常见问题之xa模式下插入一条数据再更新这条数据会报错如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
111 2
|
4天前
|
Java 关系型数据库 微服务
Seata常见问题之项目一直启动不成功如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
93 0
|
4天前
|
存储 Java Nacos
Seata常见问题之xa模式出现错误xid is not valid如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
77 4
|
4天前
|
数据库 开发者
Seata的 TCC 模式
Seata的 TCC 模式
|
4天前
|
Nacos 数据库
分布式事务解决方案Seata
分布式事务解决方案Seata
29 1
|
4天前
|
SQL 关系型数据库 数据库
学习分布式事务Seata看这一篇就够了,建议收藏
学习分布式事务Seata看这一篇就够了,建议收藏
|
4天前
|
关系型数据库 MySQL 数据库
分布式事务Seata
分布式事务Seata
|
4天前
|
存储 关系型数据库 MySQL
基于Seata实现分布式事务
通过以上步骤,你可以使用 Seata 实现分布式事务,确保在微服务架构中的事务一致性。Seata 支持多种语言和框架,能够满足不同业务场景的需求。欢迎关注威哥爱编程,一起学习成长。

热门文章

最新文章