# 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 即可。
  • 优点:无代码侵入,性能较好。

  • 缺点:依赖本地数据库事务(支持 MySQL/Oracle/PostgreSQL 等)。

# 2. TCC模式

TCC (Try-Confirm-Cancel) 是一种应用层的柔性事务。

  • 原理:将一个业务逻辑拆分为三个接口:
    • Try:资源预留(例如:冻结余额,而非直接扣减)。
    • Confirm:确认执行(真正扣减余额,使用 Try 预留的资源)。
    • Cancel:取消执行(释放预留的资源)。
  • 优点:性能强,不依赖底层数据库事务,适合跨数据库、跨服务的非关系型数据操作。
  • 缺点:代码侵入性极强(要写三个方法),需要处理空回滚、悬挂等异常情况,开发成本高。

示例:

# 3.Seata实战

  1. 下载seata https://seata.io/zh-cn/blog/download.html

  2. 配置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


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
  1. 创建数据库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;
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 将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
1
2
3
4
  1. 修改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
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
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)
  1. 如果需要使用数据库的话,需要自己导入jar包到jdbc目录中

  2. nacos中配置事务分组和集群

为什么配?

客户端不会直接写死“连 127.0.0.1:8091”,而是写“我用的事务分组是 XXX”,然后通过配置中心查“这个 vgroup 对应哪个 cluster”,再通过注册中心找到这个 cluster 下的 TC 实例。

  1. 写服务写配置写逻辑(关键代码如下)

依赖

   <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>
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
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
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
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;

    }
}

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
29
30
31
32
33

最近更新: 9/19/2026, 1:27:08 PM
编程NOTE   |