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 的整体工作流程如下:
- TM 向 TC 申请开启一个全局事务,全局事务创建成功后,TC 会针对这个全局事务生成一个全局唯一的 XID;
- XID 通过服务的调用链传递到其他服务;
- RM 向 TC 注册一个分支事务,并将其纳入 XID 对应全局事务的管辖;
- TM 根据 TC 收集的各个分支事务的执行结果,向 TC 发起全局事务提交或回滚决议;
- TC 调度 XID 下管辖的所有分支事务完成提交或回滚操作。
四、Seata AT 模式
Seata 提供了 AT、TCC、SAGA 和 XA 四种事务模式,可以快速有效地对分布式事务进行控制。在这四种事务模式中,使用广泛和边界的是 AT 模式。与其他事务模式相比,AT 模式可以满足大多数的业务场景,基本可以做到无业务入侵,开发人员能够有更多的精力关注于业务逻辑开发。
4.1.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;
4.2.AT 模式的工作机制
Seata 的 AT 模式工作时大致可以分为以两个阶段,下面我们就结合一个实例来对 AT 模式的工作机制进行介绍。假设某数据库中存在一张名为 user的表,表结构如下:
| 列名 | 类型 | 主键 |
|---|---|---|
| id | bigint(20) | √ |
| name | varchar(255) | |
| address | varchar(255) |
在某次分支事务中,我们需要在 user 表中执行以下操作:
update user set address= '上海市黄浦区125号' where name = '李响';
4.2.1.一阶段
Seata AT 模式下一阶段的工作流程如下图:

Seata AT 模式一阶段工作流程如下。
1. 获取 SQL 的基本信息:Seata 拦截并解析业务 SQL,得到 SQL 的操作类型(UPDATE)、表名(webset)、判断条件(where name = 'C语言中文网')等相关信息。
2. 查询前镜像:根据得到的业务 SQL 信息,生成“前镜像查询语句”。
select id,name,address from user where name='李响';
执行“前镜像查询语句”,得到即将执行操作的数据,并将其保存为“前镜像数据(beforeImage)”。
| id | name | url |
|---|---|---|
| 1 | 李响 | 北京市丰台区1528号 |
3. 执行业务 SQL(update userset address = '上海市黄浦区125号' where name = '李响';),将这条记录的 address修改为 上海市黄浦区125号。
4. 查询后镜像:根据“前镜像数据”的主键(id : 1),生成“后镜像查询语句”。
select id,name,address from user where id= 1;
执行“后镜像查询语句”,得到执行业务操作后的数据,并将其保存为“后镜像数据(afterImage)”。
| id | name | url |
|---|---|---|
| 1 | 李响 | 上海市黄浦区125号 |
5. 插入回滚日志:将前后镜像数据和业务 SQL 的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中,示例回滚日志如下。
{ "@class": "io.seata.rm.datasource.undo.BranchUndoLog", "xid": "172.27.54.2:8091:5962967415319516024", "branchId": 5962967415319516028, "sqlUndoLogs": [ "java.util.ArrayList", [ { "@class": "io.seata.rm.datasource.undo.SQLUndoLog", "sqlType": "UPDATE", "tableName": "user", "beforeImage": { "@class": "io.seata.rm.datasource.sql.struct.TableRecords", "tableName": "user", "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": "北京市丰台区1528号" } ] ] } ] ] }, "afterImage": { "@class": "io.seata.rm.datasource.sql.struct.TableRecords", "tableName": "user", "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": "上海市黄浦区125号" } ] ] } ] ] } } ] ] }
6. 注册分支事务,生成行锁:在这次业务操作的本地事务提交前,RM 会向 TC 注册分支事务,并针对主键 id 为 1 的记录生成行锁。
注意:以上所有操作均在同一个数据库事务内完成,可以保证一阶段的操作的原子性。
7. 本地事务提交:将业务数据的更新和前面生成的 UNDO_LOG 一并提交。
8. 上报执行结果:将本地事务提交的结果上报给 TC。
4.2.2.二阶段
提交:
- 当所有的 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 address= '北京市丰台区1528号' where id = 1;
4. 还原数据:执行回滚语句,并将前镜像数据、后镜像数据以及行锁删除。
5. 提交事务:提交本地事务,并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
五、Seata 的下载和安装
5.1.下载seata
使用浏览器访问 https://github.com/seata/seata/releases,在 Seata Server 下载页面分别下载“seata-server-1.6.1.zip”,如下图。

5.2.解压
解压 seata-server-1.6.1.zip,其目录结构如下图。

5.3.Seata 配置中心
配置中心就像是一个 大柜子一样,内部存放着各种各样的配置文件,我们可以根据自己的需要从其中获取指定的配置文件,加载到对应的客户端中。Seata 支持多种配置中心:
- nacos(推荐)
- consul
- zookeeper
- file (读本地文件,包含 conf、properties、yml 等配置文件)
5.4.Seata 整合 Nacos 配置中心
Seata和Nacos同属于阿里巴巴,作为配置中心更加的合适。Seata 整合 Nacos 配置中心的操作步骤如下。seata安装版本是1.6.1,版本不同,安装流程也可能不同,这里的版本需要保持一致
- 在代码中添加依赖
- 配置Seata事务日志存储模式,修改application.yml文件,分别修改store、config、registry相关配置。
- 在数据库中创建seata库,执行初始化SQL创建表
- seteaserver配置信息添加到nacos
- 启动服务,成功登陆seata控制台。
- 查看nacos控制台,服务列表新增seata服务。
5.4.1.配置事务日志存储模式
seata的配置在conf文件下,打开后及能找到其yml文件配置,在原配置文件中只有一些seata的基础配置,并未对其有更详细的配置,大多需要根据个人使用情况参照模板配置去配置一些自定义的配置文件。

默认情况下seata的存储模式是file,但是我们希望能通过本地数据库来进行存储以方便我们进行查看,因此在需要在原配置文件application.yml中添加模板配置文件上的如下配置,这里以MySQL数据库为例演示,修改后如下:
server: port: 7091 spring: application: name: seata-server logging: config: classpath:logback-spring.xml file: path: ${user.home}/logs/seata extend: logstash-appender: destination: 127.0.0.1:4560 kafka-appender: bootstrap-servers: 127.0.0.1:9092 topic: logback_to_logstash console: user: username: seata password: seata seata: config: # support: nacos 、 consul 、 apollo 、 zk 、 etcd3 type: nacos nacos: server-addr: http://127.0.0.1:8848 # namespace: 7392baed-d98b-48a4-8676-34e1b38eade6 namespace: group: SEATA_GROUP username: password: ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: "" #secret-key: "" data-id: seataServer.properties registry: # support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa type: nacos preferred-networks: 30.240.* nacos: application: seata-server server-addr: http://127.0.0.1:8848 group: SEATA_GROUP namespace: # namespace: 7392baed-d98b-48a4-8676-34e1b38eade6 cluster: default username: password: ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: "" #secret-key: "" store: # support: file 、 db 、 redis mode: db # server: # service-port: 8091 #If not configured, the default is '${server.port} + 1000' security: secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017 tokenValidityInMilliseconds: 1800000 ignore: urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
如下图所示:

另外需要注意的一点是:seata-1.6.1默认的mysql配置的数据库驱动是8.0以下的,因此如果我们使用8.0的配置的话,除了更改配置信息还需要检查运行jar包是否存在。在lib文件下的jdbc目录下可以看到已经包含了8.0+的jar包了,不需要我们额外导入,如果其他版本需要但是没有的话还是要自己手动导入jar包的,如下图所示:

5.4.2.创建seata库执行初始化SQL建表(仅db方式)
全局事务会话信息由3块内容构成,全局事务–>分支事务–>全局锁,对应表global_table、branch_table、lock_table,当前版本支持 3大数据库:mysql、postgresql 、oracle。在MySQL 中创建数据库seata,执行的mysql脚本为: /seata/script/server/db/mysql.sql,创建后如下图所示:

5.4.3.在nacos中添加Seata Server 配置信息
Seata从v1.4.2版本开始,支持从nacos中一个Nacos dataId配置项中获取所有配置信息。所以,在nacos配置中心中新建配置,dataId为 seataServer.properties配置项
- 在nacos中 添加dataID:seataServer.properties文件,组位是:SEATA_GROUP,具体内容还是config.txt中(在 Seata Server 安装目录进入目录:/seata/script/config-center/config.txt)的配置,可以自己添加修改内容,如下:
修改内容说明:

事务路由规则配置,只针对客户端(项目代码和这里要保持一致),配置修改内容说明:

修改store mode 为 db`则需要这些配置

整个修改过的配置文件如下:
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html #Transport configuration, for client and server transport.type=TCP transport.server=NIO transport.heartbeat=true transport.enableTmClientBatchSendRequest=false transport.enableRmClientBatchSendRequest=true transport.enableTcServerBatchSendResponse=false transport.rpcRmRequestTimeout=30000 transport.rpcTmRequestTimeout=30000 transport.rpcTcRequestTimeout=30000 transport.threadFactory.bossThreadPrefix=NettyBoss transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler transport.threadFactory.shareBossWorker=false transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector transport.threadFactory.clientSelectorThreadSize=1 transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread transport.threadFactory.bossThreadSize=1 transport.threadFactory.workerThreadSize=default transport.shutdown.wait=3 transport.serialization=seata transport.compressor=none #Transaction routing rules configuration, only for the client 事务路由规则配置,需和代码配置文件application.yml中保持一致 service.vgroupMapping.default_tx_group=default #If you use a registry, you can ignore it 这里的default和上一行的值保持一致 service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disableGlobalTransaction=false #Transaction rule configuration, only for the client client.rm.asyncCommitBufferLimit=10000 client.rm.lock.retryInterval=10 client.rm.lock.retryTimes=30 client.rm.lock.retryPolicyBranchRollbackOnConflict=true client.rm.reportRetryCount=5 client.rm.tableMetaCheckEnable=true client.rm.tableMetaCheckerInterval=60000 client.rm.sqlParserType=druid client.rm.reportSuccessEnable=false client.rm.sagaBranchRegisterEnable=false client.rm.sagaJsonParser=fastjson client.rm.tccActionInterceptorOrder=-2147482648 client.tm.commitRetryCount=5 client.tm.rollbackRetryCount=5 client.tm.defaultGlobalTransactionTimeout=60000 client.tm.degradeCheck=false client.tm.degradeCheckAllowTimes=10 client.tm.degradeCheckPeriod=2000 client.tm.interceptorOrder=-2147482648 client.undo.dataValidation=true client.undo.logSerialization=jackson client.undo.onlyCareUpdateColumns=true server.undo.logSaveDays=7 server.undo.logDeletePeriod=86400000 client.undo.logTable=undo_log client.undo.compress.enable=true client.undo.compress.type=zip client.undo.compress.threshold=64k #For TCC transaction mode tcc.fence.logTableName=tcc_fence_log tcc.fence.cleanPeriod=1h #Log rule configuration, for client and server log.exceptionRate=100 #Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.修改为db模式 store.mode=db store.lock.mode=db store.session.mode=db #Used for password encryption store.publicKey="" #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=123456 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 #Transaction rule configuration, only for the server server.recovery.committingRetryPeriod=1000 server.recovery.asynCommittingRetryPeriod=1000 server.recovery.rollbackingRetryPeriod=1000 server.recovery.timeoutRetryPeriod=1000 server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 server.rollbackRetryTimeoutUnlockEnable=false server.distributedLockExpireTime=10000 server.xaerNotaRetryTimeout=60000 server.session.branchAsyncQueueSize=5000 server.session.enableBranchAsyncRemove=false server.enableParallelRequestHandle=false #Metrics configuration, only for the server metrics.enabled=false metrics.registryType=compact metrics.exporterList=prometheus metrics.exporterPrometheusPort=9898
- 添加配置之后,如下图所示:

5.4.4.启动seata
此首先需要启动Nacos后然后手动启动seate服务,双击seata-server.bat即可启动成功:

查看nacos控台台

访问seata控制台在浏览器输入如下图所示:http://127.0.0.1:7091/ 账户密码均为:seata


浙公网安备 33010602011771号