分布式事务实战(Seata 版):解决分布式系统数据一致性问题(含代码教学)

在分布式系统中,跨服务调用场景(如订单创建→库存扣减→支付扣钱)会导致数据一致性问题 ------ 若某一步操作失败,已执行的操作无法回滚,会出现「订单创建成功但库存未扣减」「库存扣减成功但支付失败」等数据不一致问题。分布式事务的核心是保证跨服务操作的原子性:要么全部成功,要么全部失败。

Seata 是阿里开源的分布式事务框架,支持 AT、TCC、SAGA 等模式,其中 AT 模式(自动补偿)因「无侵入、易用性高」成为企业级首选。本文聚焦 SpringBoot 集成 Seata AT 模式,从环境搭建、依赖配置、代码实现到避坑指南,全程嵌入可复用代码,帮你快速落地分布式事务,解决跨服务数据一致性问题。

一、核心认知:分布式事务与 Seata 核心原理

1. 分布式事务的核心问题

分布式系统中,每个服务拥有独立数据库,无法通过本地事务保证跨服务操作的原子性,主要面临三大挑战:

  • 原子性:跨服务操作要么全部成功,要么全部回滚;
  • 一致性:事务执行后,所有服务数据处于一致状态;
  • 隔离性:并发事务执行时,互不干扰。

2. Seata AT 模式核心原理(自动补偿)

AT 模式基于「两阶段提交」实现,无需手动编写补偿逻辑,对业务代码无侵入,适合大多数业务场景:

  1. 一阶段(执行阶段):
    • 拦截 SQL,解析并记录数据修改前的快照(undo_log);
    • 执行 SQL 并提交本地事务;
    • 向 Seata 服务器注册分支事务。
  2. 二阶段(提交 / 回滚阶段):
    • 提交:Seata 协调器通知所有分支事务提交,删除 undo_log;
    • 回滚:Seata 协调器通知所有分支事务回滚,通过 undo_log 恢复数据至修改前状态。

3. Seata 核心角色

  • Transaction Coordinator(TC):事务协调器(独立服务),负责协调全局事务的提交 / 回滚;
  • Transaction Manager(TM):事务管理器(发起事务的服务),负责开启、提交、回滚全局事务;
  • Resource Manager(RM):资源管理器(参与事务的服务),负责执行本地事务、注册分支事务、接收回滚 / 提交指令。

二、核心实战一:Seata 环境搭建(Docker 快速部署)

1. 部署 Seata TC 服务器(事务协调器)

bash

运行

复制代码
# 1. 拉取 Seata 镜像(最新稳定版)
docker pull seataio/seata-server:latest

# 2. 启动 Seata 容器
docker run -d --name seata-server \
  -p 8091:8091 \
  -e SEATA_IP=127.0.0.1 \ # 本机IP,确保服务能访问
  -e SEATA_PORT=8091 \
  seataio/seata-server:latest
  • 端口 8091:Seata TC 与 TM/RM 通信的端口;
  • 生产环境需配置持久化(MySQL),避免重启后事务数据丢失。

2. 数据库准备(每个参与服务的数据库需创建 undo_log 表)

Seata AT 模式依赖 undo_log 表记录数据快照,用于回滚时恢复数据,所有参与分布式事务的服务数据库均需执行此 SQL:

sql

复制代码
CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL COMMENT '分支事务ID',
  `xid` varchar(100) NOT NULL COMMENT '全局事务ID',
  `context` varchar(128) NOT NULL COMMENT '上下文信息',
  `rollback_info` longblob NOT NULL COMMENT '回滚信息',
  `log_status` int NOT NULL COMMENT '日志状态:0-未提交,1-已提交',
  `log_created` datetime NOT NULL COMMENT '创建时间',
  `log_modified` datetime NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Seata 回滚日志表';

三、核心实战二:SpringBoot 集成 Seata(AT 模式)

本次实战模拟「订单服务(order-service)→ 库存服务(stock-service)」跨服务调用场景,通过 Seata 保证订单创建与库存扣减的原子性。

1. 引入依赖(两个服务均需引入)

xml

复制代码
<!-- Seata 依赖 -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>
<!-- Spring Cloud Alibaba Seata 适配(若使用Spring Cloud) -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2022.0.0.0-RC2</version>
</dependency>
<!-- MyBatis-Plus 依赖(数据访问层,可选) -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

2. 配置文件(application.yml,两个服务均需配置)

yaml

复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: order-service # 库存服务为 stock-service

# Seata 配置
seata:
  enabled: true
  application-id: ${spring.application.name} # 应用ID,与服务名一致
  tx-service-group: my_test_tx_group # 事务组名称(需与TC配置一致)
  registry:
    type: file # 注册中心类型(生产环境用nacos/eureka)
  config:
    type: file # 配置中心类型
  service:
    vgroup-mapping:
      my_test_tx_group: default # 事务组与TC集群映射(默认default)
  client:
    rm:
      report-success-enable: true
      table-meta-check-enable: false # 关闭表元数据检查
    tm:
      commit-retry-count: 3 # 提交重试次数
      rollback-retry-count: 3 # 回滚重试次数

3. 数据源代理配置(关键,Seata 需拦截 SQL 生成快照)

Seata AT 模式需对数据源进行代理,才能拦截 SQL 并记录 undo_log,需配置数据源代理类:

java

运行

复制代码
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;

@Configuration
public class DataSourceProxyConfig {

    // 数据源代理:Seata 对原始数据源进行代理
    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    // 配置 MyBatis SqlSessionFactory,使用 Seata 代理数据源
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
        MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
        factoryBean.setDataSource(dataSourceProxy);
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/**/*.xml"));
        factoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setMapUnderscoreToCamelCase(true);
        factoryBean.setConfiguration(configuration);
        return factoryBean.getObject();
    }
}

四、核心实战三:分布式事务代码实现(跨服务调用)

1. 库存服务(stock-service):提供库存扣减接口

(1)实体类与 Mapper

java

运行

复制代码
// 库存实体类
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;

@Data
@TableName("t_stock")
public class Stock {
    private Long id;
    private Long productId; // 商品ID
    private Integer num; // 库存数量
}

// 库存 Mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.stock.entity.Stock;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface StockMapper extends BaseMapper<Stock> {
}
(2)Service 层:库存扣减业务

java

运行

复制代码
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.stock.entity.Stock;
import com.example.stock.mapper.StockMapper;
import javax.annotation.Resource;

@Service
public class StockService {
    @Resource
    private StockMapper stockMapper;

    // 库存扣减方法(本地事务,Seata 会自动管理分支事务)
    @Transactional
    public void deductStock(Long productId, Integer num) {
        // 1. 查询库存
        Stock stock = stockMapper.selectOne(new UpdateWrapper<Stock>()
                .eq("product_id", productId));
        if (stock == null || stock.getNum() < num) {
            throw new RuntimeException("库存不足");
        }
        // 2. 扣减库存
        stockMapper.update(null, new UpdateWrapper<Stock>()
                .eq("product_id", productId)
                .setSql("num = num - " + num));
    }
}
(3)Controller 层:提供接口供订单服务调用

java

运行

复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.stock.service.StockService;
import javax.annotation.Resource;

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

    // 库存扣减接口
    @PostMapping("/deduct")
    public void deductStock(
            @RequestParam Long productId,
            @RequestParam Integer num
    ) {
        stockService.deductStock(productId, num);
    }
}

2. 订单服务(order-service):发起分布式事务

(1)远程调用库存服务(Feign)

java

运行

复制代码
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

// 远程调用库存服务
@FeignClient(name = "stock-service") // 库存服务名
public interface StockFeignClient {
    @PostMapping("/stock/deduct")
    void deductStock(
            @RequestParam("productId") Long productId,
            @RequestParam("num") Integer num
    );
}
(2)Service 层:发起全局事务

java

运行

复制代码
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.order.entity.Order;
import com.example.order.mapper.OrderMapper;
import com.example.order.feign.StockFeignClient;
import javax.annotation.Resource;

@Service
public class OrderService {
    @Resource
    private OrderMapper orderMapper;
    @Resource
    private StockFeignClient stockFeignClient;

    // @GlobalTransactional:Seata 全局事务注解,标记为事务发起者
    @GlobalTransactional(name = "create-order-transaction", rollbackFor = Exception.class)
    @Transactional
    public void createOrder(Long userId, Long productId, Integer num) {
        try {
            // 1. 远程调用库存服务,扣减库存(分支事务1)
            stockFeignClient.deductStock(productId, num);

            // 2. 创建订单(本地事务,分支事务2)
            Order order = new Order();
            order.setUserId(userId);
            order.setProductId(productId);
            order.setNum(num);
            order.setStatus(1); // 订单状态:1-已创建
            orderMapper.insert(order);

            // 模拟异常:测试事务回滚(生产环境删除)
            // throw new RuntimeException("订单创建失败,测试回滚");
        } catch (Exception e) {
            // 抛出异常,Seata 会触发全局回滚
            throw new RuntimeException("订单创建失败:" + e.getMessage());
        }
    }
}
(3)Controller 层:订单创建接口

java

运行

复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.order.service.OrderService;
import javax.annotation.Resource;

@RestController
@RequestMapping("/order")
public class OrderController {
    @Resource
    private OrderService orderService;

    // 创建订单接口(发起分布式事务)
    @PostMapping("/create")
    public String createOrder(
            @RequestParam Long userId,
            @RequestParam Long productId,
            @RequestParam Integer num
    ) {
        orderService.createOrder(userId, productId, num);
        return "订单创建成功";
    }
}

3. 测试分布式事务

  1. 正常场景:库存充足,订单创建成功,库存扣减成功,事务提交;
  2. 异常场景:在订单创建后抛出异常,Seata 会触发全局回滚,库存恢复至扣减前状态,订单不保存。

五、Seata 进阶配置(生产环境必备)

1. 持久化配置(MySQL)

默认 Seata TC 数据存储在内存,重启后丢失,生产环境需配置 MySQL 持久化:

  1. 在 Seata 服务器中执行初始化 SQL(Seata 官网提供);
  2. 修改 Seata 配置文件 registry.conffile.conf,指定数据库连接信息。

2. 事务超时配置

yaml

复制代码
seata:
  client:
    tm:
      default-global-transaction-timeout: 60000 # 全局事务超时时间(60秒)

3. 重试机制配置

yaml

复制代码
seata:
  client:
    tm:
      commit-retry-count: 3 # 提交重试次数
      rollback-retry-count: 3 # 回滚重试次数
    rm:
      lock:
        retry-interval: 100 # 锁重试间隔(毫秒)
        retry-times: 3 # 锁重试次数

六、避坑指南

坑点 1:数据源未代理,undo_log 表无数据,事务无法回滚

表现:事务异常时,库存扣减后无法回滚,undo_log 表无记录;✅ 解决方案:确保配置了 DataSourceProxy,且 MyBatis 使用代理后的数据源,避免直接使用原始数据源。

坑点 2:事务组名称配置不一致,无法注册分支事务

表现:启动时报错「no available service」,分支事务无法注册到 TC;✅ 解决方案:确保所有服务的 tx-service-group 与 Seata TC 配置的事务组名称一致。

坑点 3:Feign 调用超时,导致事务回滚

表现:跨服务调用耗时过长,触发 Feign 超时,Seata 事务回滚;✅ 解决方案:调整 Feign 超时时间,确保大于 Seata 全局事务超时时间。

坑点 4:本地事务未开启,数据无法提交

表现:业务逻辑执行成功,但数据未入库;✅ 解决方案:参与事务的 Service 方法需添加 @Transactional 注解,开启本地事务。

七、终极总结:分布式事务的核心是「权衡一致性与性能」

Seata AT 模式以「无侵入、自动补偿」的优势,成为分布式事务的主流解决方案,但其本质是通过两阶段提交实现数据一致性,会带来一定性能损耗。企业级实战中,需在「数据一致性」与「系统性能」之间权衡:

  1. 非核心场景:可放弃强一致性,采用最终一致性(如消息队列异步补偿),提升性能;
  2. 核心场景(订单、支付):必须保证强一致性,使用 Seata AT 模式,确保数据安全。

核心原则总结:

  1. 最小化事务范围:分布式事务仅包含必要的跨服务操作,减少锁持有时间,提升性能;
  2. 异常快速失败:业务逻辑中尽早判断异常场景(如库存不足),避免事务执行过半后回滚;
  3. 配置适配生产:开启持久化、合理设置超时与重试次数,避免因配置不当导致事务失败;
  4. 监控运维保障:生产环境需集成 Seata 监控,实时监控事务状态,快速排查异常。

记住:分布式事务不是「银弹」,需结合业务场景选择合适的方案,Seata AT 模式为核心业务提供了简单高效的一致性保障,是后端开发者必备的分布式技能。

相关推荐
2501_942191772 小时前
【深度学习实战】数字仪表字符识别项目详解——基于YOLO11-HAFB-2模型的优化实现
人工智能·深度学习
Bruce-XIAO2 小时前
数据标注方法
人工智能·nlp
Where-2 小时前
深度学习中的过拟合问题及解决方式
人工智能·深度学习
wen__xvn2 小时前
目标检测的局限
人工智能·目标检测·计算机视觉
力学与人工智能2 小时前
博士答辩PPT分享 | 高雷诺数湍流场数据同化与湍流模型机器学习研究
人工智能·机器学习·ppt分享·高雷诺数·流场数据同化·湍流模型
调参札记2 小时前
医学研究中的因果推断:重视态度与实践流程的结构性落差
人工智能
木卫四科技2 小时前
Chonkie 技术深度学习
人工智能·python·rag
努力毕业的小土博^_^2 小时前
【地学应用】溜砂坡scree slope / talus slope的定义、机制、分布、危害、与滑坡区别、研究方向与代表论文
人工智能·深度学习·遥感·地质灾害·地学应用
JeffDingAI2 小时前
【Datawhale学习笔记】基于Gensim的词向量实战
人工智能·笔记·学习