分布式事务之Seata AT 事务

2023-05-16

1. Seata介绍

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

1.1 Seata AT事务方案

Seata 的 AT 模式(Automatic Transaction)是一种无侵入的分布式事务解决方案。下面结合具体业务场景来分析其执行的原理。

1.1.1 业务场景

订单系统

当用户下订单时,执行以下三步流程:
(1)订单系统保存订单
(2)订单系统调用库存服务,减少商品库存
(3)订单系统调用账户服务,扣减用户金额
这三步要作为一个整体事务进行管理,要么整体成功,要么整体失败。

1.2 Seata AT基本原理

1.2.1 第一阶段:执行各分支事务

  • 微服务系统中,各服务之间无法相互感知事务是否执行成功,这时就需要一个专门的服务,来协调各个服务的运行状态。这个服务称为 TC(Transaction Coordinator),事务协调器。
  • 订单系统开始执行保存订单之前,首先启动 TM(Transaction Manager,事务管理器),由 TM 向 TC 申请开启一个全局事务:
  • 这时TC会产生一个全局事务ID,称为 XID,并将 XID 传回 TM,自此开启全局事务。
  • 全局事务开启后,开始执行创建订单的业务。首先执行保存订单,这时会先启动一个 RM(Resource Manager,资源管理器),并将 XID 传递给 RM。
  • RM 负责对分支事务(即微服务的本地事务)进行管理,并与 TC 通信,上报分支事务的执行状态、接收全局事务的提交或回滚指令。
    RM 首先会使用 XID 向 TC 注册分支事务,将分支事务纳入对应的全局事务管辖。
  • 现在可以执行保存订单的分支事务了。一旦分支事务执行成功,RM 会上报事务状态:
  • TC 收到后,会将该状态信息传递到 TM,到此,保存订单过程结束。
  • 下面是调用库存服务,减少商品库存,与订单的执行过程相同。首先调用库存服务,启动 RM,并传递 XID:
  • 库存服务的 RM 使用 XID 向 TC 进行注册,纳入全局事务管辖:
  • 执行本地事务成功后上报状态,TC会将状态发送给TM,到此调用库存就完成了。

  • 完成账户分支事务:

1.2.2 第二阶段:控制全局事务最终提交

  • 现在,TM(全局事务管理器)收集齐了全部分支事务的成功状态,它会进行决策,确定全局事务成功,向 TC 发送全局事务的提交请求:
  • 然后,TC 会向所有 RM 发送提交操作指令,RM 会完成最终提交操作。到此,全局事务全部提交完成!

1.2.3 第二阶段:控制全局事务最终回滚

下面是事务执行失败的情况,需要回滚。

  • 假设订单业务执行过程中,扣减账户金额这一步分支事务执行失败,那么失败状态对TC上报,然后再发送给TM:
  • TM 会进行决策,确定全局事务失败,向 TC 发送全局事务的回滚请求:
  • 然后,TC 会向所有 RM 发送回滚操作指令,RM 会完成最终回滚操作:

1.3 Seata AT具体工作机制

1.3.1 第一阶段:执行分支事务

  • 以订单业务中的库存服务为例:
    库存表中存在一条商品的库存信息:
  • 现在要执行业务操作减少库存,从50件减少到40件:
  • 执行修改库存业务操作前, 会先取出旧的库存信息:
  • 现在开始修改库存
  • 接着,取出更新后的新数据:
  • 接下来,会把旧数据和新数据合并起来,保存到一个事务回滚日志表:undo_log表:
  • 至此,第一阶段,分支事务完成,将状态上报给TC:

1.3.2 第二阶段:控制全局事务最终回滚

  • 假如全局事务失败,那么第一阶段已提交的分支事务要执行回滚操作。首先会收到来自 TC 的全局事务回滚指令:
  • 接下来,根据事务回滚日志(undo_log)表的记录,将商品恢复成旧的库存数据:
  • 然后删除事务日志,最终完成第二阶段回滚操作:

1.3.3 第二阶段:控制全局事务最终提交

  • 如果全局事务成功,要完成最终提交,AT模式最终提交操作非常简单,只需要删除日志数据即可。
    首先接收到 TC 的全局事务提交指令:
  • 接着,直接删除事务日志,就完成了第二阶段提交操作:

2. Seata Server - TC全局事务协调器

2.1 概述

在Seata AT 事务中,有三个AT 事务的角色:TC(事务协调器)、TM(事务管理器)和RM(资源管理器),其中 TM 和 RM 是嵌入在业务应用中的,而 TC 则是一个独立服务。

2.2 下载

Seata Server 就是 TC,直接从官方仓库下载启动即可,下载地址:https://github.com/seata/seata/releases

3. seata-server相关的配置与启动

3.1 启动参数设置

启动文件:seata-server.bat
用文本编辑器打开文件,找到文件中这一行:

%JAVACMD% %JAVA_OPTS% -server -Xmx2048m -Xms2048m -Xmn1024m -Xss512k -XX:Sur......

看到 Seata Server 默认使用 2G 内存,测试环境我们可以把内存调低:

%JAVACMD% %JAVA_OPTS% -server -Xmx256m -Xms256m -Xmn128m -Xss512k -XX:Sur......

3.2 file.conf 配置数据的存储位置

Seata 需要存储全局事务信息、分支事务信息、全局锁信息,这些数据需要设定存储的位置。
可以放在本地文件、配置中心、db数据库、redis缓存中,这里我们选择db数据库,还需要配置数据库的相关参数信息。

3.3 registry.conf 配置注册中心

Seata Server 要向注册中心进行注册,这样,其他服务就可以通过注册中心去发现 Seata Server,与 Seata Server 进行通信。
Seata 支持多款注册中心服务:nacos 、eureka、redis、zk、consul、etcd3、sofa。
项目中我们使用 eureka 注册中心,这还需要在 registry.conf 文件中配置:eureka服务的连接地址、注册的服务名。

3.4 启动seata-server

双击 seata-server.bat 启动 Seata Server。
可查看 Eureka 注册中心 Seata Server 的注册信息:

4. 入门案例

4.1 order订单服务添加 Seata AT 事务

在订单项目中要启动全局事务,还要执行订单保存的分支事务。

4.1.1 order-parent 添加 seata 依赖

打开父工程的pom.xml文件,把其中注释的代码取消注释,其即为seata依赖。

<dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-alibaba-seata</artifactId>
          <version>${spring-cloud-alibaba-seata.version}</version>
          <exclusions>
            <exclusion>
              <artifactId>seata-all</artifactId>
              <groupId>io.seata</groupId>
            </exclusion>
          </exclusions>
        </dependency>
        <dependency>
          <groupId>io.seata</groupId>
          <artifactId>seata-all</artifactId>
          <version>${seata.version}</version>
        </dependency>

4.1.2 三个配置文件

1. application.yml

TC 事务协调器通过“事务组”的方式将多个服务组织成一个全局事务。每个服务启动时要向TC注册,加入同一个事务组。

spring:
  application:
    name: order
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    jdbcUrl: ${spring.datasource.url}
    username: root
    password: root

  cloud:
    alibaba:
      seata:
        tx-service-group: order_tx_group

server:
  port: 8083
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

mybatis-plus:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: cn.tedu.entity
      configuration:
        map-underscore-to-camel-case: true
logging:
  level:
    cn.tedu.mapper: debug

2. registry.conf

需要从注册中心获得 TC 的地址,这里配置注册中心的地址。
TC 在注册中心注册的服务ID在 file.conf 中指定。

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "eureka"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    # application = "default"
    # weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
    password = ""
    cluster = "default"
    timeout = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
  type = "file"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    group = "SEATA_GROUP"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

3. file.conf

在这里我们指定 TC 的服务ID seata-server

vgroupMapping.order_tx_group = "seata-server"

order_tx_group 对应 application.yml 中注册的事务组名。

transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #transaction service group mapping
  # order_tx_group 与 yml 中的 “tx-service-group: order_tx_group” 配置一致
  # “seata-server” 与 TC 服务器的注册名一致
  # 从eureka获取seata-server的地址,再向seata-server注册自己,设置group
  # 事务组对应使用的协调器:seata-server
  vgroupMapping.order_tx_group = "seata-server"
  #only support when registry.type=file, please don't set multiple addresses
  order_tx_group.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}

4.1.3 创建 seata 数据源代理对象

Seata AT 事务对业务代码无侵入,全自动化处理全局事务,其功能是靠 Seata 的数据源代理工具实现的。
这里我们创建 Seata 的数据源代理,并排除 Spring 默认的数据源。
注意:
需要在业务层的业务方法上添加注解:
@Transactional //本地事务
@GlobalTransactional //开启全局事务
需要在启动类上添加注解:排除默认数据源。

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) //排除默认的数据源自动配置类,使用自定义的自动配置
package cn.tedu;

import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
 * 自定义的数据源自动配置类
 * 用来创建AT事务的数据源代理对象
 */
@Configuration
public class DSAutoConfiguration {
    @ConfigurationProperties(prefix = "spring.datasource") //注入数据库参数
    @Bean //自定义数据源
    public DataSource dataSource(){
        return new HikariDataSource();
    }

    @Primary //首选对象
    @Bean //数据源代理对象
    public DataSource dataSourceProxy(DataSource ds){
        return new DataSourceProxy(ds);
    }
}

4.2 account账户和storage库存添加Seata AT事务

4.2.1 三个配置文件

1. application.yml

仿照order中的yml文件,把以下代码添加至相应的位置即可。

 jdbcUrl: ${spring.datasource.url}

cloud:
    alibaba:
      seata:
        tx-service-group: order_tx_group

2. registry.conf与file.conf 文件

这两个文件与order模块中的文件完全相同,可直接复制到对应的目录下。

4.2.2 创建seata数据源代理对象

直接复制order中的DSAutoConfiguration类,到相应的目录下即可。
注意:
还需在业务层的实现类的业务方法上添加注解:@Transactional //本地事务控制
还需在启动类上,添加排除默认数据源的注解:@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) //排除默认数据源

4.3 测试

  • 按顺序启动项目:
    Eureka
    Seata Server
    Easy Id Generator
    Storage
    Account
    Order

  • 访问:http://localhost:8083/order/create?userId=1&productId=1&count=10&money=100

  • 订单会调用库存和账户,这三个服务会分别启动一个分支事务,三个分支事务一起组成一个全局事务。
    观察三个项目的控制台都有Seata AT事务的日志。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

分布式事务之Seata AT 事务 的相关文章

  • 【Week14作业 C】Q老师的考验【矩阵快速幂】

    题意 xff1a Q老师 对数列有一种非同一般的热爱 xff0c 尤其是优美的斐波那契数列 这一天 xff0c Q老师 为了增强大家对于斐波那契数列的理解 xff0c 决定在斐波那契的基础上创建一个新的数列 f x 来考一考大家 数列 f
  • 【Week14作业 D】Q老师染砖【矩阵快速幂优化dp】

    题意 xff1a 衣食无忧的 Q老师 有一天突发奇想 xff0c 想要去感受一下劳动人民的艰苦生活 具体工作是这样的 xff0c 有 N 块砖排成一排染色 xff0c 每一块砖需要涂上红 蓝 绿 黄这 4 种颜色中的其中 1 种 且当这 N
  • 【Week15实验 D】瑞瑞爱上字符串【模拟】

    题意 xff1a 瑞瑞最近迷上了字符串 xff0c 因此决定出一个字符串的题 给定两个正整数 N K xff0c 考虑所有由 N 2 个 a 和 2 个 b 组成的字符串 xff0c 要求输出其中字典序第 K 小的 例如当 N 61 5 时
  • 【Week15作业 C】ZJM与纸条【KMP】

    题意 xff1a ZJM 的女朋友是一个书法家 xff0c 喜欢写一些好看的英文书法 有一天 ZJM 拿到了她写的纸条 xff0c 纸条上的字暗示了 ZJM 的女朋友 想给 ZJM 送生日礼物 ZJM 想知道自己收到的礼物是不是就是她送的
  • 【Week16实验 A】TT数鸭子【模拟】

    题意 xff1a 思路 xff1a 如果k gt 10 xff0c 则每个数都可以满足条件 xff0c 答案即为n xff1b 否则依次读入每一个数 xff0c 计算不同数位的个数sum xff0c 如果sum lt k xff0c 则这个
  • 【Week16实验 B】ZJM要抵御宇宙射线【模拟】

    题意 xff1a 思路 xff1a 对每个点都求出到其余点的距离平方 xff0c 然后取该点到其他点的距离平方的最大值为半径平方 xff0c 最后对所有点的半径平方取最小值 注意有多解时将x较小 y较小的点作为答案 总结 xff1a 一道简
  • 【Week16实验 C】宇宙狗的危机【区间dp】

    题意 xff1a 思路 xff1a 这道题是区间dp xff0c 不是BST xff01 xff01 xff01 令l i j 表示以j为根 j的左子树可到i这样的BST是否存在 xff0c r i j 表示以i为根 i的右子树可到j这样的
  • Ubuntu实现远程登陆之telnet——安装telnet服务端

    telnet是一种网络通信协议 xff0c 我们可以使用它登录远程服务器 xff0c 虽然telnet有安全问题这一硬伤 xff0c 但正因如此 xff0c 它十分简洁 xff0c 非常容易地在资源受限设备上 xff08 如嵌入式环境 xf
  • week6 限时大模拟 A - 掌握魔法の东东 II

    题意 思路 创建一个pair lt int int gt 类型的数组a xff0c 用来保存一副牌的花色以及大小 运用stl的vector xff0c 来存储手牌shoupai xff0c 随后使用dfs搜索 xff0c 数组a里的牌在手牌
  • Hadoop可视化神器-Hue安装、编译、运行

    下载安装 CDH版本 xff1a http archive cloudera com cdh5 官网 xff1a http gethue com tar zxf hue 3 9 0 cdh5 15 0 tar gz C opt module
  • 电脑通过wifi连接树莓派3

    前提要求 树莓派首先要能用网线和电脑连接起来 树莓派和电脑连接的是同一个wifi 电脑的网络设置和电脑网线直连树莓派的设置是一样的 xff0c 可以参照我的上篇博文设置 树莓派必须是树莓派3型 xff0c 因为只有这个型号才内置了wifi模
  • Ubuntu更换源说明

    Ubuntu更换源说明 更换源步骤如下 xff1a 备份源列表 sudo cp etc apt sources list etc apt sources list bak 命令行打开sources list文件 sudo gedit etc
  • MyBatis-Plus 分页查询以及自定义sql分页

    一 引言 分页查询每个人程序猿几乎都使用过 xff0c 但是有部分同学不懂什么是物理分页和逻辑分页 物理分页 xff1a 相当于执行了limit分页语句 xff0c 返回部分数据 物理分页只返回部分数据占用内存小 xff0c 能够获取数据库
  • golang为什么用协程而不是线程

    当协程阻塞的时候 xff0c 运行时会自动将同一操作系统线程上的其他协程移动到另一个可运行的线程上 xff0c 这样这些协程就不会阻塞 xff0c 并且这个操作对开发人员来说是透明的 调用协程的开销很小 xff0c 大概只在内存的栈区用几K
  • golang从channel读数据的各种情况

    文章目录 用var定义channel且不make用var定义channel且make直给写操作加for直给读操作加for读写都加for读channel的第二个返回值关闭channel继续读写完然后关闭channel再开始读加个selectc
  • 一个小技巧:修改swagger源码让postman更好用

    本文假设你知道swagger xff0c 并且会用go generate swagger generate spec o swagger json生成json 首先生成的swagger json是可以直接导入到postman的 xff0c
  • rancher导入集群时证书报错

    rancher导入集群时证书报错 现象 导入集群时 xff0c cattle cluster agent报错如下 xff1a time 61 34 2022 06 28T08 00 28Z 34 level 61 error msg 61
  • goland远程调试k8s上容器

    goland远程调试k8s上容器 上次也遇到过一次需要在k8s的容器里debug程序的情况 xff0c 当时找了个偷懒的办法绕过去了 xff0c 这回绕不过去了 xff0c 老老实实整一把 主要还是用dlv配合goland的远程调试功能 选
  • 星际争霸2免修改器修改绝对一劳永逸

    那年那个冬天 xff0c 那是的我还是个那么纯洁的同学 长话短说 xff0c 就那个寒假百无聊赖的玩了玩星际争霸2单机模式 xff0c 然后各种被虐 劳资表示不服 xff0c 于是开启上帝模式打开游戏安装目录 随后将各种模式下的cpu玩家海
  • kali 简单版攻击WiFi(附详细教程)

    首先检查是否有无限网卡 在root终端输入ifconfig xff0c 检查是否有wlan0 然后分为以下几个步骤 xff08 review专用 xff0c 没有基础就看下面详细教程 xff09 1 输入airmon ng start wl

随机推荐