分布式事务组件****seata
单体应用,拆分成微服务架构后,每个微服务都是独立运行,拥有独立的进程,需要开发人员来解决"确保在分分布式环境下,服务之间的数据一致性"这个技术难点。
Seata就是为了解决这个技术难点而生的。
事务的概念
事务是由多个计算任务构成的一个具有明确边界的工作集合。有两个目的:
- 为数据库操作提供从"失败态"恢复到"正常态"的方法,提供在数据库出现异常时,确保数据一致性的方法。
- 当多个应用并发访问数据库时,可以隔离应用,确保应用能够安全地访问数据库。
事务具有4个特性**(ACID):**
原子性(Atomicity) 保证事务作为整体被执行。
一致性(Consistency) 数据完整。
隔离性(Isolation) 事务并发执行不能影响其他事务。
持久性(Durability) 数据永久保存。
分布式事务
事务的参与者、支持事务的服务器、资源服务器、事务管理器分别位于分布式系统的不同的实例上。一个大的操作,由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用。分布式事务需要保证这些小的操作要么全部执行成功,要么全部执行失败。
从本质上来,分布式事务就是为了确保不同应用间的数据一致性,包括RPC的数据一致和数据存储的数据一致性。
数据一致性
可以分为三类:
强一致性:在数据更新成功后,任意时刻,所有副本中的数据都是一致的。
弱一致性:在数据更新成功后,系统不承诺立即可以读取最新写入的数据,也承诺具体多久后可以读取最新写入的数据。
最终一致性:弱一致性的一种形式,在数据更新成功后,系统不承诺可以实时地返回最新写入的数据,但是可以保证一定会返回上一次更新操作之后最新数据,只是会有些时延。
为什么需要分布式事务
分布式事务场景分为两类:
事务只涉及一个应用,但涉及多个数据存储。
事务涉及多个应用,且每个应用可能连接着一个或者多个数据存储。
例如订单和商品服务同时访问订单库和商品库拆分成各自访问数据库。
Seata简介
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,其内部版本在阿里系内部一直扮演着应用架构层数据一致性的中间件角色,帮助经济体平稳的度过历年的双11,对上层业务进行了有力的技术支撑。经过多年沉淀与积累,其商业化产品先后在阿里云、金融云上售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata正式宣布对外开源,未来 Seata 将以社区共建的形式帮助用户快速落地分布式事务解决方案。

术语表
TC (Transaction Coordinator) - 事务协调者:
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器:
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器:
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
原理:
首先要有一个全局的事务协调者(TC),各个本地事务(RM)在开始摷行的时候,或者在执行的过程中,及时将自己的状态报告给全局事务协调者,这个全局事务协调者知道每一个分支事务目前的执行状态,当事务协调者发现所有的本地事务都执行成功时,就通知大家一起提交。如果发现在本次事务中,有本地事务执行失败的时候,就通知大家一起回滚(这个回滚不一定是真的回滚,而是一种反向补偿,因为可能已经读取数据了)。
一个事务什么时候开始、什么时候结束 ,也就是事务的边界在哪里,Seata中分布式事务通过TM来进行处理,实际上,就是通过一个注解来处理,@GlobalTransactional。
假设我们现在有三个微服务 M1,M2,M3,在M1中分别调用了M2和M3服务,为了确保M2和M3同时成功或同时失败,可以使用分布式事务。如果M2操作成功,并且事务执行完毕提交,M3操作出现问题需要回滚,通知TC让所有微服务本地事务都回滚。此时M2需要将更改过的数据复原。
三者的关系
TC:单独部署,Seata服务器
TM和RM与应用强绑定,共用一个虚拟机并一起部署,在TM和RM与TC之间使用RPC完成数据的交换
两阶段提交协议**(2PC)**
有协调者,有参与者。
第一个阶段,准备阶段(投票阶段)
假设有一个协调者,参与者a和参与者b,
各参与者都成功的情况:
a和b完成任务后发送信息给协调者告知成功,或者有一个失败。
第二阶段:提交阶段
如果都成功,就提交commit,如果有失败的就要求所有参与者都回滚。
2PC特点是原理简单,实现简单
缺点:
同步阻塞问题:
所有参与节点都是事务阻塞型的。在一个分布事务执行期间,所有RM预执行完成自己的分支事务之后就会处于阻塞状态,等待协调者再次发送提交或者回滚指令。协调者要等待所有的RM全部处理的结果 ,才能再次发送指令,当RM占有临界资源时,其他资源管理器如果要访问同一临界资源, 会处于阻塞状态。协调者要等待所有的参与者全部处理完才能再发送指令。此时其他的参与者要访问数据就会产生堵塞。
单点故障问题:
(中心化问题),一旦事务协调者发生故障,整个系统都牌停滞状态,尤其是在提交阶段,事务协调者发生故障,RM就会由于需要等待协调者给的消息,而一直锁定事务资源,导致整个系统被阻塞。
数据不一致问题:
在提交阶段,当TC向RM发送指令后,由于网络等异常,或者在发送提交请求的过程中,有RM发生了故障,此时,有收指令的RM会执行提交指令,没有收到指令的,不能执行事务提交,导致整 个分布式系统数据不一致。就是相当于有的参与者收到指令而有的没有收到。
过于保守问题:
任何一个参与者在准备阶段,出现问题,都将引发分布式事务全局性失败。
Seata****全局事务基础框架
在Seata定义的全局事务基础框架中,不同角色的功能是不一样的,在全局事务基本框架中,流程图:

(1)TM向TC发起请求,包括开启、提交和回滚全局事务。
(2)TM把代表全局事务的XID绑定到分支事务。
(3)RM向TC发起请求,并注册分支事务,把分支事务关联到XID所代表的全局事务。
(4)RM把分支事务的执行结果上报给TC。
(5)TC发送分支事务提交或分支事务回滚的指令给RM。
在Seata中,全局事务的处理过程分为如下两个阶段:
(1)执行阶段:Seata会执行与分支事务相关的逻辑,并保证执行结果 是可以回滚和持久化的。
(2)完成阶段:应用会根据全局事务中所有的分支事务在执行阶段的结果 ,形成全局事务的决议,并通过TM向TC发起提交或者回滚全局事务的请求,之后TC向RM发起请求,提交或者回滚分支事务不同的事务模式会使用不同的方式去完成全局事务的两个阶段,形成了Seata的四种事务模式:AT模式、TCC模式、Saga模式、XA模式。
AT模式(默认):
Auto Transaction,是一种无侵入的分布式解决方案。AT模式也是Seata主推的分布式事务解决方案。最早来源于阿里里中间件团队发布的TXC服务,"上云"后改名为GTS。AT模式屏蔽了底层JDBC数据层的细节,让应用能够无感知的使用分布式事务,自动代理应用数据源,并进行事务相关的操作。
在AT模式下,用户只需要关注自己的业务SQL,用户的业务SQL作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。默认,简单,需要增加undo_log表,生成反向SQL,性能高。
回滚后,原来没数据的,现在还是没数据,AT模式的自动回滚就是基undo_log表。
前提:
基于支持本地 ACID 事务的关系型数据库。
Java 应用,通过 JDBC 访问数据库。
整体机制
两阶段提交协议的演变:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿:

(1)第一阶段:应用只需要关注自己的业务SQL代码,Seata将应用的业务SQL代码的执行作为第一阶段。Seata框架会自动代理应用的数据源,并生成事务第二阶段的提交和回滚事务,记录在UNDOLOG日志表中。
(2)第二阶段:如果TC事务协调器通知分支事务处理成功,则Seata会提交分支事务,如果TC协调器通知分支事务失败,则Seata会回滚分支事务。
工作机制
以一个示例来说明整个 AT 分支的工作过程。
业务表: productField
Type
Key
id
bigint(20)
PRI
name
varchar(100)
since
varchar(100)
id
name
since
1
TXC
2014
id
name
since
1
GTS
2014
AT 分支事务的业务逻辑:
一阶段
过程:
- 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。
- 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
得到前镜像: - 执行业务 SQL:更新这条记录的 name 为 'GTS'。
- 查询后镜像:根据前镜像的结果,通过 主键定位数据。
得到后镜像: - 插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到
UNDO_LOG 表中。
{
"branchId": 641789253,
"undoItems": { "afterImage": { "rows": \[{ "fields": \[{ "name": "id", "type": 4, "value": 1 }, { "name": "name", "type": 12, "value": "GTS" }, { "name": "since", "type": 12, "value": "2014" }
}],
"tableName": "product"
},
"beforeImage": {
"rows": { "fields": \[{ "name": "id", "type": 4, "value": 1 }, { "name": "name", "type": 12, "value": "TXC" }, { "name": "since", "type": 12, "value": "2014" }
}],
"tableName": "product"
},
"sqlType": "UPDATE"
}],
"xid": "xid:xxx"
} - 提交前,向 TC 注册分支:申请 product 表中,主键值等于 1 的记录的 全局锁。
- 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
- 将本地事务提交的结果上报给 TC。
二阶段**-**回滚 - 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
- 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
- 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务
之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。 - 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
5.提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。删除相应地UNDO_LOG记录。
二阶段**-**提交 - 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
- 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
TCC模式:
Try、Confirm、Cancel模式(最终一致性)
2019年3月,Seata开源了TCC模式,一个分布式的全局事务,整体是 两阶段提交的模型,全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交的模型要求,即需要每个分支事务都具备自己的:
一阶段 prepare 行为
二阶段 commit 或 rollback 行为

(1) 在TCC模式中,用try()、confirm()、cancel()三个方法来实现事务的两阶段提交。
只会走第1,3补或者第2,3步。三个阶段代码都要自已实现,Seata只负责调度,对业务代码侵入性较强,必要时可能还要修改数据库。
|-----------------------------------------------------------------------|
| try():在应用中检测和预留系统资源 ,完成业务的准备工作 |
| contrim():在应用中提交执行的业务操作,完成业务的提交处理。TCC模式要求下,如果try执行成功, 则confirm一定要执行成功 |
| cancel():在应用中释放预留资源 、完成事务回滚 |
(2)RM负责管理第一阶段的try语句,以及第二阶段的confirm和cancel语句。
(3)TM开启事务,在RM调用TM之后,TM执行一阶段的try方法并注册分支事务,并分析和上传分支事务的状态。
(4)如果调用链路已经完成,则TM向TC发起第二阶段的分支事务决议的请求:如果所有分支事务的决议都是通过的,则TC驱动RM去执行第二阶段的事务confirm或者cancel语句。
(5)应用需要在代码中使用try、confirm和cancal方法实现分布式事务。
根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction Mode和 TCC**(Branch) Transaction Mode。**
AT 模式基于 支持本地ACID事务的 关系型数据库:
一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
二阶段 commit 行为:马上成功结束,自动异步批量清理回滚日志。
二阶段 rollback 行为:通过回滚日志,自动生成补偿操作,完成数据回滚。
相应的,TCC 模式,不依赖于底层数据资源的事务支持:
一阶段 prepare 行为:调用 自定义的 prepare 逻辑。
二阶段 commit 行为:调用 自定义的 commit 逻辑。
二阶段 rollback 行为:调用 自定义的 rollback 逻辑。
所谓 TCC 模式,是指支持把 自定义的分支事务纳入到全局事务的管理中。
SAGA****模式
理论基础:Hector & Kenneth 发表论⽂ Sagas (1987)。
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

适用于业务流程长且需要保证事务最终一致性的业务系统 。
如果(别人写的服务)事务参与者可能是其他公司的服务或者是遗留系统的服务,无法进行改造和提供TCC要求的接口,可以使用Saga模式。
Saga的核心就是补偿:第一阶段就是服务的正常顺序调用(数据库事务正常提交),如果都执行成功,则第二阶段不需要操作。如果在第一阶段中有执行失败出现,则在第二阶段会依次调用其补偿服务,保证整个业务的一致性。
Seata的Saga正向服务和补偿服务都是由业务开发者 实现,所以也是业务侵入式的。基于状态机来实现的,需要一个JSON文件,可异步执行。

(1)在开启状态机后,会注册分支事务;在关闭状态机后,会分析并上传分支事务的状态。
(2)在TC通知RM去提交分支事务时,会转发状态机,在TC通知RM回滚分支事务时,会重新加 载状态机。
(3)在去行状态机之后,将产生的数据存储在数据库中。
(4)在Saga模式中,主要用状态机来编排分布式事务中应用执行的顺序,以及事务执行的阶段。
(5)分布式事务通常是由事件驱动的,各个参与者之间是异步执行的。
优势:
|------------------------------------|
| 在第一阶段提交本地数据库事务时,采用的是无锁设计,所以应用具备高性能 |
| 参与者可以采用事务驱动来异步执行,所以应用具备高吞吐量 |
| 补偿服务就是正向服务的反向,所以应用非常容易实现,具备易用性 |
XA****模式:
强一致性
在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。
XA协议(Unix Transaction)是一种分布式事务解决方案,基于XA协议,XA协议是由Tuxedo首先提出的,交给X/Open组织 ,作为资源管理器与事务管理器的接口标准,基于数据库XA协议来实现2PC又称为XA方案,适用于强一致性的场景,比如金融,银行等。需要数据库本身支持XA协议,可以跨数据库。
XA规范描述了全局的事务管理器与局部的资源管理器之间的接口。目的是允许多个资源 (数据库、应用服务器、消息队列等)在同一事务中访问,可以使事务的ACID属性跨越应用程序保持有效。
前提:
支持XA 事务的数据库。
Java 应用,通过 JDBC 访问数据库。

(1)第一阶段,利用RM代理应用的数据源,创建数据库的代理连接。通过开启XA模式事务拦截应用的sql语句,执行XA模式预处理。为了防止应用宕机,造成数据丢失,第一阶段的XA模式操作会被Seata持久化(之后任何意外都不会造成无法回滚的情况 )。
(2)第二阶段,TC通知RM执行提交或者回滚XA模式的分支事务。
Seata已经支持了AT、TCC、Saga三种模式,为什么还要在Seata中支持XA模式?
前面三种都属于补偿型,补偿型事务处理机制构建在事务资源之上(要么是在中间件层面,要么在应用层面),事务资源本身对分布式事务是无感知的,没有办法做到真正的全局一致性。
假设100个商品,用户买了50个,商品数据库有100个,要减去50个,用户数据表当前用户余额为30,一个商品一块,发现余额不足就要回滚用户服务的事务,而商品服务。
XA优点:
业务无侵入
数据库支持广泛
多语言支持容易
对于传统基于XA应用的迁移
应用Seata:

类比一个项目,例如火车购票系统中的订单处理,事务开启有两种,一种是Spring框架自带的
@Transactional和Seata的@GlobalTransactional全局事务注解
|----------------------|------------|----------------------|
| 注解名 | 来源 | 作用 |
| @GlobalTransactional | Spring框架自带 | 只能管理单库,开销较小,只回滚当前数据库 |
| @Transactional | Seata事务开启 | 可以管理所有参加服务的数据库并回滚 |
接着引入依赖(SpringBoot Alibaba 2025.0.0版本):
html
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
**Seata Server(TC)**的下载及配置:
解压后,找到seata->conf目录,打开application.yml配置文件,早期版本中,有file.conf、registry.conf文件,现在合并成application.yml文件。
用户应用程序,需要和服务协调者进行通信,使用nacos注册中心、配置中心,可以更好地实现负载均衡,并且在配置中心中,可以放置TC相关的配置。
参考application.example.yml文件,修改application.yml文件
修改配置中心nacos部分
java
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP
username: nacos
password: nacos
context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
data-id: seataServer.properties
注册中心部分:
java
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace:
cluster: default
username: nacos
password: nacos
context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
修改存储部分:
java
store:
# support: file 、 db 、 redis
mode: db
session:
mode: db
lock:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
// 3306后面的数据库名字可以自已配置,在数据库里同名
user: root
password: 密码
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
创建数据库seata,需要生成四个表
java
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
在seata->script->server->db下找到mysql.sql文件导入,生成以上四个表,运行sql脚本,自动生成四个表。
java
#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
#store.publicKey=
#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
#store.file.dir=file_store/data
#store.file.maxBranchSessionSize=16384
#store.file.maxGlobalSessionSize=512
#store.file.fileWriteBufferCacheSize=16384
#store.file.flushDiskMode=async
#store.file.sessionReloadReadSize=100
java
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=数据库密码
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
//不需要redis,注释掉
#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
#store.redis.mode=single
#store.redis.single.host=127.0.0.1
#store.redis.single.port=6379
#store.redis.sentinel.masterName=
#store.redis.sentinel.sentinelHosts=
#store.redis.sentinel.sentinelPassword=
#store.redis.maxConn=10
#store.redis.minConn=1
#store.redis.maxTotal=100
#store.redis.database=0
#store.redis.password=
#store.redis.queryLimit=100
配置完成后,在seata->bin下,找到seata-server.bat
进入cmd,执行seata-server.bat指令, 参数 -p 端口号 -h 主机 -m 元数据库类型 -s 存储模式