Seata 入门教程 - 基础篇(2020 年)

大纲

前言

官方资源

分布式事务基础

Seata 介绍

Seata 的简介

2019 年 1 月,阿里巴巴中间件团队发起了开源项目 Fescar(Fast & Easy Commit And Rollback),其愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题。Fescar 开源后,蚂蚁金服加入 Fescar 社区参与共建,并在 Fescar 0.4.0 版本中贡献了 TCC 模式。为了打造更中立、更开放、生态更加丰富的分布式事务开源社区,经过社区核心成员的投票,决定对 Fescar 进行品牌升级,于 2019 年 5 月 开始更名为 Seata,意为:Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事务解决方案,为用户提供了 AT、TCC、SAGA 和 XA 事务模式。Seata 融合了阿里巴巴和蚂蚁金服在分布式事务技术上的积累,并沉淀了新零售、云计算和新金融等场景下丰富的实践经验,但要实现适用于所有的分布式事务场景的愿景,仍有很长的路要走。更多介绍可参考:Seata 项目Seata 官方示例代码Seata 官网Seata 官方中文文档

Seata 的演进历史

  • (1) 早在 2007 年,阿里巴巴和蚂蚁集团内部开发了分布式事务中间件,用于解决电商、支付、物流等业务场景中应用数据的一致性问题。内部项目分别被称为 TXC(Taobao Transaction Constructor) / XTS(eXtended Transaction Service),该项目几乎在每笔订单的交易支付链路几乎都有使用。
  • (2) 自 2013 年以来,阿里巴巴和蚂蚁集团已在阿里云和金融云上向企业客户分别发布了分布式事务云服务产品 GTS(Global Transaction Service) / DTX (Distributed Transaction-eXtended),在各个行业领域积累了大量用户。
  • (3) 2019 年 1 月,阿里巴巴集团正式开源了该项目,项目命名为 Fescar(Fast & Easy Commit and Rollback)。项目开源以来,它受到了众多开发人员的热烈欢迎和赞扬,开源一周收获了超 3k star,曾一度蝉联 GitHub Trending 排行榜第一。
  • (4) 2019 年 4 月,蚂蚁集团数据中间件团队加入了 Fescar 社区。为了创建一个更加开放和中立的社区,Fescar 改名为 Seata(Simple Extensible Autonomous Transaction Architecture),代码仓库从 Alibaba Organization 迁移到其独立的 Seata Organization。
  • (5) 2019 年 12 月,Seata 开源项目正式发布 1.0.0 GA 版本,标志着项目已基本可生产使用。
  • (6) 2023 年 10 月,为了更好的通过社区驱动技术的演进,阿里和蚂蚁集团正式将 Seata 捐赠给 Apache 基金会,该提案已通过了 Apache 基金会的投票决议,Seata 正式进入 Apache 孵化器。

Seata 的设计理念

Seata 的设计目标是对业务无侵入,因此从业务无侵入的 2PC 方案着手,在传统 2PC 的基础上改良。它把一个分布式事务理解成一个包含了若干分支事务的全局事务。全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个关系型数据库的本地事务。

seata-architecture

Seata 的三大组件

  • TC(Transaction Coordinator):事务协调器,维护全局和分支事务的状态,负责协调并驱动全局事务的提交或回滚
  • TM(Transaction Manager):事务管理器,控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
  • RM(Resource Manager):资源管理器,负责管理分支事务上的资源,向 TC 注册分支事务,上报分支事务的状态,接受 TC 的命令来提交或者回滚分支事务

seata-modules

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

总结

  • TC(事务协调器):就是 Seata 自身,负责维护全局事务和分支事务的状态,驱动全局事务提交或回滚。
  • TM(事务管理器):就是标注全局事务注解 @GlobalTransactional 启动入口动作的微服务模块(比如订单模块),它是事务的发起者,负责定义全局事务的范围,并根据 TC 维护的全局事务和分支事务状态,作出开启全局事务、提交全局事务、回滚全局事务的决议。
  • RM(资源管理器):就是 MySQL 数据库本身,可以有多个 RM,负责管理分支事务上的资源,向 TC 注册分支事务,上报分支事务状态,接受 TC 的命令来提交或者回滚分支事务。

Seata 的执行流程

Seata 三大组件会相互协作运行,TC 以 Seata 服务器(Server)形式独立部署,TM 和 RM 则是以 Seata Client 的形式集成在微服务应用中运行。Seata 的具体执行流程如下:

  • 1)A 服务的 TM 向 TC 申请开启一个全局事务,TC 就会创建一个全局事务并返回一个唯一的 XID
  • 2)A 服务的 RM 向 TC 注册分支事务,并将其纳入 XID 对应全局事务的管辖
  • 3)A 服务执行分支事务,向数据库执行操作
  • 4)A 服务开始远程调用 B 服务,此时 XID 会在微服务的调用链上传播
  • 5)B 服务的 RM 向 TC 注册分支事务,并将其纳入 XID 对应的全局事务的管辖
  • 6)B 服务执行分支事务,向数据库执行操作
  • 7)全局事务调用链处理完毕,TM 根据有无异常向 TC 发起全局事务的提交或者回滚决议
  • 8)TC 协调其管辖之下的所有分支事务,决定是否回滚

总结

  • (1) TM 向 TC 申请开启一个全局事务,全局事务创建成功后会生成一个全局唯一的 XID
  • (2) XID 在微服务调用链路的上下文中传播
  • (3) RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖
  • (4) TM 向 TC 发起针对 XID 的全局提交或回滚决议
  • (5) TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求

Seata 的 ORM 框架支持

Seata 虽然是保证数据一致性的组件,但对于 ORM 框架并没有特殊的要求,像主流的 Mybatis、Mybatis-Plus、Spring Data JPA、Hibernate 等都支持。这是因为 ORM 框架位于 JDBC 结构的上层,而 Seata 的 AT、XA 事务模式是对 JDBC 标准接口操作的拦截和增强。

Seata 实现的 2PC 与传统 2PC 的区别

  • 1)架构层次方面:传统 2PC 方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身,通过 XA 协议实现,而 Seata 的 RM 是以 Jar 包的形式作为中间件层部署在应用程序这一侧的
  • 2)两阶段提交方面:传统 2PC 无论第二阶段的决议是 Commit 还是 Rollback,事务性资源的锁都要保持到 Phase2(阶段二) 完成才释放。而 Seata 的做法是在 Phase1(阶段一) 就将本地事务提交,这样就可以省去 Phase2(阶段二) 持锁的时间,整体提高了效率

Seata Server 安装

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

Seata Server 下载

1)Seata Server 的官方下载地址在这里,直接下载已编译好的二进制包(seata-server-1.4.0.tar.gz ),然后解压即可使用

1
2
3
4
5
# 下载
$ wget https://github.com/seata/seata/releases/download/v1.4.0/seata-server-1.4.0.tar.gz

# 解压
$ tar -xvf seata-server-1.4.0.tar.gz

2)Seata 的初始化资源的官方下载地址在这里,需要下载 Seata 的源代码包(Source code),后面初始化数据库或者配置中心时会用到资源目录里的文件

1
2
3
4
5
6
7
8
9
10
11
# 下载
$ wget https://github.com/seata/seata/archive/v1.4.0.tar.gz

# 解压
# tar -xvf v1.4.0.tar.gz

# 资源目录的结构
seata-1.4.0/script
├── client
├── config-center
└── server

资源目录说明如下:

  • server:Server 端数据库脚本及各个容器配置
  • client:存放 Client 端的 SQL 脚本、参数配置
  • config-center:各个配置中心参数导入脚本,其中的 config.txt(包含 Server 和 Client,原名为 nacos-config.txt) 为通用参数文件

Seata Server 配置

1)将 Seata Server(TC)的存储模式更改为 DB,即使用数据库来存储全局事务会话信息,同时自定义事务组的名称。这里演示使用的数据库为 MySQL,默认支持的数据库类型包括:MySQL、Oracle、PostgreSQL、H2、Oceanbase,其中 service.vgroupMapping 的详细介绍可以看自定义事务组的名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 备份配置文件
$ cp seata/conf/file.conf seata/conf/file.conf.bak

# 编辑配置文件,更改或者新增以下内容
$ vim seata/conf/file.conf

service {
vgroupMapping.tx_group_test = "default" #自定义事务组的名称,若不存在service配置项,直接新增对应的配置内容即可
default.grouplist = "127.0.0.1:8091"
enableDegrade = false
disable = false
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
disableGlobalTransaction = false
}

store {
mode = "db" # 存储模式

db {
dbType = "mysql" # 数据库类型
datasource = "druid" # 数据库连接池
driverClassName = "com.mysql.cj.jdbc.Driver" # 数据库驱动
url = "jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false" # 数据库连接地址
user = "mysql" # 数据库用户名
password = "mysql" # 数据库密码
}
}

2)初始化 Seata Server(TC)依赖的 MySQL 数据库,用于存储全局事务会话信息,SQL 初始化脚本的位置是 Seata 源码目录下的 script/server/db/mysql.sql。全局事务会话信息由三块内容构成,全局事务 –> 分支事务 –> 全局锁,对应的表分别是 global_tablebranch_tablelock_table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建Seata数据库
mysql> create database seata default character set utf8;

# 切换数据库
mysql> use seata;

# 执行SQL初始化脚本
mysql> source seata-1.4.0/script/server/db/mysql.sql

# 查看数据库表
mysql> show tables;
+-----------------+
| Tables_in_seata |
+-----------------+
| branch_table |
| global_table |
| lock_table |
+-----------------+
3 rows in set (0.00 sec)

3)指定 Seata Server(TC)依赖的注册中心,这里使用的注册中心是 Nacos。为了演示方便,这里不再使用配置中心来存储 TC 的相关配置,即直接使用本地的 file.conf 配置文件。默认支持的注册中心与配置中心列表如下:

  • 配置中心支持类型:File、Nacos 、Apollo、Zookeeper、Consul、Etcd3
  • 注册中心支持类型:File 、Nacos 、Eureka、Zookeeper、Consul、Etcd3、Sofa、Redis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 备份配置文件
$ cp seata/conf/registry.conf seata/conf/registry.conf.bak

# 编辑配置文件,更改或者新增以下内容
$ vim seata/conf/registry.conf

registry {
type = "nacos"

nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = ""
password = ""
}
}

config {
type = "file"

file {
name = "file.conf"
}
}

Seata Server 启动

先将 Seata Server 依赖的数据库、注册中心服务启动了,最后才启动 Seata Server。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建GC的日志目录
$ mkdir seata/logs

# 进入bin目录
$ cd seata/bin

# 执行启动脚本
$ sh seata-server.sh

# 或者后台启动
$ nohup sh seata-server.sh &

# 或者指定启动参数
$ sh seata-server.sh -h 127.0.0.1 -p 8091 -n 1

启动参数说明如下:

  • -h: 注册到注册中心的 IP
  • -p: Seata Server 的本地监听端口,默认端口是 8091
  • -m: 全局事务会话信息存储模式,file、db、redis,优先读取启动参数 (Seata-Server 1.3 及以上版本支持 Redis)
  • -n: Server Node,多个 Server 时,需区分各自节点,用于生成不同区间的 transactionId,以免冲突
  • -e: 多环境配置可以参考这里

特别注意

堆内存建议分配 2G,堆外内存 1G,JVM 的内存参数可以直接在 seata/bin/-server.sh 脚本里调整。

Seata Server 成功启动后,在注册中心的服务列表里,可以看到 Seata Server 的服务已经成功注册:

seata-nacos-service

Seata Server 配置介绍

配置文件说明

  • registry.conf:用于指定 Seata Server(TC) 的注册中心和获取配置信息的方式,默认获取方式是 file。如果使用了其他注册中心,可以将 Seata Server 自身也注册到注册中心(如 Nacos)
  • file.conf:用于指定 Seata Server(TC)的相关配置。如果使用了配置中心,可以将 file.conf 文件里的配置信息都存储到配置中心(如 Nacos)

配置中心使用

若在 registry.conf 中指定使用配置中心来存储 TC 的相关配置(如下),即利用配置中心来替代 file.conf 配置文件,那么此时需要手动将 file.conf 里的配置信息添加到配置中心

1
2
3
4
5
6
7
8
9
10
11
12
13
config {
type = "nacos"

nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
}

...
}

Seata 官方提供了将配置信息批量写入到各种主流配置中心的 Shell 脚本,存放路径是在 Seata 源码目录下的 script/config-center 目录(如下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
script/config-center
├── apollo
│   └── apollo-config.sh
├── config.txt
├── consul
│   └── consul-config.sh
├── etcd3
│   └── etcd3-config.sh
├── nacos
│   ├── nacos-config.py
│   └── nacos-config.sh
├── README.md
└── zk
└── zk-config.sh

其中 config.txt 为通用参数文件,包含了 Seata Server 需要的所有配置信息,只需执行对应的 Shell 脚本将配置信息写入到配置中心即可。值得一提的是,config.txt 文件必须在 xxxx.sh 的上级目录里;若使用 Nacos 作为配置中心,执行脚本时可以指定一些启动参数,如 Nacos 的 IP、端口号、命名空间、配置组等,Shell 脚本的具体使用方法可以查看 script/config-center/README.md 说明文档。

1
$ nacos-config.sh -h 127.0.0.1 -p 8848 -t namespace -g group -u username -w password

成功批量导入配置信息到配置中心后,控制台会输出如下提示:

1
2
3
4
=========================================================================
Complete initialization parameters, total-count:79 , failure-count:0
=========================================================================
Init nacos config finished, please start seata-server.

访问 Nacos 的控制台,可以看到已经有对应的配置信息

seata-inport-to-config-center

配置 TC 的存储模式

Seata Server(TC)的存储模式现有 File、DB、Redis 三种(后续将引入 Raft、Mongodb),需要在 file.conf 配置文件中指定(如下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
store {
mode = "file"

## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}

...
}

默认存储模式为 File,若使用 File 模式则无需改动任何配置,直接启动即可,每种模式的说明如下:

  • File 模式为单机模式,全局事务会话信息在内存中读写,并持久化为本地文件 root.data,性能较高
  • DB 模式为高可用模式,全局事务会话信息通过 DB 共享,性能会差一点
  • Redis 模式在 Seata-Server 1.3 及以上版本开始支持,性能较高,存在事务信息丢失风险,需要配置适合当前业务场景的 Redis 持久化策略

自定义事务组的名称

特别注意,file.conf 中的 service.vgroupMapping 这个配置,在 Spring Cloud 中的值默认是 ${spring.application.name}-fescar-service-group,可以通过指定 application.yml 中的 spring.cloud.alibaba.seata.tx-service-group 这个属性来覆盖;但是必须要和 file.conf 中的 service.vgroupMapping 一致,否则会出现 no available service 'null' found, please make sure registry config correct 的错误,举例说明如下:

1
2
3
service {
vgroupMapping.tx_group_test = "default"
}
1
2
3
4
5
spring:
cloud:
alibaba:
seata:
tx-service-group: tx_group_test

在上述的配置中,Spring Cloud 中 tx-service-group 的值也必须为 tx_group_test;如果将 vgroupMapping.xxxx 中的 xxxx(Key 值)改为 abcdefg,则 Spring Cloud 中 tx-service-group 的值也必须为 abcdefg,即这两个值必须保持一致

Seata Server 的坑

default.grouplist 属性

在 Seata Server 的 file.conf 配置文件中,有个 default.grouplist 配置,该配置的使用说明如下:

  • 1)只有在 registry.conf 中配置了 registry.type=file,即注册中心是 File 模式时,该配置才会起作用
  • 2)对应的值可以配置多个,配置多个就需要搭建 Seata Server 集群。由于默认并未提供本地文件的同步功能,所以在 store.mode=file 模式下,这种集群方式的配置会报错;如果 Seata Server 搭建为集群,建议配置为 store.mode=db,这样就可以通过 DB 来共享 TC(Seata Server) 集群间的数据
  • 3)当 registry.type=file 时,这个 default.grouplist 才会起作用,但是 File 方式并不能提供一个注册中心的完整功能,比如健康检查机制,实例列表的更新剔出等,建议选择 Nacos 、Eureka、Redis、Zookeeper、Consul、Etcd3、Sofa 作为注册中心
  • 4)registry.type=fileconfig.type=file 的设计初衷,是让开发者在不依赖第三方注册中心或配置中心的前提下,可以通过 File 这种简单的直连快速验证 Seata 服务,达到快速上手的目的

service.vgroup_mapping 属性

Seata Server <=1.0 的版本用的是 service.vgroup_mapping,但在新版本里改成了 service.vgroupMapping。若应用启动后无法连接 Seata Server,且抛出了以下异常信息,此时应该注意使用的是不是旧的 service.vgroup_mapping

1
no available service 'null' found, please make sure registry config correct

file.conf 配置文件中,service.vgroupMapping 支持配置多个:

1
2
3
4
service.vgroupMapping.user-service-group=default
service.vgroupMapping.order-service-group=default
service.vgroupMapping.account-service-group=default
service.vgroupMapping.storage-service-group=default

参考资料