当前位置: 首页 > article >正文

java进阶:seata分布式事务未生效问题排查纪实|主事务回滚成功,分支事务未回滚

0.引言

近期在做公司老项目和新系统的模块打通时出现了一个分布式事务不生效的问题,主要表现为主事务中出现报错,主事务回滚成功了,但是分支事务未回滚。特此记录,以供后续参考。

1. 背景

首先声明一下我的业务背景,项目是基于dubbo+zookeeper进行服务间通信的,seata版本为1.5.2,且采用的是file形式作为注册和配置中心。

业务流程为主服务A调用服务B做数据更新,B更新成功后服务A再执行剩下的更新操作。其调用伪代码如下:


// 被调用服务B
@DubboReference
private CdmService cdmService;

// 主服务A
@Resource
private ApiMapper apiMapper;

@GlobalTransactional(timeoutMills = 30000)
public boolean syncToCdm(T data) {
    // 调用服务B	cdmService
	Result result = cdmService.insertApi(data);
	if(result.isSuccess()){
	    // 主服务A 进行数据更新
		apiMapper.update(result);
	}
}

被调用服务B中,注意这里没有加@Transactional,这里加不加都可以,不影响全局事务执行
(这里我后面验证,添加了@Transactional的,该服务的undo_log没有数据,但是全局事务可以正常回滚执行)

    public BaseResult<String> insertApi(T data) {
   		...
    }

2. 排查步骤

1、首先检查配置,初步怀疑是seata server端配置问题,查看seata服务端配置

seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: file
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: file
  store:
    # support: file 、 db 、 redis
    mode: file

可以看到是采用了file作为注册中心和配置中心,没有连接其他的注册中心组件,那么再看一下客户端配置。在各个使用seata的服务中看到配置了file.conf和registry.conf文件。并且通过在启动参数中添加-Dseata-server-address=seata服务端ip:8091来指定seata地址,并且引入了seata依赖。

同时配置文件中也设置了seata.enableAutoDataSourceProxy=false,来取消datasource自动代理

另外再检查下各个服务之间的tx-service-group设置的是否一致,发现也都一样

如此看起来seata配置都没有什么问题。

  <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>${seata.version}</version>
 </dependency>

2、这里因为B服务是我新增的,不太想用原来spring的这种配置方式,感觉太繁琐,所以我额外调整了配置,改为spring-boot-starter形式
引入依赖

<dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.5.2</version>
</dependency>

调整配置项

seata:
  enabled: true
  # 你的服务名,这里用了dubbo服务名
  applicationId: ${dubbo.application.name}
  data-source-proxy-mode: AT
  registry:
    type: file
  tx-service-group: my_test_tx_group
  enable-auto-data-source-proxy: false
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: seata-server:18091

3、这里我首先在被调用方(服务B)和调用方(服务A)都通过RootContext.getXID()打印了全局事务Id,发现全局事务ID都是一致的,说明事务是进来了的。

这里查阅了另外一篇文章中说明了如果全局事务ID不一致时的一种排查情况,大家可以参考
Seata做分布式事务时,报错后事务不回滚的问题

如果调用链比较复杂,全局ID丢失的,需要手动绑定全局事务id,可以通过RootContext.bind(xid);实现

4、事务id一致,那么就是继续看回滚日志数据是否有正常添加,断点查看调用方(服务A)undo_log数据情况,发现调用方正常新增
在这里插入图片描述
于是继续查看被调用方undo_log,发现被调用方忘记创建undo_log表了,但奇怪的是并没有报错(这里提醒,所有参与seata全局事务的业务所涉及的数据库中都要创建undo_log表,表结构参考如下)

create table undo_log
(
    id            bigint auto_increment
        primary key,
    branch_id     bigint       not null,
    xid           varchar(100) not null,
    context       varchar(128) not null,
    rollback_info longblob     not null,
    log_status    int          not null,
    log_created   datetime     not null,
    log_modified  datetime     not null,
    ext           varchar(100) null,
    constraint ux_undo_log
        unique (xid, branch_id)
)
    charset = utf8;

5、于是怀疑是B服务完全没有执行到全局事务回滚日志新增的方法,seata server端控制undo_log新增,肯定是根据各服务的数据库驱动类来实现的,于是考虑被调用方是mysql8.0, 调用方是mysql5.7,检查是否是驱动类声明的问题,一定要求你的驱动器与数据库版本保持一致,比如mysql8.0驱动要求com.mysql.cj.jdbc.Driver,另外要检查所涉及数据库是否支持事务性操作,如果数据库本身不支持,那么自然不成功。

6、这里检查发现调用方服务是通过多源数据库组件dynamic-datasource控制的,于是怀疑是不是这个组件本身和seata有什么冲突
在这里插入图片描述

查看官方说明,在3.x后支持seata, 如果版本低的可以升级版本,但目前使用的应该都是3.x版本以上
在这里插入图片描述
于是继续查看其配置seata的配置项

7、最终在application.yml中的dynamic-datasource部分开启seata,并指定其模式为AT

spring.datasource.dynamic.seata=true
spring.datasource.dynamic.seata-AT
spring:
  # 数据库配置
  datasource:
    # 指定使用 Druid 数据源
    type: com.alibaba.druid.pool.DruidDataSource
    #        driverClassName: com.mysql.cj.jdbc.Driver # 注释掉默认驱动
    dynamic:
      seata: true
      seata-mode: AT
      primary: master # 主数据源
      strict: false # 严格模式,是否在启动时检查数据源是否可用
      datasource:
        # 主库数据源
        master:
          url: jdbc:mysql://xxx/xxx
          username: xxx
          password: xxx

seata:
  enabled: true
  applicationId: xxx
  data-source-proxy-mode: AT
  registry:
    type: file
  tx-service-group: my_test_tx_group
  enable-auto-data-source-proxy: false
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: seata-server:18091

8、最终重启项目,继续断点执行操作,发现服务B的undo_log可以正常添加日志了,事务也回滚了,问题解决!
在这里插入图片描述

4. 原因汇总

1、检查seata-server, seata-client配置是否正确,是否在同一个group中

2、检查数据库驱动声明是否符合版本,数据库本身是否支持事务,所有涉及数据库中是否都有创建undo_log表

3、如有使用dynamic-datasource,检查是否其配置是否开启seata

4、seata事务模式是否是AT,其他模式本文未验证,可能有未知错误问题。


http://www.kler.cn/a/465358.html

相关文章:

  • Mysql SQL 超实用的7个日期算术运算实例(10k)
  • Python自学 - 函数式编程初步(lambda、map、filter)
  • springCloud 脚手架项目功能模块:Java分布式锁
  • GIT 企业级开发学习 1_基本操作
  • 在 macOS 上,你可以使用系统自带的 终端(Terminal) 工具,通过 SSH 协议远程连接服务器
  • Golang的并发编程实战经验
  • C# 设计模式(创建型模式):建造者模式
  • RSA e与phi不互质(AMM算法进行有限域开根)
  • PostgreSQL的备份方式
  • Ubuntu 系统配置指南:Fcitx5 输入法与 KDE 桌面环境安装教程
  • mac m2 安装 docker
  • SQL-leetcode-197. 上升的温度
  • Day 20:日志管理与 Logback
  • Go语言在实际项目中的应用:从RESTful API到日志监控 (十四)
  • wordpress右侧浮动咨询台插件
  • 频域滤波为什么使用psf2otf函数?
  • 大语言模型(LLMs)数学推理的经验技巧【思维链CoT的应用方法】
  • 【JavaWeb后端学习笔记】MySQL的常用函数(字符串函数,数值函数,日期函数,流程函数)
  • Java学习-Redis
  • Next.js 实战 (六):如何实现文件本地上传
  • 目录中只有一个子目录时把子目录移动到父目录
  • OpenCV的人脸检测模型FaceDetectorYN
  • 25考研王道数据结构课后习题笔记
  • 2025三掌柜赠书活动第一期:动手学深度学习(PyTorch版)
  • 什么是实体完整性约束?
  • CSS系列(43)-- Anchor Positioning详解