Seata 实战部署 + 核心模式代码示例(AT模式为主)

我会以最常用的AT模式为例,完整讲解Seata的「环境部署」+「代码实战」,包含从环境搭建到微服务跨库事务的完整流程,新手也能直接落地。

一、Seata 环境部署(生产级,基于1.7.0版本)

1. 部署架构说明

本次部署采用「TC集群 + Nacos注册中心 + MySQL存储」(生产环境标准配置),涉及组件:

  • Seata Server(TC):2台(集群),端口8091/8092
  • Nacos:注册中心/配置中心(端口8848)
  • MySQL:TC存储库 + 业务库(微服务A/微服务B)
  • JDK:1.8+

2. 步骤1:环境准备

(1)创建TC存储库

在MySQL中创建Seata TC的数据库(如seata_db),执行官方SQL脚本(表结构用于存储全局事务状态):

-- 从Seata官网获取最新脚本:https://github.com/seata/seata/blob/1.7.0/server/src/main/resources/db/mysql.sql
CREATE DATABASE IF NOT EXISTS seata_db DEFAULT CHARACTER SET utf8mb4;
USE seata_db;

-- 全局事务表
CREATE TABLE IF NOT EXISTS `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint DEFAULT NULL,
  `status` tinyint NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int DEFAULT NULL,
  `begin_time` bigint DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_status_gmt_modified` (`status`,`gmt_modified`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 分支事务表
CREATE TABLE IF NOT EXISTS `branch_table` (
  `branch_id` bigint NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 锁表
CREATE TABLE IF NOT EXISTS `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(128) DEFAULT NULL,
  `transaction_id` bigint DEFAULT NULL,
  `branch_id` bigint DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `status` tinyint DEFAULT NULL COMMENT '0:locked,1:rollbacking',
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`),
  KEY `idx_status` (`status`),
  KEY `idx_branch_id` (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

(2)下载并配置Seata Server

  1. 下载Seata Server:https://github.com/seata/seata/releases/tag/v1.7.0
  2. 解压后修改conf/application.yml(核心配置):
server:
  port: 8091 # 集群第二台改为8092
spring:
  application:
    name: seata-server
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # Nacos地址
        namespace: public # 命名空间
        group: SEATA_GROUP
      config:
        server-addr: 127.0.0.1:8848
        namespace: public
        group: SEATA_GROUP
        file-extension: yml
seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: public
      group: SEATA_GROUP
      data-id: seataServer.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace: public
      group: SEATA_GROUP
  store:
    mode: db # 存储模式:db(生产)/file(测试)
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/seata_db?useUnicode=true&rewriteBatchedStatements=true
      user: root
      password: 123456
      min-conn: 5
      max-conn: 30
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
  server:
    service-port: 8091 # 与server.port一致
  1. 在Nacos中添加配置seataServer.properties(分组SEATA_GROUP):
# 事务分组配置(核心:微服务的tx-service-group要匹配)
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091,127.0.0.1:8092
service.enableDegrade=false
service.disableGlobalTransaction=false

# 客户端配置
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.sqlParserType=druid
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5

(3)启动Seata Server

# 进入bin目录,启动第一台
sh seata-server.sh -p 8091 -h 127.0.0.1 -m db
# 启动第二台(集群)
sh seata-server.sh -p 8092 -h 127.0.0.1 -m db

3. 步骤2:业务库准备(AT模式必备)

在每个微服务的业务库中创建undo_log表(AT模式用于存储镜像数据):

CREATE TABLE IF NOT EXISTS `undo_log` (
  `branch_id` bigint NOT NULL COMMENT '分支事务ID',
  `xid` varchar(128) NOT NULL COMMENT '全局事务ID',
  `context` varchar(128) NOT NULL COMMENT '上下文',
  `rollback_info` longblob NOT NULL COMMENT '回滚信息',
  `log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime NOT NULL COMMENT '创建时间',
  `log_modified` datetime NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`branch_id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AT模式回滚日志表';

二、AT模式代码实战(Spring Cloud微服务)

1. 场景说明

模拟「订单服务(order-service)」调用「库存服务(stock-service)」的分布式事务:

  • 订单服务:创建订单(写order库)
  • 库存服务:扣减库存(写stock库)
  • 目标:要么都成功,要么都回滚(比如库存不足时,订单也回滚)

2. 步骤1:微服务依赖配置

(1)Pom依赖(两个微服务都需引入)

<!-- Spring Cloud Nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2022.0.0.0-RC2</version>
</dependency>
<!-- Seata Starter -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2022.0.0.0-RC2</version>
    <!-- 排除自带的低版本Seata,使用1.7.0 -->
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.7.0</version>
</dependency>
<!-- MyBatis + MySQL -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.0</version>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- OpenFeign(服务调用) -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

(2)application.yml配置(订单服务为例)

spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: 123456

# Seata配置(核心)
seata:
  tx-service-group: my_test_tx_group # 与Nacos中配置的事务分组一致
  service:
    vgroup-mapping:
      my_test_tx_group: default # 与Nacos中service.vgroupMapping一致
    grouplist:
      default: 127.0.0.1:8091,127.0.0.1:8092
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: public
      group: SEATA_GROUP
      application: seata-server

mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

库存服务的配置仅需修改spring.application.name=stock-servicespring.datasource.url=jdbc:mysql://127.0.0.1:3306/stock_db,其余与订单服务一致。

3. 步骤2:业务代码实现

(1)库存服务(stock-service)

① 库存实体与Mapper
// Stock实体
public class Stock {
    private Long id;
    private String skuCode; // 商品编码
    private Integer stock;  // 库存数量
    // getter/setter
}

// StockMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.stock.mapper.StockMapper">
    <update id="deductStock">
        UPDATE stock SET stock = stock - #{num} WHERE sku_code = #{skuCode}
    </update>
</mapper>

// StockMapper接口
@Mapper
public interface StockMapper {
    int deductStock(@Param("skuCode") String skuCode, @Param("num") Integer num);
}
② 库存Service与Controller
// StockService
@Service
public class StockService {
    @Autowired
    private StockMapper stockMapper;

    // 扣减库存(分支事务)
    public void deduct(String skuCode, Integer num) {
        // 模拟库存不足场景:num>10时抛出异常
        if (num > 10) {
            throw new RuntimeException("库存不足,扣减失败");
        }
        stockMapper.deductStock(skuCode, num);
    }
}

// StockController
@RestController
@RequestMapping("/stock")
public class StockController {
    @Autowired
    private StockService stockService;

    @PostMapping("/deduct")
    public String deduct(@RequestParam String skuCode, @RequestParam Integer num) {
        stockService.deduct(skuCode, num);
        return "库存扣减成功";
    }
}

(2)订单服务(order-service)

① 订单实体与Mapper
// Order实体
public class Order {
    private Long id;
    private String orderNo; // 订单编号
    private String skuCode;  // 商品编码
    private Integer num;     // 购买数量
    // getter/setter
}

// OrderMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.order.mapper.OrderMapper">
    <insert id="createOrder" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO order (order_no, sku_code, num) VALUES (#{orderNo}, #{skuCode}, #{num})
    </insert>
</mapper>

// OrderMapper接口
@Mapper
public interface OrderMapper {
    int createOrder(Order order);
}
② Feign客户端(调用库存服务)
@FeignClient(name = "stock-service")
public interface StockFeignClient {
    @PostMapping("/stock/deduct")
    String deduct(@RequestParam("skuCode") String skuCode, @RequestParam("num") Integer num);
}
③ 订单Service(全局事务发起方)
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private StockFeignClient stockFeignClient;

    // 全局事务入口:@GlobalTransactional标记
    @GlobalTransactional(rollbackFor = Exception.class) // AT模式核心注解
    public void createOrder(String skuCode, Integer num) {
        // 1. 创建订单(本地分支事务)
        Order order = new Order();
        order.setOrderNo(UUID.randomUUID().toString());
        order.setSkuCode(skuCode);
        order.setNum(num);
        orderMapper.createOrder(order);

        // 2. 调用库存服务扣减库存(远程分支事务)
        stockFeignClient.deduct(skuCode, num);
    }
}
④ 订单Controller
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @PostMapping("/create")
    public String createOrder(@RequestParam String skuCode, @RequestParam Integer num) {
        try {
            orderService.createOrder(skuCode, num);
            return "订单创建成功";
        } catch (Exception e) {
            return "订单创建失败:" + e.getMessage();
        }
    }
}

4. 测试验证

(1)正常场景(num=5,库存充足)

调用接口:POST http://localhost:8080/order/create?skuCode=iphone15&num=5

  • 结果:订单库新增订单记录,库存库扣减5个库存,事务提交成功。

(2)异常场景(num=20,库存不足)

调用接口:POST http://localhost:8080/order/create?skuCode=iphone15&num=20

  • 库存服务抛出“库存不足”异常;
  • 订单服务的@GlobalTransactional触发全局回滚;
  • 结果:订单库无新增记录,库存库数据无变化,实现分布式事务回滚。

三、TCC模式代码示例(补充)

如果需要TCC模式,核心差异在业务代码层(需手动实现Try/Confirm/Cancel),以下是关键代码:

1. 库存服务的TCC接口

@Service
public class StockTccService {
    // Try阶段:预占库存
    @TccAction(name = "deductStockTcc", commitMethod = "confirm", rollbackMethod = "cancel")
    public boolean prepareDeductStock(String skuCode, Integer num) {
        // 1. 预占库存(冻结库存,如stock_freeze表)
        // 2. 记录TCC事务状态
        return true;
    }

    // Confirm阶段:确认扣减(正式扣减库存)
    public boolean confirm(String skuCode, Integer num) {
        // 执行正式扣减
        return true;
    }

    // Cancel阶段:回滚预占(释放冻结库存)
    public boolean cancel(String skuCode, Integer num) {
        // 释放冻结的库存
        return true;
    }
}

2. 订单服务调用(全局事务仍用@GlobalTransactional)

@GlobalTransactional
public void createOrder(String skuCode, Integer num) {
    // 创建订单
    orderMapper.createOrder(order);
    // 调用TCC的prepare方法
    stockFeignClient.prepareDeductStock(skuCode, num);
}

四、总结

  1. Seata部署核心:TC集群化(Nacos注册)+ MySQL存储(生产必选)+ 业务库undo_log表(AT模式必备)。
  2. AT模式关键:通过@GlobalTransactional标记全局事务入口,业务代码0侵入,底层依赖undo_log的镜像数据自动补偿。
  3. 测试验证:异常场景下,Seata会自动触发全局回滚,保证跨库事务的最终一致性;TCC模式需手动实现Try/Confirm/Cancel接口,适配高并发场景。
posted @ 2026-03-11 22:19  七星6609  阅读(7)  评论(0)    收藏  举报