发现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中反序列化出来的数据类型有问题


相关文章
|
7月前
|
自然语言处理 监控 Dubbo
Seata常见问题之使用tcc模式配置yml如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
217 4
|
23天前
|
数据库 微服务
SEATA模式
Seata 是一款开源的分布式事务解决方案,支持多种事务模式以适应不同的应用场景。其主要模式包括:AT(TCC)模式,事务分三阶段执行;TCC 模式,提供更灵活的事务控制;SAGA 模式,基于状态机实现跨服务的事务一致性;XA 模式,采用传统两阶段提交协议确保数据一致性。
41 5
|
29天前
Seata框架在AT模式下是如何保证数据一致性的?
通过以上这些机制的协同作用,Seata 在 AT 模式下能够有效地保证数据的一致性,确保分布式事务的可靠执行。你还可以进一步深入研究 Seata 的具体实现细节,以更好地理解其数据一致性保障的原理。
40 3
|
6月前
|
Apache 开发者
Apache Seata 如何解决 TCC 模式的幂等、悬挂和空回滚问题
【6月更文挑战第8天】Apache Seata 是一款分布式事务框架,解决TCC模式下的幂等、悬挂和空回滚问题。通过记录事务状态处理幂等,设置超时机制避免悬挂,明确标记Try操作成功来处理空回滚。Seata 提供丰富配置和管理功能,确保分布式事务的可靠性和效率,支持复杂事务处理场景,为企业业务发展提供支持。
242 7
|
7月前
|
存储 Java Nacos
Seata常见问题之xa模式出现错误xid is not valid如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
241 4
|
7月前
|
NoSQL Java 数据库
Seata常见问题之xa模式下插入一条数据再更新这条数据会报错如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
208 2
|
7月前
|
Java 关系型数据库 微服务
Seata常见问题之项目一直启动不成功如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
600 0
|
1月前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
19天前
|
消息中间件 SQL 中间件
大厂都在用的分布式事务方案,Seata+RocketMQ带你打破10万QPS瓶颈
分布式事务涉及跨多个数据库或服务的操作,确保数据一致性。本地事务通过数据库直接支持ACID特性,而分布式事务则需解决跨服务协调难、高并发压力及性能与一致性权衡等问题。常见的解决方案包括两阶段提交(2PC)、Seata提供的AT和TCC模式、以及基于消息队列的最终一致性方案。这些方法各有优劣,适用于不同业务场景,选择合适的方案需综合考虑业务需求、系统规模和技术团队能力。
133 7
|
1月前
|
存储 Java 关系型数据库
在Spring Boot中整合Seata框架实现分布式事务
可以在 Spring Boot 中成功整合 Seata 框架,实现分布式事务的管理和处理。在实际应用中,还需要根据具体的业务需求和技术架构进行进一步的优化和调整。同时,要注意处理各种可能出现的问题,以保障分布式事务的顺利执行。
54 6