Seata:Spring Cloud Alibaba分布式事务组件

随着业务的不断发展,单体架构已经无法满足我们的需求,分布式微服务架构逐渐成为大型互联网平台的首选,但所有使用分布式微服务架构的应用都必须面临一个十分棘手的问题,那就是“分布式事务”问题。

在分布式微服务架构中,几乎所有业务操作都需要多个服务协作才能完成。对于其中的某个服务而言,它的数据一致性可以交由其自身数据库事务来保证,但从整个分布式微服务架构来看,其全局数据的一致性却是无法保证的。

例如,用户在某电商系统下单购买了一件商品后,电商系统会执行下 4 步:

  1. 调用订单服务创建订单数据
  2. 调用库存服务扣减库存
  3. 调用账户服务扣减账户金额
  4. 最后调用订单服务修改订单状态

为了保证数据的正确性和一致性,我们必须保证所有这些操作要么全部成功,要么全部失败,否则就可能出现类似于商品库存已扣减,但用户账户资金尚未扣减的情况。各服务自身的事务特性显然是无法实现这一目标的,此时,我们可以通过分布式事务框架来解决这个问题。

Seata 就是这样一个分布式事务处理框架,它是由阿里巴巴和蚂蚁金服共同开源的分布式事务解决方案,能够在微服务架构下提供高性能且简单易用的分布式事务服务。

Seata 的发展历程

阿里巴巴作为国内最早一批进行应用分布式(微服务化)改造的企业,很早就遇到微服务架构下的分布式事务问题。

阿里巴巴对于分布式事务问题先后发布了以下解决方案:

  • 2014 年,阿里中间件团队发布 TXC(Taobao Transaction Constructor),为集团内应用提供分布式事务服务。
  • 2016 年,TXC 在经过产品化改造后,以 GTS(Global Transaction Service) 的身份登陆阿里云,成为当时业界唯一一款云上分布式事务产品。在阿云里的公有云、专有云解决方案中,开始服务于众多外部客户。
  • 2019 年起,基于 TXC 和 GTS 的技术积累,阿里中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback, FESCAR),和社区一起建设这个分布式事务解决方案。
  • 2019 年 fescar 被重命名为了seata(simple extensiable autonomous transaction architecture)。
  • TXC、GTS、Fescar 以及 seata 一脉相承,为解决微服务架构下的分布式事务问题交出了一份与众不同的答卷。

分布式事务相关概念

分布式事务主要涉及以下概念:

  • 事务:由一组操作构成的可靠、独立的工作单元,事务具备 ACID 的特性,即原子性、一致性、隔离性和持久性
  • 本地事务本地事务由本地资源管理器(通常指数据库管理系统 DBMS,例如 MySQL、Oracle 等)管理,严格地支持 ACID 特性,高效可靠。本地事务不具备分布式事务的处理能力,隔离的最小单位受限于资源管理器,即本地事务只能对自己数据库的操作进行控制,对于其他数据库的操作则无能为力。
  • 全局事务:全局事务指的是一次性操作多个资源管理器完成的事务,由一组分支事务组成
  • 分支事务:在分布式事务中,就是一个个受全局事务管辖和协调的本地事务

我们可以将分布式事务理解成一个包含了若干个分支事务的全局事务。全局事务的职责是协调其管辖的各个分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个满足 ACID 特性的本地事务

Seata 整体工作流程

Seata 对分布式事务的协调和控制,主要是通过 XID 和 3 个核心组件实现的。

XID

XID 是全局事务的唯一标识,它可以在服务的调用链路中传递,绑定到服务的事务上下文中

核心组件

Seata 定义了 3 个核心组件:

  • TC(Transaction Coordinator):事务协调器,它是事务的协调者(这里指的是 Seata 服务器),主要负责维护全局事务和分支事务的状态,驱动全局事务提交或回滚
  • TM(Transaction Manager):事务管理器,它是事务的发起者,负责定义全局事务的范围,并根据 TC 维护的全局事务和分支事务状态,做出开始事务、提交事务、回滚事务的决议
  • RM(Resource Manager):资源管理器,它是资源的管理者(这里可以将其理解为各服务使用的数据库)。它负责管理分支事务上的资源,向 TC 注册分支事务,汇报分支事务状态,驱动分支事务的提交或回滚

以上三个组件相互协作,TC 以 Seata 服务器(Server)形式独立部署,TM 和 RM 则是以 Seata Client 的形式集成在微服务中运行,其整体工作流程如下图。

 

 

 Seata 的整体工作流程如下:

    1. TM 向 TC 申请开启一个全局事务,全局事务创建成功后,TC 会针对这个全局事务生成一个全局唯一的 XID;
    2. XID 通过服务的调用链传递到其他服务;
    3. RM 向 TC 注册一个分支事务,并将其纳入 XID 对应全局事务的管辖;
    4. TM 根据 TC 收集的各个分支事务的执行结果,向 TC 发起全局事务提交或回滚决议;
    5. TC 调度 XID 下管辖的所有分支事务完成提交或回滚操作。

Seata AT 模式

Seata 提供了 AT、TCC、SAGA 和 XA 四种事务模式,可以快速有效地对分布式事务进行控制。

在这四种事务模式中使用最多,最方便的就是 AT 模式。与其他事务模式相比,AT 模式可以应对大多数的业务场景,且基本可以做到无业务入侵,开发人员能够有更多的精力关注于业务逻辑开发。

AT 模式的前提

任何应用想要使用 Seata 的 AT 模式对分布式事务进行控制,必须满足以下 2 个前提:

  • 必须使用支持本地 ACID 事务特性的关系型数据库,例如 MySQL、Oracle 等;
  • 应用程序必须是使用 JDBC 对数据库进行访问的 JAVA 应用

此外,我们还需要针对业务中涉及的各个数据库表,分别创建一个 UNDO_LOG(回滚日志)表。不同数据库在创建 UNDO_LOG 表时会略有不同,以 MySQL 为例,其 UNDO_LOG 表的创表语句如下

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

AT 模式的工作机制

Seata 的 AT 模式工作时大致可以分为以两个阶段,下面我们就结合一个实例来对 AT 模式的工作机制进行介绍。

假设某数据库中存在一张名为 webset 的表,表结构如下。

列名类型主键
id bigint(20)
name varchar(255)  
url varchar(255)

 

在某次分支事务中,我们需要在 webset 表中执行以下操作。

update webset set url = 'org.example' where name = '实例';

一阶段

Seata AT 模式一阶段的工作流程如下图所示。

 

 

 Seata AT 模式一阶段工作流程如下。

1. 获取 SQL 的基本信息:Seata 拦截并解析业务 SQL,得到 SQL 的操作类型(UPDATE)、表名(webset)、判断条件(where name = '实例')等相关信息。

2. 查询前镜像:根据得到的业务 SQL 信息,生成“前镜像查询语句”。

select id,name,url from webset where  name='实例';

执行“前镜像查询语句”,得到即将执行操作的数据,并将其保存为“前镜像数据(beforeImage)”。

idnameurl
1 实例 org.example.com

idnameurl1C语言中文网biancheng.net

3. 执行业务 SQL(update webset set url = 'org.example' where name = '实例';),将这条记录的 url 修改为 org.example。

4. 查询后镜像:根据“前镜像数据”的主键(id : 1),生成“后镜像查询语句”。

select id,name,url from webset where  id= 1;

执行“后镜像查询语句”,得到执行业务操作后的数据,并将其保存为“后镜像数据(afterImage)”。

idnameurl
1 实例 org.example

5. 插入回滚日志:将前后镜像数据和业务 SQL 的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中,示例回滚日志如下。

{
  "@class": "io.seata.rm.datasource.undo.BranchUndoLog",
  "xid": "172.26.54.1:8091:5962967415319516023",
  "branchId": 5962967415319516027,
  "sqlUndoLogs": [
    "java.util.ArrayList",
    [
      {
        "@class": "io.seata.rm.datasource.undo.SQLUndoLog",
        "sqlType": "UPDATE",
        "tableName": "webset",
        "beforeImage": {
          "@class": "io.seata.rm.datasource.sql.struct.TableRecords",
          "tableName": "webset",
          "rows": [
            "java.util.ArrayList",
            [
              {
                "@class": "io.seata.rm.datasource.sql.struct.Row",
                "fields": [
                  "java.util.ArrayList",
                  [
                    {
                      "@class": "io.seata.rm.datasource.sql.struct.Field",
                      "name": "id",
                      "keyType": "PRIMARY_KEY",
                      "type": -5,
                      "value": [
                        "java.lang.Long",
                        1
                      ]
                    },
                    {
                      "@class": "io.seata.rm.datasource.sql.struct.Field",
                      "name": "url",
                      "keyType": "NULL",
                      "type": 12,
                      "value": "org.example.com"
                    }
                  ]
                ]
              }
            ]
          ]
        },
        "afterImage": {
          "@class": "io.seata.rm.datasource.sql.struct.TableRecords",
          "tableName": "webset",
          "rows": [
            "java.util.ArrayList",
            [
              {
                "@class": "io.seata.rm.datasource.sql.struct.Row",
                "fields": [
                  "java.util.ArrayList",
                  [
                    {
                      "@class": "io.seata.rm.datasource.sql.struct.Field",
                      "name": "id",
                      "keyType": "PRIMARY_KEY",
                      "type": -5,
                      "value": [
                        "java.lang.Long",
                        1
                      ]
                    },
                    {
                      "@class": "io.seata.rm.datasource.sql.struct.Field",
                      "name": "url",
                      "keyType": "NULL",
                      "type": 12,
                      "value": "org.example"
                    }
                  ]
                ]
              }
            ]
          ]
        }
      }
    ]
  ]
}

6. 注册分支事务,生成行锁:在这次业务操作的本地事务提交前,RM 会向 TC 注册分支事务,并针对主键 id 为 1 的记录生成行锁。

以上所有操作均在同一个数据库事务内完成,可以保证一阶段的操作的原子性。

7. 本地事务提交:将业务数据的更新和前面生成的 UNDO_LOG 一并提交

8. 上报执行结果:将本地事务提交的结果上报给 TC。

二阶段:提交

当所有的 RM 都将自己分支事务的提交结果上报给 TC 后,TM 根据 TC 收集的各个分支事务的执行结果,来决定向 TC 发起全局事务的提交或回滚。

若所有分支事务都执行成功,TM 向 TC 发起全局事务的提交,并批量删除各个 RM 保存的 UNDO_LOG 记录和行锁;否则全局事务回滚。

二阶段:回滚

若全局事务中的任何一个分支事务失败,则 TM 向 TC 发起全局事务的回滚,并开启一个本地事务,执行如下操作。

1. 查找 UNDO_LOG 记录:通过 XID 和分支事务 ID(Branch ID) 查找所有的 UNDO_LOG 记录。

2. 数据校验:将 UNDO_LOG 中的后镜像数据(afterImage)与当前数据进行比较,如果有不同,则说明数据被当前全局事务之外的动作所修改,需要人工对这些数据进行处理。

3. 生成回滚语句:根据 UNDO_LOG 中的前镜像(beforeImage)和业务 SQL 的相关信息生成回滚语句:

update webset set url= 'org.example.com' where id = 1;

4. 还原数据:执行回滚语句,并将前镜像数据、后镜像数据以及行锁删除。

5. 提交事务:提交本地事务,并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

下载 Seata 服务器

1. 使用浏览器访问“https://github.com/seata/seata/releases/tag/v1.4.2”,在 Seata Server 下载页面分别下载“seata-server-1.4.2.zip”,如下图。

 

 

Seata 配置中心

所谓“配置中心”,就像是一个“大衣柜”,内部存放着各种各样的配置文件,我们可以根据自己的需要从其中获取指定的配置文件,加载到对应的客户端中。

Seata 支持多种配置中心:

  • nacos
  • consul
  • apollo
  • etcd
  • zookeeper
  • file (读本地文件,包含 conf、properties、yml 等配置文件)

Seata 整合 Nacos 配置中心

对于 Seata 来说,Nacos 是一种重要的配置中心实现。

Seata 整合 Nacos 配置中心的操作步骤十分简单,大致步骤如下.

添加 Maven 依赖

我们需要将 nacos-client 的 Maven 依赖添加到项目的 pom.xml 文件中

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>最新版</version>
</dependency>
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.2.0及以上版本</version>
</dependency>

在 Spring Cloud 项目中,通常只需要在 pom.xml 中添加 spring-cloud-starter-alibaba-seata 依赖即可,代码如下。

<!--引入 seata 依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

Seata Server 配置

在 Seata Server 安装目录下的 config/registry.conf 中,将配置方式(config.type)修改为 Nacos,并对 Nacos 配置中心的相关信息进行配置,示例配置如下。

config {
  # Seata 支持 file、nacos 、apollo、zk、consul、etcd3 等多种配置中心
  #配置方式修改为 nacos
  type = "nacos"
  nacos {
    #修改为使用的 nacos 服务器地址
    serverAddr = "127.0.0.1:1111"
    #配置中心的命名空间
    namespace = ""
    #配置中心所在的分组
    group = "SEATA_GROUP"
    #Nacos 配置中心的用户名
    username = "nacos"
    #Nacos 配置中心的密码
    password = "nacos"
  }
}

Seata Client 配置

我们可以在 Seata Client(即微服务架构中的服务)中,通过 application.yml 等配置文件对 Nacos 配置中心进行配置,示例代码如下。

seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:1111 # Nacos 配置中心的地址
      group : "SEATA_GROUP"  #分组
      namespace: ""
      username: "nacos"   #Nacos 配置中心的用户名
      password: "nacos"  #Nacos 配置中心的密码

上传配置到 Nacos 配置中心

在完成了 Seata 服务端和客户端的相关配置后,接下来,我们就可以将配置上传的 Nacos 配置中心了,操作步骤如下。

1. 我们需要获取一个名为 config.txt 的文本文件,该文件包含了 Seata 配置的所有参数明细。

我们可以通过 Seata Server 源码/script/config-center 目录中获取 config.txt,然后根据自己需要修改其中的配置,如下图。

 

 

 2. 在 /script/config-center/nacos 目录中,有以下 2 个 Seata 脚本:

  • nacos-config.py:python 脚本。
  • nacos-config.sh:为 Linux 脚本,我们可以在 Windows 下通过 Git 命令,将 config.txt 中的 Seata 配置上传到 Nacos 配置中心。

在 seata-1.4.2\script\config-center\nacos 目录下,右键鼠标选择 Git Bush Here,并在弹出的 Git 命令窗口中执行以下命令,将 config.txt 中的配置上传到 Nacos 配置中心。

sh nacos-config.sh -h 127.0.0.1 -p 1111  -g SEATA_GROUP -u nacos -w nacos

Git 命令各参数说明如下:

  • -h:Nacos 的 host,默认取值为 localhost
  • -p:端口号,默认取值为 8848
  • -g:Nacos 配置的分组,默认取值为 SEATA_GROUP
  • -u:Nacos 用户名
  • -w:Nacos 密码

验证 Nacos 配置中心

在以上所有步骤完成后,启动 Nacos Server,登陆 Nacos 控制台查看配置列表,结果如下图。

 

 

 

Seata 注册中心

所谓“注册中心”,可以说是微服务架构中的“通讯录”,它记录了服务与服务地址的映射关系。

在分布式微服务架构中,各个微服务都可以将自己注册到注册中心,当其他服务需要调用某个服务时,就可以从这里找到它的服务地址进行调用,常见的服务注册中心有 Nacos、Eureka、zookeeper 等。

Seata 支持多种服务注册中心:

  • eureka
  • consul
  • nacos
  • etcd
  • zookeeper
  • sofa
  • redis
  • file (直连)

Seata 通过这些服务注册中心,我们可以获取 Seata Sever 的服务地址,进行调用。

Seata 整合 Nacos 注册中心

对于 Seata 来说,Nacos 是一种重要的注册中心实现。

Seata 整合 Nacos 注册中心的步骤十分简单,步骤如下。

添加 Maven 依赖

将 nacos-client 的 Maven 依赖添加到项目的 pom.xml 文件中:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>最新版</version>
</dependency>
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.2.0及以上版本</version>
</dependency>

在 Spring Cloud 项目中,通常只需要在 pom.xml 中添加 spring-cloud-starter-alibaba-seata 依赖即可,代码如下。

<!--引入 seata 依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

Seata Server 配置注册中心

在 Seata Server 安装目录下的 config/registry.conf 中,将注册方式(registry.type)修改为 Nacos,并对 Nacos 注册中心的相关信息进行配置,示例配置如下。

registry {
  # Seata 支持 file 、nacos 、eureka、redis、zk、consul、etcd3、sofa 作为其注册中心
  # 将注册方式修改为 nacos
  type = "nacos"
  nacos {
    application = "seata-server"
    # 修改 nacos 注册中心的地址
    serverAddr = "127.0.0.1:1111"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
}

Seata Client 配置注册中心

我们可以在 Seata Client 的 application.yml 中,对 Nacos 注册中心进行配置,示例配置如下。

seata:
  registry:
    type: nacos
    nacos:
      application: seata-server  
      server-addr: 127.0.0.1:1111 # Nacos 注册中心的地址
      group : "SEATA_GROUP" #分组
      namespace: ""   
      username: "nacos"   #Nacos 注册中心的用户名
      password: "nacos"   # Nacos 注册中心的密码

验证 Nacos 注册中心

在以上所有步骤完成后,先启动 Nacos Server 再启动 Seata Server,登录 Nacos 控制台查看服务列表,结果如下图。

 

 

 从图 9 可以看出,seata-server 服务已经注册到了 Nacos 注册中心。

Seata 事务分组

事务分组是 Seata 提供的一种 TC(Seata Server) 服务查找机制。

Seata 通过事务分组获取 TC 服务,流程如下:

  1. 在应用中配置事务分组。
  2. 应用通过配置中心去查找配置:service.vgroupMapping.{事务分组},该配置的值就是 TC 集群的名称。
  3. 获得集群名称后,应用通过一定的前后缀 + 集群名称去构造服务名。
  4. 得到服务名后,去注册中心去拉取服务列表,获得后端真实的 TC 服务列表。

下面我们以 Nacos 服务注册中心为例,介绍 Seata 事务的使用。

Seata Server 配置

在 Seata Server 的 config/registry.conf 中,进行如下配置。

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"                #使用 Nacos作为注册中心
  nacos {
    serverAddr = "127.0.0.1:1111"    # Nacos 注册中心的地址
    namespace = ""              # Nacos 命名空间id,"" 为 Nacos 保留 public 空间控件,用户勿配置 namespace = "public"
    cluster = "c.biancheng.net"         # seata-server在 Nacos 的集群名
  }
}
config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"                # 使用nacos作为配置中心
  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
}

Seata Client 配置

Seata Client 中 application.yml 的配置如下。

spring:
     alibaba:
      seata:
        tx-service-group: service-order-group  #事务分组名
seata:
  registry:
    type: nacos   #从 Nacos 获取 TC 服务
    nacos:
      server-addr: 127.0.0.1:1111
  config:
    type: nacos   #使用 Nacos 作为配置中心
    nacos:
      server-addr: 127.0.0.1:1111
      name

在以上配置中,我们通过 spring.cloud.alibaba.seata.tx-service-group 来配置 Seata 事务分组名,其默认取值为:

服务名-fescar-service-group 

上传配置到 Nacos

将以下配置上传到 Nacos 配置中心。

service.vgroupMapping.service-order-group=c.biancheng.net

在以上配置中,

  • service-order-group:为事务分组的名称;
  • c.biancheng.net:为 TC 集群的名称。

获取事务分组

1. 先启动 Nacos,再启动 Seata Server,最后再启动 Seata Client。

2. Seata Client 在启动时,会从 application.yml 的配置中,根据 spring.cloud.alibaba.seata.tx-service-group 获取事务分组的名称:service-order-group。

获取 TC 集群名

使用事务分组名“service-order-group”拼接成“service.vgroupMapping.service-order-group”,并从 Nacos 配置中心获取该配置的取值,这个值就是 TC 集群的名称:“c.biancheng.net”。

查找 TC 服务

根据 TC 集群名、Nacos 注册中心的地址(server-addr)以及命名空间(namespace),在 Nacos 注册中心找到真实的 TC 服务列表。

总结

通过事务分组获取服务名,共需要以下 3 步:

  1. 服务启动时,从配置文件中获取服务分组的名称;
  2. 从配置中心,通过事务分组名获取 TC 集群名;
  3. 根据 TC 群组名以及其他信息构建服务名,获取真实的 TC 服务列表。

启动 Seata Server

1. 建表

Seata Server 共有以下 3 种存储模式(store.mode):

模式说明准备工作
file 文件存储模式,默认存储模式;

该模式为单机模式,全局事务的会话信息在内存中读写,并持久化本地文件 root.data,性能较高
-
db 数据库存储模式;

该模式为高可用模式,全局事务会话信息通过数据库共享,性能较低。
建数据库表
redis 缓存存储模式;

Seata Server 1.3 及以上版本支持该模式,性能较高,但存在事务信息丢失风险,
配置 redis 持久化配置

在 db 模式下,我们需要针对全局事务的会话信息创建以下 3 张数据库表。

  • 全局事务表,对应的表为:global_table
  • 分支事务表,对应的表为:branch_table
  • 全局锁表,对应的表为:lock_table

在 MySQL 中,创建一个名为 seata 的数据库实例,并在该数据库内执行以下 SQL。

global_table 的建表 SQL 如下。

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

branch_table 的建表 SQL 如下。

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

lock_table 的建表 SQL 如下。

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

2. 修改 Seata Server 配置

在 seata-server-1.4.2/conf/ 目录下的 registry.conf 中,将 Seata Server 的服务注册方式(registry.type)和配置方式(config.type)都修改为 Nacos,修改内容如下。

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  # 将注册方式修改为 nacos
  type = "nacos"
  nacos {
    application = "seata-server"
    # 修改 nacos 的地址
    serverAddr = "127.0.0.1:1111"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
}
config {
  # file、nacos 、apollo、zk、consul、etcd3
  #配置方式修改为 nacos
  type = "nacos"
  nacos {
    #修改为使用的 nacos 服务器地址
    serverAddr = "127.0.0.1:1111"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    #不使用 seataServer.properties 方式配置
    #dataId = "seataServer.properties"
  }
}

3. 将 Seata 配置上传到 Nacos

1) 下载并解压 Seata Server 的源码 seata-1.4.2.zip,然后修改 seata-1.4.2/script/config-center 目录下的 config.txt,修改内容如下。

#将 Seata Server 的存储模式修改为 db
store.mode=db
# 数据库驱动
store.db.driverClassName=com.mysql.cj.jdbc.Driver
# 数据库 url
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&characterEncoding=UTF-8&useUnicode=true&serverTimezone=UTC 
# 数据库的用户名
store.db.user=root 
# 数据库的密码
store.db.password=root
# 自定义事务分组
service.vgroupMapping.service-order-group=default 
service.vgroupMapping.service-storage-group=default
service.vgroupMapping.service-account-group=default 

2) 在 seata-1.4.2\script\config-center\nacos 目录下,右键鼠标选择 Git Bush Here,在弹出的 Git 命令窗口中执行以下命令,将 config.txt 中的配置上传到 Nacos 配置中心。

sh nacos-config.sh -h 127.0.0.1 -p 1111  -g SEATA_GROUP -u nacos -w nacos

3) 当 Git 命令窗口出现以下执行日志时,则说明配置上传成功。

=========================================================================
Complete initialization parameters,  total-count:87 ,  failure-count:0
=========================================================================
Init nacos config finished, please start seata-server.

4) 使用浏览器访问 Nacos 服务器主页,查看 Nacos 配置列表,如下图。

 

 

 注意:在使用 Git 命令将配置上传到 Nacos 前,应该先确保 Nacos 服务器已启动。

4. 启动 Seata Server

双击 Seata Server 端 bin 目录下的启动脚本 seata-server.bat ,启动 Seata Server。

 

 

 Seata Server 启动日志如下。

6:52:48,549 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
16:52:48,549 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
16:52:48,550 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/C:/Users/79330/Desktop/seata-server-1.4.2/conf/logback.xml]
16:52:48,551 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs multiple times on the classpath.
16:52:48,551 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [jar:file:/C:/Users/79330/Desktop/seata-server-1.4.2/lib/seata-server-1.4.2.jar!/logback.xml]
……
SLF4J: A number (18) of logging calls during the initialization phase have been intercepted and are
SLF4J: now being replayed. These are subject to the filtering rules of the underlying logging system.
SLF4J: See also http://www.slf4j.org/codes.html#replay
16:52:49.003  INFO --- [                     main] io.seata.config.FileConfiguration        : The file name of the operation is registry
16:52:49.008  INFO --- [                     main] io.seata.config.FileConfiguration        : The configuration file used is C:\Users\79330\Desktop\seata-server-1.4.2\conf\registry.conf
16:52:51.063  INFO --- [                     main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
16:52:51.981  INFO --- [                     main] i.s.core.rpc.netty.NettyServerBootstrap  : Server started, listen port: 8091

业务系统集成 Seata 

接下来,我们就以电商系统为例,来演示下业务系统是如何集成 Seata 的。

在电商系统中,用户下单购买一件商品,需要以下 3 个服务提供支持:

  • Order(订单服务):创建和修改订单。
  • Storage(库存服务):对指定的商品扣除仓库库存。
  • Account(账户服务) :从用户帐户中扣除商品金额。

这三个微服务分别使用三个不同的数据库,架构图如下所示。

 

 

 当用户从这个电商网站购买了一件商品后,其服务调用步骤如下:

  1. 调用 Order 服务,创建一条订单数据,订单状态为“未完成”;
  2. 调用 Storage 服务,扣减商品库存;
  3. 调用 Account 服务,从用户账户中扣除商品金额;
  4. 调用 Order 服务,将订单状态修改为“已完成”。

 

实例:业务系统集成 Seata

创建订单(Order)服务

搭建库存(Storage)服务

搭建账户(Account)服务

 

posted @ 2022-03-30 15:22  幻影黑子  阅读(303)  评论(0编辑  收藏  举报