SpringCloud Alibaba Seata2.0分布式事务AT模式实践总结

news2025/1/11 2:49:07

这里我们划分订单、库存与支付三个module来实践Seata的分布式事务。

依赖版本(jdk17):

<spring.boot.version>3.1.7</spring.boot.version>
<spring.cloud.version>2022.0.4</spring.cloud.version>
<spring.cloud.alibaba.version>2022.0.0.0-RC2</spring.cloud.alibaba.version>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>${spring.boot.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring.cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<!--springcloud alibaba 2022.0.0.0-RC2-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>${spring.cloud.alibaba.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

【1】构建基础module

这里我们构建order、account和storage三个module,也就是演示订单的流程:创建订单、扣减库存、扣减账户余额。

Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。

整体module如下,module之间使用openfign进行调用。以订单模块为例进行说明,其他两个相似。

seata-account-service2003
seata-order-service2001
seata-storage-service2002

① pom依赖

<!-- nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--alibaba-seata-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--openfeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--loadbalancer-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

② yml配置

server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848         #Nacos服务注册中心地址
  # ==========applicationName + druid-mysql8 driver===================
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
    username: root
    password: 123456
# ========================mybatis===================
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.jane.cloud.entities
  configuration:
    map-underscore-to-camel-case: true

# ========================seata===================
seata:
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: SEATA_GROUP
      application: seata-server
  tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称
  service:
    vgroup-mapping: # 点击源码分析
      default_tx_group: default # 事务组与TC服务集群的映射关系
  data-source-proxy-mode: AT

logging:
  level:
    io:
      seata: info

这里重点关注Seata的配置,相比1.0版本,Seata2.0版本无论是server端还是client端配置均进行了优化。

③ 创建undo_log表

undo_log建表、配置参数(仅AT模式)。SQL脚本 地址:https://github.com/apache/incubator-seata/blob/2.x/script/client/at/db/mysql.sql


-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

最终数据库效果如下图:

在这里插入图片描述

【2】不添加@GlobalTransactional进行测试

如下所示,我们在订单处理方法里面进行远程调用扣减库存、扣减账户。service方法未添加@GlobalTransactional注解,也就是不启用分布式事务。

public void create(Order order)
    {
        //xid全局事务id的检查,重要
        String xid = RootContext.getXID();
        //1 新建订单
        log.info("---------------开始新建订单: "+"\t"+"xid: "+xid);
        //订单新建时默认初始订单状态是零
        order.setStatus(0);
        int result = orderMapper.insertSelective(order);
        // 插入订单成功后获得插入mysql的实体对象
        Order orderFromDB = null;

        if(result > 0)
        {
            // 从mysql里面查出刚插入的记录
            orderFromDB = orderMapper.selectOne(order);
            log.info("-----> 新建订单成功,orderFromDB info: "+orderFromDB);
            System.out.println();
            //2 扣减库存
            log.info("-------> 订单微服务开始调用Storage库存,做扣减count");
            storageFeignApi.decrease(orderFromDB.getProductId(),orderFromDB.getCount());
            log.info("-------> 订单微服务结束调用Storage库存,做扣减完成");
            System.out.println();

            //3 扣减账户余额
            log.info("-------> 订单微服务开始调用Account账号,做扣减money");
            accountFeignApi.decrease(orderFromDB.getUserId(),orderFromDB.getMoney());
            log.info("-------> 订单微服务结束调用Account账号,做扣减完成");
            System.out.println();

            //4 修改订单状态
            //将订单状态从零修改为1,表示已经完成
            log.info("-------> 修改订单状态");
            orderFromDB.setStatus(1);

            Example whereCondition = new Example(Order.class);
            Example.Criteria criteria = whereCondition.createCriteria();
            criteria.andEqualTo("userId",orderFromDB.getUserId());
            criteria.andEqualTo("status",0);

            int updateResult = orderMapper.updateByExampleSelective(orderFromDB, whereCondition);
            log.info("-------> 修改订单状态完成"+"\t"+updateResult);
            log.info("-------> orderFromDB info: "+orderFromDB);
        }
        System.out.println();
        log.info("---------------结束新建订单: "+"\t"+"xid: "+xid);
    }

那么假设account抛了异常,此时应该插入了订单并扣减了库存,但是账户余额没有扣除。我们修改AccountServiceImpl 使其超时:

public class AccountServiceImpl implements AccountService
{
    @Resource
    AccountMapper accountMapper;

    /**
     * 扣减账户余额
     */
    @Override
    public void decrease(Long userId, Long money)
    {
        log.info("------->account-service中扣减账户余额开始");

        accountMapper.decrease(userId,money);

        myTimeOut();
        log.info("------->account-service中扣减账户余额结束");
    }

    /**
     * 模拟超时异常,全局事务回滚
     */
    private static void myTimeOut()
    {
        try { TimeUnit.SECONDS.sleep(65); } catch (InterruptedException e) { e.printStackTrace(); }
    }
}

此时我们查看数据库,可以验证上述推测是正确的:插入了订单并扣减了库存,但是账户余额没有扣除。其实这也就是我们为什么要引入分布式事务处理的原因:在分布式体系中,@Transactional 注解只能控制本地事务,不能控制其他服务事务,故而会引起分布式事务问题。

【3】添加@GlobalTransactional进行测试

如下所示,我们修改create方法如下:

# 添加全局事务注解
@GlobalTransactional(name = "create-order",rollbackFor = Exception.class) //AT
public void create(Order order)
{
    //xid全局事务id的检查,重要
    String xid = RootContext.getXID();
    //1 新建订单
    log.info("---------------开始新建订单: "+"\t"+"xid: "+xid);
    //订单新建时默认初始订单状态是零
    order.setStatus(0);
    //...
}

再次进行测试,仍然让account抛出异常。此时我们看控制台发现均已进行了回滚:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
数据库订单表、账户表以及库存表并没有产生脏数据,说明分布式事务生效。


我们再次测试,在account方法处打上断点,那么order模块会抛出超时异常:feign.RetryableException: Read timed out 。

这时TC就会让各个分支事务进行回滚,比如库存模块:

在这里插入图片描述

在Seata的控制台,我们可以看到目前只有account持有全局锁:

在这里插入图片描述

事务信息界面目前状态为RollbackRetrying,也就是重复尝试回滚(因为我们debug中)。

在这里插入图片描述
此时order分支二阶段回滚也已成功,但是全局事务回滚还未成功。
在这里插入图片描述

我们释放account的断点,使其二阶段回滚成功,此时全局事务也会回滚成功。

在这里插入图片描述

【4】AT模式如何做到对业务的无侵入

官网地址:Seata 是什么? Seata默认是AT模式,两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

  • 二阶段:

    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

① 一阶段加载

在一阶段,Seata 会拦截“业务 SQL”,

  • 1 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
  • 2 执行“业务 SQL”更新业务数据,在业务数据更新之后,
  • 3 其保存成“after image”,最后生成行锁。

以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
在这里插入图片描述

② 二阶段提交

二阶段如是顺利提交的话,因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

在这里插入图片描述

③ 二阶段回滚

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。

回滚方式便是用“before image”还原业务数据。但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”:

  • 如果两份数据完全一致就说明没有脏写,可以还原业务数据,
  • 如果不一致就说明有脏写,出现脏写就需要转人工处理。

在这里插入图片描述

【5】复习:@Transactional事务注解什么时候失效

@Transactional 注解可能会在以下几种情况下失效:

  1. 非public方法:当@Transactional注解应用在非public修饰的方法上时,事务管理将不会生效。这是因为在Spring AOP(面向切面编程)代理机制中,事务拦截器仅对public方法进行拦截处理。

  2. 自我调用:在一个类的内部,一个带有@Transactional注解的方法直接被另一个没有事务注解的方法调用时,事务可能不会生效。因为在这种情况下,调用是直接发生的,没有经过Spring的代理对象,从而不会触发事务管理。

  3. 未被Spring管理的类:如果带有@Transactional注解的类没有被Spring容器管理(例如,没有使用@Component、@Service等注解标记,或者没有在Spring配置中声明),事务注解将不起作用。

  4. 异常处理不当:如果在事务方法中捕获并吞没了应导致事务回滚的异常(通常是未检查异常,如RuntimeException),事务可能不会回滚。除非在@Transactional注解中明确指定了rollbackFor属性来捕获特定类型的异常。

  5. 代理模式问题:使用基于Java接口的代理时(Spring默认的代理方式),只有通过接口调用的方法才会被代理增强,如果直接在实现类上调用方法(而不是通过接口),事务可能不会生效。

  6. 事务传播行为设置不当:如果方法的事务传播行为配置不当(例如,一个事务方法调用另一个事务方法时,传播行为设置为PROPAGATION_NOT_SUPPORTEDPROPAGATION_NEVER),可能导致事务不生效或被挂起。

  7. 缺少数据库事务支持:如果底层数据访问技术不支持事务管理,如使用了非事务性的JDBC连接或某些NoSQL数据库操作,即使@Transactional注解正确使用,也无法实现事务管理。

要确保@Transactional注解能正常工作,需要确保遵循正确的使用方式,并注意上述可能引起事务失效的情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1864389.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

美多多商城定义用户模型类遇见的问题

from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class User(AbstractUser):mobile models.CharField(max_length11, uniqueTrue,verbose_name手机号)class Meta:db_tabletb_users #自定义表名verbose_name用户…

【动态内存】详解

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

深入浅出 langchain 1. Prompt 与 Model

示例 从代码入手来看原理 from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI prompt ChatPromptTemplate.from_template("tell me a short joke about…

B端页面:日志管理页面,简洁实用的设计法门

B端日志管理是指在企业级后台系统中对系统操作日志进行记录、查看和管理的功能。 它的作用主要有以下几点&#xff1a; 1. 安全审计&#xff1a;通过记录用户的操作日志&#xff0c;可以对系统的安全性进行审计和监控&#xff0c;及时发现异常操作和安全漏洞。 2. 故障排查&a…

Program LLMs,不只是Prompt LLMs

前言 随着大模型的使用和应用越来越频繁&#xff0c;也越来越广泛&#xff0c;大家有没有陷入到无限制的研究、调优自己的prompt。 随之&#xff0c;市面上也出现了提示词工程师&#xff0c;更有专门的提示工程一说。 现在网上搜一搜&#xff0c;有各种各样的写提示词的技巧…

Python多线程技巧心得详解

概要 多线程是一种能够并发执行代码的方法,可以提高程序的执行效率和响应速度。本文将详细介绍 Python 中多线程的概念、使用场景、基本用法以及实际应用,可以更好地掌握多线程编程。 什么是多线程? 多线程是一种在单个进程内并发执行多个线程的技术。每个线程共享相同的内…

电脑CPU速度很快,为什么3dMax还会出现卡顿的情况?

我们在使用3dmax时会经常遇到电脑变得很缓慢甚至卡顿的情况&#xff08;多发生于新手群体&#xff09;&#xff0c;即使我们的电脑CPU已经足够快&#xff0c; 也会出现滞后或性能延迟。包括但不限于 Intel i9 和 AMD“Ryzen Threadrippers”。 例如单击用户界面的任何区域或移…

红酒舞动,运动风采,品味力与美

当夜幕降临&#xff0c;城市的灯火渐次亮起&#xff0c;忙碌了一天的人们开始寻找那份属于自己的宁静与愉悦。在这个时刻&#xff0c;红酒与运动&#xff0c;这两个看似截然不同的元素&#xff0c;却能以它们不同的魅力&#xff0c;为我们带来一场视觉与感官的盛宴。 红酒&…

如何轻松获取 GitLab 指定分支特定路径下的文件夹内容

第一步&#xff1a; 获取 accessToken 及你的 项目 id &#xff1a; 获取 accessToken ,点击用户头像进入setting 按图示操作&#xff0c;第 3 步 填写你发起请求的域名。 获取项目 id , 简单粗暴方案 进入 你项目仓库页面后 直接 源码搜索 project_id&#xff0c; value 就…

ApolloClient GraphQL 与 ReactNative

要在 React Native 应用程序中设置使用 GraphQL 的简单示例&#xff0c;您需要遵循以下步骤&#xff1a; 设置一个 React Native 项目。安装 GraphQL 必要的依赖项。创建一个基本的 GraphQL 服务器&#xff08;或使用公共 GraphQL 端点&#xff09;。从 React Native 应用中的…

零基础STM32单片机编程入门(三)中断详解及按键中断实战含源码视频

文章目录 一.概要二.可嵌套的向量中断控制器 (NVIC)三.中断向量表四.中断优先级详解五.STM32外部中断控制器(EXTI)1.EXTI简介2.EXTI在中断向量表的位置3.EXTI外部中断产生的信号流向4.EXTI中断产生后的中断服务程序 六.CubeMX配置一个GPIO输入中断的例程七.CubeMX工程源代码下载…

Alibaba Cloud Linux详解_操作系统兼容性_alinux稳定性全解析

Alibaba Cloud Linux是阿里云自研的稳定、安全、高性能的服务器Linux操作系统&#xff0c;完全兼容CentOS/RHEL生态和操作方式&#xff0c;又阿里云提供免费提供长期支持和维护LTS。Alibaba Cloud Linux是目前阿里云服务器最大规模使用的操作系统之一&#xff0c;可部署在Web网…

Appium+python自动化(二十六)- 烟花一瞬,昙花一现 -Toast提示(超详解)

简介  今天宏哥在这里首先给小伙伴们和童鞋们分享一个有关昙花的小典故&#xff1a;话说昙花原是一位花神&#xff0c;她每天都开花&#xff0c;四季都灿烂。她还爱上了每天给她浇水除草的年轻人。后来&#xff0c;此事给玉帝得知。于是&#xff0c;玉帝大发雷霆&#xff0c;要…

DHT11

第一个传感----DHT11 通过前面的学习&#xff0c;你已经学会了控制IO口、延时函数、串口的收发。接下来&#xff0c;你就可以借助以上的知识点完成自己的第一个传感器--DHT11啦&#xff01; DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。应用非常广…

《Three.JS零基础入门教程》第九篇:环境详解

往期回顾&#xff1a; 《Three.JS零基础入门教程》第一篇&#xff1a;搭建开发环境 《Three.JS零基础入门教程》第二篇&#xff1a;起步案例 《Three.JS零基础入门教程》第三篇&#xff1a;开发辅助 《Three.JS零基础入门教程》第四篇&#xff1a;基础变换 《Three.JS零基…

数据处理神器Elasticsearch_Pipeline:原理、配置与实战指南

文章目录 &#x1f4d1;引言一、Elasticsearch Pipeline的原理二、Elasticsearch Pipeline的使用2.1 创建 Pipeline2.2 使用 Pipeline 进行索引2.3 常用的 Processor 三、实际应用场景3.1 日志数据处理3.2 数据清洗和标准化3.3 数据增强 四、最佳实践4.1 性能优化4.2 错误处理4…

【CPP】类 -- class 使用思想

目录 3 类 -- class3.1 什么是类3.2 类的定义3.3 类内参数的使用3.4 类与结构体 -- class VS struct3.5 类的实操 -- log类(日志类)3.6 类定义 与 static 3 类 – class 3.1 什么是类 类是面向对象的一种设计,其中封装了我们对对象的"描述",或者说对象的"特征…

生命在于学习——Python人工智能原理(4.6)

在这里插一句话&#xff0c;我有两个好兄弟的github项目&#xff0c;感兴趣的可以去看一下&#xff0c;star一下&#xff0c;谢谢。 https://github.com/fliggyaa/fscanpoc https://github.com/R0A1NG/Botgate_bypass 四、Python的程序结构与函数 4.1 Python的分支结构 &…

C语言——链表专题

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 点击主页&#xff1a;optimistic_chen和专栏&#xff1a;c语言&#xff0c; 创作不易&#xff0c;大佬们点赞鼓…

国产操作系统上多种压缩和解压命令详解 _ 统信 _ 麒麟 _ 中科方德

原文链接&#xff1a;国产操作系统上多种压缩和解压命令详解 | 统信 | 麒麟 | 中科方德 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇在国产操作系统上多种压缩和解压命令详解的文章。压缩和解压缩是我们在日常工作中经常需要进行的操作&#xff0c;尤其是在处理大…