# 07.微服务-Seata
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。它的核心设计思想是对业务无侵入。
# 1.分布式事务理论基础
# 1.基本介绍
解决分布式事务,也有相应的规范和协议。分布式事务相关的协议有2PC、3PC。
由于三阶段提交协议3PC非常难实现,目前市面主流的分布式事务解决方案都是2PC协议。这就是文章开始提及的常见分布式事务解决方案里面,那些列举的都有一个共同点"两阶段"的内在原因。
有些文章分析2PC时,几乎都会用TCC两阶段的例子,第一阶段try,第二阶段完成confirm或cancel。其实2PC并不是专为实现TCC设计的,2PC具有普适性——协议一样的存在,目前绝大多数分布式解决方案都是以两阶段提交协议2PC为基础的。TCC(Try-Confirm-Cancel)实际上是服务化的两阶段提交协议。
# 2.核心组件
- TC (Transaction Coordinator) - 事务协调者:
- 它是“大管家”,维护全局和分支事务的状态,驱动全局提交或回滚。
- 独立部署的 Seata Server。
- TM (Transaction Manager) - 事务管理器:
- 它是“发起人”,定义全局事务的范围:开始全局事务、提交或回滚全局事务。
- 通常是我们业务代码中的
@GlobalTransactional注解所在的方法。
- RM (Resource Manager) - 资源管理器:
- 它是“执行者”,管理分支事务处理的资源,向 TC 注册分支事务,汇报分支事务状态,并驱动分支事务的提交或回滚。
- 通常是我们连接数据库的微服务。
# 3. 2PC协议流程
2PC 把分布式事务的提交拆成两个阶段:
- 第一阶段(准备/投票):协调者问所有参与者“你能不能提交?”;参与者执行并锁住资源,回复 Yes/No。
- 第二阶段(提交/回滚):协调者根据投票做决策——全 Yes 则全局 Commit,有 No 或超时则全局 Rollback;参与者执行对应动作并回复确认。

# 4. 2PC的问题
同步阻塞,参与者必须等待其他参与者都响应完之后才能进行操作,倘若参与者与协调者之间网络异常导致参与者一直收不到协调者信息,那么会导致参与者一直阻塞下去。
单点,在 2PC 中,一切请求都来自协调者,所以协调者的地位是至关重要的,如果协调者宕机,那么就会使参与者一直阻塞并一直占用事务资源。
数据不一致,Commit 事务过程中 Commit 请求/Rollback 请求可能因为协调者宕机或协调者与参与者网络问题丢失,那么就导致了部分参与者没有收到Commit/Rollback 请 求,而其他参与者则正常收到执行了 Commit/Rollback 操作
环境可靠性依赖,协调者Prepare请求发出后,等待响应,然而如果有参与者宕机或与协调者之间的网络中断,都会导致协调者无法收到所有参与者的响应,那么在2PC中,协调者会等待一定时间,然后超时后,会触发事务中断,在这个过程中,调者和所有其他参与者都是出于阻塞的。这种机制对网络问题常见的现实环境来说太苛刻了
# 2.Seata的模式
# 1.AT模式
原理:基于本地数据库的 ACID 特性 + 全局锁 + Undo Log(回滚日志)。
两阶段提交:
- 一阶段:业务 SQL 执行时,Seata 会拦截 SQL,解析数据,生成“前镜像”和“后镜像”,存入
undo_log表。然后提交本地事务(注意:这里直接提交了,不阻塞),并向 TC 注册分支。 - 二阶段:
- 如果全局回滚:TC 通知 RM。RM 读取
undo_log,利用“前镜像”生成反向 SQL 并执行,恢复数据,然后删除日志。 - 如果全局提交:TC 通知 RM。RM 异步删除
undo_log即可。
- 如果全局回滚:TC 通知 RM。RM 读取
- 一阶段:业务 SQL 执行时,Seata 会拦截 SQL,解析数据,生成“前镜像”和“后镜像”,存入
优点:无代码侵入,性能较好。
缺点:依赖本地数据库事务(支持 MySQL/Oracle/PostgreSQL 等)。
# 2. TCC模式
TCC (Try-Confirm-Cancel) 是一种应用层的柔性事务。
- 原理:将一个业务逻辑拆分为三个接口:
- Try:资源预留(例如:冻结余额,而非直接扣减)。
- Confirm:确认执行(真正扣减余额,使用 Try 预留的资源)。
- Cancel:取消执行(释放预留的资源)。
- 优点:性能强,不依赖底层数据库事务,适合跨数据库、跨服务的非关系型数据操作。
- 缺点:代码侵入性极强(要写三个方法),需要处理空回滚、悬挂等异常情况,开发成本高。
示例:

# 3.Seata实战
下载seata https://seata.io/zh-cn/blog/download.html
配置seata 中
config.txt,主要是数据库和存储类型
#Tansaction 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=
#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.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=1234
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 创建数据库seata,执行安装包中的sql语句和单独的
undolog日志表
CREATE TABLE `undo_log` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(0) NOT NULL,
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(0) NOT NULL,
`log_created` datetime(0) NOT NULL,
`log_modified` datetime(0) NOT NULL,
`ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
2
3
4
5
6
7
8
9
10
11
12
13
- 将config.txt信息注册到nacos中
sh nacos-config.sh -h nacos服务地址 -p 8848 -g SEATA_GROUP -t 刚刚注册时候的命名空间id -u nacos -w nacos
案例
sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t cc7b3c50-d6e9-4b86-934a-1cd7e5a6a0e2 -u nacos -w nacos
2
3
4
- 修改seata启动的配置文件application.yml并启动服务
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${log.home:${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: 47.94.9.59:8848
namespace: 9cf42bec-4711-46aa-b174-f76dd62496a1
group: SEATA_GROUP
context-path:
##1.The following configuration is for the open source version of Nacos
username: nacos
password: 2eb4e10f
##2.The following configuration is for the MSE Nacos on aliyun
#access-key:
#secret-key:
##3.The following configuration is used to deploy on Aliyun ECS or ACK without authentication
#ram-role-name:
data-id: seataServer.properties
consul:
server-addr: 127.0.0.1:8500
acl-token:
key: seata.properties
apollo:
appId: seata-server
apollo-meta: http://192.168.1.204:8801
apollo-config-service: http://192.168.1.204:8080
namespace: application
apollo-access-key-secret:
cluster: seata
zk:
server-addr: 127.0.0.1:2181
session-timeout: 6000
connect-timeout: 2000
username:
password:
node-path: /seata/seata.properties
etcd3:
server-addr: http://localhost:2379
key: seata.properties
registry:
# support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa
type: nacos
preferred-networks: 30.240.*
nacos:
application: seata-server
server-addr: 47.94.9.59:8848
group: SEATA_GROUP
namespace: 9cf42bec-4711-46aa-b174-f76dd62496a1
cluster: default
context-path:
##1.The following configuration is for the open source version of Nacos
username: nacos
password: 2eb4e10f
##2.The following configuration is for the MSE Nacos on aliyun
#access-key:
#secret-key:
##3.The following configuration is used to deploy on Aliyun ECS or ACK without authentication
#ram-role-name:
eureka:
service-url: http://localhost:8761/eureka
application: default
weight: 1
redis:
server-addr: localhost:6379
db: 0
password:
cluster: default
timeout: 0
zk:
cluster: default
server-addr: 127.0.0.1:2181
session-timeout: 6000
connect-timeout: 2000
username:
password:
consul:
cluster: default
server-addr: 127.0.0.1:8500
acl-token:
etcd3:
cluster: default
server-addr: http://localhost:2379
sofa:
server-addr: 127.0.0.1:9603
application: default
region: DEFAULT_ZONE
datacenter: DefaultDataCenter
cluster: default
group: SEATA_GROUP
address-wait-time: 3000
server:
service-port: 8091 #If not configured, the default is '${server.port} + 1000'
max-commit-retry-timeout: -1
max-rollback-retry-timeout: -1
rollback-retry-timeout-unlock-enable: false
enable-check-auth: true
enable-parallel-request-handle: true
enable-parallel-handle-branch: false
retry-dead-threshold: 130000
xaer-nota-retry-timeout: 60000
enableParallelRequestHandle: true
applicationDataLimitCheck: true
applicationDataLimit: 64000
recovery:
committing-retry-period: 1000
async-committing-retry-period: 1000
rollbacking-retry-period: 1000
timeout-retry-period: 1000
undo:
log-save-days: 7
log-delete-period: 86400000
session:
branch-async-queue-size: 5000 #branch async remove queue size
enable-branch-async-remove: false #enable to asynchronous remove branchSession
store:
# support: file 、 db 、 redis 、 raft
mode: db
session:
mode: db
lock:
mode: db
file:
dir: sessionStore
max-branch-session-size: 16384
max-global-session-size: 512
file-write-buffer-cache-size: 16384
session-reload-read-size: 100
flush-disk-mode: async
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
user: root
password: 1234
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
redis:
mode: single
# support: lua 、 pipeline
type: lua
database: 0
min-conn: 10
max-conn: 100
password:
max-total: 100
query-limit: 1000
single:
host: 127.0.0.1
port: 6379
sentinel:
master-name:
sentinel-hosts:
sentinel-password:
metrics:
enabled: false
registry-type: compact
exporter-list: prometheus
exporter-prometheus-port: 9898
transport:
rpc-tc-request-timeout: 15000
enable-tc-server-batch-send-response: false
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
boss-thread-size: 1
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/version.json,/health,/error
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
seata中的application.yml和config.txt区别
| 文件 | 主要用途 | 读取方 | 形式 |
|---|---|---|---|
| application.yml | Seata Server(TC)自身的启动配置 | Server 端(Spring Boot 加载) | YAML |
| config.txt | 通用配置清单,用于导入到配置中心 | 配置中心导入脚本使用 | 纯文本(key=value) |
如果需要使用数据库的话,需要自己导入jar包到
jdbc目录中nacos中配置事务分组和集群

为什么配?
客户端不会直接写死“连 127.0.0.1:8091”,而是写“我用的事务分组是 XXX”,然后通过配置中心查“这个 vgroup 对应哪个 cluster”,再通过注册中心找到这个 cluster 下的 TC 实例。
- 写服务写配置写逻辑(关键代码如下)
依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos 服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>
</dependencies>
</project>
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
配置
server:
port: 8083
spring:
application:
name: order-service
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
username: root
password: 1234
cloud:
nacos:
server-addr: 47.94.9.59:8848
discovery:
username: nacos
password: 2eb4e10f
seata:
enabled: true
#服务id,不重复即可
application-id: production-seata-service
#group名,需要跟Nacos添加的配置保持一致
tx-service-group: service-order-group
service:
# seata.service.vgroup-mapping.XxX xxX需要跟上面保持一致
disable-global-transaction: false
vgroup-mapping:
service-order-group: default
# seata.registry的信息,跟application.ymL的registry信息保持一致
registry:
type: nacos
nacos:
application: seata-server
server-addr: 47.94.9.59:8848
group: SEATA_GROUP
namespace: 9cf42bec-4711-46aa-b174-f76dd62496a1
username: nacos
password: 2eb4e10f
cluster: default
# seata.config的信息,跟application.ymL的config信息保持一致
config:
type: nacos
nacos:
server-addr: 47.94.9.59:8848
group: SEATA_GROUP
namespace: 9cf42bec-4711-46aa-b174-f76dd62496a1
username: nacos
password: 2eb4e10f
client:
rm:
report-success-enable: true
report-retry-count: 5
# MyBatis-Plus配置
mybatis-plus:
mapper-locations: classpath:mapper/\*\*/\*.xml
type-aliases-package: com.xy.order.domain.po
configuration:
map-underscore-to-camel-case: true
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
逻辑
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private ProductionService productionService;
@Autowired
private OrderService orderService;
@GetMapping("/buy")
@GlobalTransactional(name = "order", rollbackFor = Exception.class)
public String addOrder() {
System.out.println("事务id是" + RootContext.getXID());
Order order = new Order();
order.setUserId("1");
order.setProductId("1");
order.setProductName("1");
order.setProductPrice(new BigDecimal(1));
order.setCreateTime(null);
orderService.save(order);
String deduct = productionService.deduct(1);
return "添加订单,"+deduct;
}
}
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
29
30
31
32
33