分布式事务解决方案Seata之AT事务

简介: Seata AT模式是零侵入分布式事务方案,基于改进两阶段提交(2PC),通过自动代理数据源、拦截SQL、记录undo_log实现全局事务一致性,无需修改业务代码,仅需`@GlobalTransactional`注解即可快速接入。

Seata AT事务模式详解

AT(Automatic Transaction)模式是 Seata 提供的零侵入分布式事务解决方案,无需修改业务代码,仅需添加注解即可实现分布式事务。

一、AT 模式核心概念

1.1 全局事务与分支事务

在 Seata 的 AT 模式中,理解分布式事务生命周期需要掌握两个核心概念:

全局事务 (Global Transaction):

  • 是整个业务链路(跨多个微服务)
  • 由 TM(事务管理器)发起和管理
  • 拥有全局唯一的 XID(事务 ID)

分支事务 (Branch Transaction):

  • 是单个微服务内的本地数据库操作
  • 自动注册到全局事务
  • 拥有 Branch ID 标识
全局事务 (XID: xxx)
    ↓
├─ 分支事务 1 (订单服务 → 订单 DB)
├─ 分支事务 2 (库存服务 → 库存 DB)
├─ 分支事务 3 (账户服务 → 账户 DB)
└─ 分支事务 4 (积分服务 → 积分 DB)

1.2 两阶段提交模型

AT 模式采用改进的两阶段提交(2PC)模型:

阶段一(Phase 1):

  1. 拦截业务 SQL,解析语义
  2. 查询数据前镜像(Before Image)
  3. 执行业务 SQL
  4. 查询数据后镜像(After Image)
  5. 保存 undo_log 回滚日志
  6. 提交本地事务(立即释放锁
  7. 向 TC 汇报执行结果

阶段二(Phase 2):

  • 成功提交: 异步批量删除 undo_log(快速返回)
  • 失败回滚: 根据 undo_log 前镜像恢复数据
┌─────────────────────────────────────────────┐
│          Phase 1: 执行并记录                 │
│  查询前镜像 → 执行 SQL → 查询后镜像          │
│  → 保存 undo_log → 提交本地事务              │
└─────────────────────────────────────────────┘
                    ↓
        ┌───────┴───────┐
        ↓               ↓
   成功则提交       失败则回滚
   (删除 undo_log)  (根据 undo_log 恢复)

二、自动注册原理

2.1 数据源代理技术

Seata 通过 DataSourceProxy(数据源代理)和 SQL 拦截技术实现分支事务的自动注册。

核心组件:

  • DataSourceProxy: 代理真实数据源
  • ConnectionProxy: 代理数据库连接
  • StatementProxy: 代理 SQL 执行
  • ExecuteTemplate: SQL 执行模板

2.2 自动注册流程

1. 业务代码执行 SQL
   ↓
   调用 mapper.insert() 或 jdbcTemplate.update()

2. Seata 拦截 SQL 执行
   ↓
   DataSourceProxy 拦截数据库连接
   检查 RootContext 中是否有 XID

3. 自动注册分支事务
   ↓
   有 XID → 向 TC 注册分支事务,获取 Branch ID
   无 XID → 普通本地事务,直接执行

4. 执行 SQL 并记录日志
   ↓
   查询前镜像 → 执行业务 SQL → 查询后镜像
   保存 undo_log → 提交本地事务

5. 汇报执行结果
   ↓
   向 TC 汇报 Phase 1 执行成功

2.3 XID 传递机制

XID 是什么:

  • 全局事务的唯一标识符
  • 格式:{ip}:{port}:{transactionId}
  • 例如:192.168.1.100:8091:1234567890

XID 传递路径:

入口服务 (@GlobalTransactional)
   ↓ RootContext.bind(xid)
Spring Cloud OpenFeign / Dubbo
   ↓ HTTP Header / RPC Context
下游服务 1
   ↓ RootContext.bind(xid)
下游服务 2

注意事项:

  • ✅ Spring Cloud/Dubbo 默认支持 XID 透传
  • ⚠️ 自定义线程需手动传递 XID
  • ⚠️ 消息队列需手动绑定 XID

三、快速开始

3.1 数据库初始化

在各个微服务的数据库中创建 undo_log 表:

-- AT 模式必须创建 undo_log 表
CREATE TABLE IF NOT EXISTS `undo_log` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `branch_id` BIGINT(20) 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(11) NOT NULL COMMENT '日志状态',
  `log_created` DATETIME NOT NULL COMMENT '创建时间',
  `log_modified` DATETIME NOT NULL COMMENT '修改时间',
  `ext` VARCHAR(100) DEFAULT NULL COMMENT '扩展字段',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AT事务回滚日志表';

表结构说明:

字段 类型 说明
branch_id BIGINT 分支事务 ID
xid VARCHAR 全局事务 ID
context VARCHAR 序列化方式等上下文
rollback_info LONGBLOB 前后镜像数据
log_status INT 0-正常,1-防御性回滚
log_created DATETIME 创建时间
log_modified DATETIME 修改时间

3.2 添加依赖

各微服务项目引入 Seata 依赖:

Maven:

<dependencies>
    <!-- Spring Cloud Alibaba Seata -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <version>2021.0.1.0</version>
    </dependency>

    <!-- MyBatis Plus (可选,如果使用的话) -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>
</dependencies>

Gradle:

dependencies {
   
    implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-seata:2021.0.1.0'
}

3.3 配置文件

application.yml:

seata:
  enabled: true
  tx-service-group: default_tx_group  # 事务组名称
  service:
    vgroup-mapping:
      default_tx_group: DEFAULT  # 映射到 Seata 集群
    grouplist:
      DEFAULT: 192.168.1.100:8091  # Seata Server 地址
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: dev-wdg
      group: DEFAULT_GROUP
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: dev-wdg
      group: DEFAULT_GROUP
      data-id: seataService.properties

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    # 关闭 MyBatis 自身的事务管理器,使用 Seata
    default-executor-type: simple

3.4 开启全局事务

在业务入口类添加 @GlobalTransactional 注解:

@Service
public class OrderServiceImpl implements OrderService {
   

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private AccountService accountService;

    /**
     * 创建订单(分布式事务入口)
     */
    @Override
    @GlobalTransactional(timeoutMills = 300000, name = "create-order-tx")
    public void createOrder(OrderDTO orderDTO) {
   
        // 1. 创建订单
        OrderPO order = convertToPO(orderDTO);
        orderMapper.insert(order);

        // 2. 扣减库存(自动注册分支事务)
        inventoryService.decreaseStock(
            orderDTO.getProductId(), 
            orderDTO.getCount()
        );

        // 3. 扣减余额(自动注册分支事务)
        accountService.decreaseBalance(
            orderDTO.getUserId(), 
            orderDTO.getAmount()
        );

        // 如果任何一步抛出异常,所有操作都会回滚
    }

    private OrderPO convertToPO(OrderDTO dto) {
   
        OrderPO po = new OrderPO();
        BeanUtils.copyProperties(dto, po);
        return po;
    }
}

注解参数说明:

参数 类型 默认值 说明
timeoutMills long 60000 超时时间(毫秒)
name String "" 事务名称(便于监控)
rollbackFor Class[] Exception.class 回滚异常类型
noRollbackFor Class[] - 不回滚异常类型

3.5 下游服务实现

下游服务无需添加注解,自动参与分布式事务:

@Service
public class InventoryServiceImpl implements InventoryService {
   

    @Autowired
    private InventoryMapper inventoryMapper;

    /**
     * 扣减库存
     * 无需 @GlobalTransactional,自动注册为分支事务
     */
    @Override
    public void decreaseStock(Long productId, Integer count) {
   
        int updated = inventoryMapper.decreaseStock(productId, count);
        if (updated == 0) {
   
            throw new RuntimeException("库存不足");
        }
    }
}
@Service
public class AccountServiceImpl implements AccountService {
   

    @Autowired
    private AccountMapper accountMapper;

    /**
     * 扣减余额
     * 无需 @GlobalTransactional,自动注册为分支事务
     */
    @Override
    public void decreaseBalance(Long userId, BigDecimal amount) {
   
        int updated = accountMapper.decreaseBalance(userId, amount);
        if (updated == 0) {
   
            throw new RuntimeException("余额不足");
        }
    }
}

3.6 测试验证

单元测试

@SpringBootTest
public class OrderServiceTest {
   

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private InventoryMapper inventoryMapper;

    @Test
    public void testCreateOrderSuccess() {
   
        // 准备数据
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setOrderId(10001L);
        orderDTO.setUserId(888L);
        orderDTO.setProductId(2001L);
        orderDTO.setCount(2);
        orderDTO.setAmount(new BigDecimal("199.00"));

        // 执行(应该成功)
        assertDoesNotThrow(() -> {
   
            orderService.createOrder(orderDTO);
        });

        // 验证订单已创建
        assertNotNull(orderMapper.selectById(10001L));

        // 验证库存已扣减
        Integer stock = inventoryMapper.selectById(2001L).getStock();
        assertEquals(98, stock); // 假设原库存 100
    }

    @Test
    public void testCreateOrderRollback() {
   
        // 准备数据
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setOrderId(10002L);
        orderDTO.setUserId(888L);
        orderDTO.setProductId(2001L);
        orderDTO.setCount(1000); // 超出库存

        // 执行(应该失败并回滚)
        assertThrows(RuntimeException.class, () -> {
   
            orderService.createOrder(orderDTO);
        });

        // 验证订单未创建
        assertNull(orderMapper.selectById(10002L));

        // 验证库存未扣减(回滚成功)
        Integer stock = inventoryMapper.selectById(2001L).getStock();
        assertEquals(100, stock); // 库存保持不变
    }
}

查看 Seata Server 日志

启动测试后,查看 Seata Server 日志:

# 查看事务日志
tail -f /opt/seata/logs/seata/seata-server.log

关键日志:

INFO  - Global transaction begin, xid: 192.168.1.100:8091:1234567890
INFO  - Branch register to tc, xid: 192.168.1.100:8091:1234567890, branchId: 1
INFO  - Branch register to tc, xid: 192.168.1.100:8091:1234567890, branchId: 2
INFO  - Branch register to tc, xid: 192.168.1.100:8091:1234567890, branchId: 3
INFO  - Global transaction commit, xid: 192.168.1.100:8091:1234567890

Seata Server 日志

四、常见问题排查

4.1 分支事务未自动注册

现象: TC 上没有分支事务记录

可能原因:

1. 手动创建了 DataSource

问题代码:

@Configuration
public class DataSourceConfig {
   

    @Bean
    public DataSource dataSource() {
   
        // ❌ 错误:直接返回 HikariDataSource
        return new HikariDataSource();
    }
}

解决方案:

@Configuration
public class DataSourceConfig {
   

    @Bean
    public DataSource dataSource() {
   
        // ✅ 正确:使用 DataSourceProxy 包装
        DataSource dataSource = new HikariDataSource();
        return new DataSourceProxy(dataSource);
    }
}

最佳实践:

  • 使用 Spring Boot 自动配置,无需手动创建 DataSource
  • 如必须手动创建,确保包装为 DataSourceProxy

2. 多数据源场景

问题描述: 使用 DynamicDataSource 切换数据源时,部分数据源未注册

解决方案:

@Configuration
public class MultiDataSourceConfig {
   

    @Bean
    public DataSource dynamicDataSource() {
   
        // 创建多个数据源
        DataSource ds1 = createDataSource("ds1");
        DataSource ds2 = createDataSource("ds2");

        // 包装为 DataSourceProxy
        DataSourceProxy proxy1 = new DataSourceProxy(ds1);
        DataSourceProxy proxy2 = new DataSourceProxy(ds2);

        // 配置动态数据源
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("ds1", proxy1);
        targetDataSources.put("ds2", proxy2);
        dynamicDataSource.setTargetDataSources(targetDataSources);

        return dynamicDataSource;
    }

    private DataSource createDataSource(String name) {
   
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/" + name);
        ds.setUsername("root");
        ds.setPassword("password");
        return ds;
    }
}

3. XID 丢失(最常见)

场景 1: 子线程中执行

❌ 错误示例:

@GlobalTransactional
public void createOrder(OrderDTO orderDTO) {
   
    // 主线程有 XID

    // ❌ 新线程没有 XID 上下文
    new Thread(() -> {
   
        inventoryService.decreaseStock(); // 不会注册分支事务
    }).start();
}

✅ 正确示例:

@GlobalTransactional
public void createOrder(OrderDTO orderDTO) {
   
    // 手动传递 XID
    String xid = RootContext.getXID();

    new Thread(() -> {
   
        try {
   
            // 绑定 XID 到新线程
            RootContext.bind(xid);
            inventoryService.decreaseStock(); // 会注册分支事务
        } finally {
   
            // 清理 XID
            RootContext.unbind();
        }
    }).start();
}
场景 2: Feign/Dubbo 调用未透传 XID

检查点:

  • ✅ Spring Cloud OpenFeign 默认支持 XID 透传
  • ✅ Dubbo 默认支持 XID 透传
  • ⚠️ 自定义 HTTP 客户端需手动传递

自定义 Feign 配置:

@Configuration
public class FeignConfig {
   

    @Bean
    public RequestInterceptor requestInterceptor() {
   
        return template -> {
   
            String xid = RootContext.getXID();
            if (xid != null) {
   
                // 将 XID 放入 HTTP Header
                template.header(RootContext.KEY_XID, xid);
            }
        };
    }
}
场景 3: 消息队列消费端

❌ 错误示例:

@RocketMQMessageListener(topic = "order-topic", ...)
public class OrderConsumer implements RocketMQListener<OrderDTO> {
   

    @Override
    public void onMessage(OrderDTO orderDTO) {
   
        // ❌ 没有绑定 XID,不会注册分支事务
        inventoryService.decreaseStock();
    }
}

✅ 正确示例:

@RocketMQMessageListener(topic = "order-topic", ...)
public class OrderConsumer implements RocketMQListener<OrderDTO> {
   

    @Autowired
    private TransactionalTemplate transactionalTemplate;

    @Override
    public void onMessage(OrderDTO orderDTO) {
   
        // 从消息头获取 XID
        String xid = getMessageHeaderXid();

        // 使用事务模板执行
        transactionalTemplate.execute(new TransactionalExecutor() {
   
            @Override
            public Object execute() throws Throwable {
   
                inventoryService.decreaseStock();
                return null;
            }

            @Override
            public UserTransaction getTransactionInfo() {
   
                // 设置 XID
                return new UserTransaction(xid, 30000);
            }
        });
    }

    private String getMessageHeaderXid() {
   
        // 从消息头提取 XID
        return MessageConst.PROPERTY_TRANSACTION_ID;
    }
}

4.2 事务不回滚

现象: 异常抛出但数据未回滚

可能原因:

1. 异常类型不匹配

❌ 错误示例:

@GlobalTransactional(rollbackFor = Exception.class)
public void createOrder() {
   
    // 抛出 RuntimeException,会回滚
    throw new RuntimeException("失败");
}

@GlobalTransactional // 默认只回滚 Exception
public void createOrder2() {
   
    // 抛出 RuntimeException,不会回滚!
    throw new RuntimeException("失败");
}

✅ 正确示例:

// 明确指定 rollbackFor
@GlobalTransactional(rollbackFor = Exception.class)
public void createOrder() {
   
    throw new Exception("失败"); // 会回滚
}

// 或捕获后重新抛出
@GlobalTransactional
public void createOrder() throws Exception {
   
    try {
   
        // ...
    } catch (Exception e) {
   
        log.error("失败", e);
        throw e; // 重新抛出,会回滚
    }
}

2. 异常被吞掉

❌ 错误示例:

@GlobalTransactional
public void createOrder() {
   
    try {
   
        inventoryService.decreaseStock();
    } catch (Exception e) {
   
        log.error("扣减库存失败", e);
        // ❌ 异常被吞掉,事务不会回滚
    }
}

✅ 正确示例:

@GlobalTransactional
public void createOrder() {
   
    try {
   
        inventoryService.decreaseStock();
    } catch (Exception e) {
   
        log.error("扣减库存失败", e);
        throw new RuntimeException("扣减库存失败", e); // 重新抛出
    }
}

4.3 性能问题

现象: 事务执行慢,TPS 上不去

优化建议:

1. Undo Log 配置优化

seata:
  client:
    undo:
      # 仅记录更新的列(减少日志大小)
      only-care-update-columns: true
      # 压缩阈值
      compress:
        enable: true
        type: zip
        threshold: 64k

2. 批量提交优化

seata:
  client:
    rm:
      # 异步提交缓冲区大小
      async-commit-buffer-limit: 10000
      # 批量发送请求
      report-success-enable: false

3. 数据库索引优化

-- 确保 undo_log 表有合适的索引
ALTER TABLE undo_log 
ADD INDEX idx_xid_branch (xid, branch_id);

-- 定期清理历史 undo_log
DELETE FROM undo_log 
WHERE log_created < DATE_SUB(NOW(), INTERVAL 7 DAY);

4.4 脏写问题

现象: Phase 1 提交后,其他事务修改了数据

原因: AT 模式 Phase 1 提交后立即释放锁,存在脏写风险

解决方案:

方案 1: 使用全局锁

seata:
  client:
    rm:
      lock:
        # 开启全局锁
        retry-interval: 10
        retry-times: 30
        retry-policy-branch-rollback-on-conflict: true

方案 2: 升级 TCC 模式

对隔离性要求高的场景,考虑使用 TCC 模式

五、最佳实践

5.1 开发规范

推荐做法:

1. 事务粒度控制

// ✅ 好:事务范围小,执行快
@GlobalTransactional(timeoutMills = 60000)
public void createOrder(OrderDTO orderDTO) {
   
    // 仅包含必要的数据库操作
    orderMapper.insert(orderDTO);
    inventoryService.decreaseStock();
    accountService.decreaseBalance();
}

// ❌ 不好:事务范围过大,包含耗时操作
@GlobalTransactional(timeoutMills = 300000)
public void processEverything(OrderDTO orderDTO) {
   
    createOrder(orderDTO);  // 数据库操作
    sendEmail();            // ❌ 耗时的 HTTP 调用 (可能 5-10 秒)
    generateReport();       // ❌ 复杂的 Excel 生成 (可能 20 秒)
    uploadToOSS();          // ❌ 文件上传 (可能 30 秒)
}

原则:

  • 事务内只包含必要的数据库操作
  • 避免 HTTP 调用、文件处理等耗时操作
  • 单个事务执行时间控制在 3 秒内

2. 超时时间设置

// 根据业务复杂度设置合理超时时间
@GlobalTransactional(timeoutMills = 300000) // 5 分钟
public void complexBusiness() {
   
    // 涉及 10+ 个微服务的复杂业务
}

@GlobalTransactional(timeoutMills = 60000) // 1 分钟
public void simpleBusiness() {
   
    // 涉及 2-3 个微服务的简单业务
}

@GlobalTransactional(timeoutMills = 10000) // 10 秒
public void quickBusiness() {
   
    // 简单的单表操作
}

建议:

  • 默认超时时间:60 秒
  • 复杂业务:不超过 5 分钟
  • 简单查询:10-20 秒

3. 异常处理

@GlobalTransactional(rollbackFor = Exception.class)
public void businessProcess(OrderDTO orderDTO) {
   
    try {
   
        // 业务逻辑
        orderService.create(orderDTO);
        inventoryService.decrease();
    } catch (SpecificException e) {
   
        // 记录详细日志
        log.error("订单创建失败,orderId={}", orderDTO.getOrderId(), e);
        // 抛出运行时异常,触发回滚
        throw new BusinessException("订单创建失败", e);
    }
}

要点:

  • 明确指定 rollbackFor = Exception.class
  • 捕获异常后必须重新抛出
  • 记录详细的错误日志便于排查

避免做法:

1. 避免在事务中执行耗时操作

@GlobalTransactional
public void badPractice(OrderDTO orderDTO) {
   
    // ✅ 快:数据库操作 (毫秒级)
    orderMapper.insert(orderDTO);

    // ❌ 慢:HTTP 调用 (秒级)
    restTemplate.postForObject(
        "http://external-service/api/notify", 
        orderDTO, 
        Response.class
    );  // 可能超时 5-10 秒

    // ❌ 慢:文件处理
    generateLargeExcel();  // 20-30 秒

    // ❌ 慢:第三方服务调用
    smsService.sendSms();  // 网络不稳定时可能 10+ 秒
}

改进方案:

@GlobalTransactional
public void goodPractice(OrderDTO orderDTO) {
   
    // 仅包含数据库操作
    orderMapper.insert(orderDTO);
    inventoryService.decrease();
    accountService.decrease();
}

// 异步执行耗时操作
@Component
public class OrderListener {
   

    @RocketMQMessageListener(topic = "order-created")
    public void onMessage(OrderDTO orderDTO) {
   
        // 异步发送通知
        sendEmailAsync(orderDTO);
        // 异步生成报表
        generateReportAsync(orderDTO);
    }
}

2. 避免事务嵌套

@GlobalTransactional
public void outerMethod() {
   
    // ❌ 内部方法也有 @GlobalTransactional
    innerMethod();  // 不会开启新事务,复用外层 XID
}

@GlobalTransactional(propagation = Propagation.REQUIRES_NEW)
public void innerMethod() {
   
    // ...
}

问题:

  • 内层事务不会开启新事务
  • 内层异常会导致外层整体回滚
  • 性能损耗(两次 TC 交互)

正确做法:

@GlobalTransactional
public void businessProcess() {
   
    // 直接调用普通方法
    step1();
    step2();
    step3();
}

// 无需注解
private void step1() {
    /* ... */ }
private void step2() {
    /* ... */ }
private void step3() {
    /* ... */ }

3. 避免跨线程 XID 丢失

@GlobalTransactional
public void processWithThreads() {
   
    String xid = RootContext.getXID();

    // ❌ 错误:新线程没有 XID
    new Thread(() -> {
   
        serviceA.doSomething();  // 不会注册分支事务
    }).start();

    // ✅ 正确:手动传递 XID
    CompletableFuture.runAsync(() -> {
   
        try {
   
            RootContext.bind(xid);  // 绑定 XID
            serviceB.doSomething(); // 会注册分支事务
        } finally {
   
            RootContext.unbind();   // 清理
        }
    });

    // ✅ 更好:使用事务模板
    transactionalTemplate.execute(() -> {
   
        serviceC.doSomething();
        return null;
    });
}

5.2 监控告警

关键指标监控

Prometheus 配置:

seata:
  metrics:
    enabled: true
    exporter-list: prometheus
    exporter-prometheus-port: 9898

核心监控指标:

指标名称 说明 告警阈值
global_transaction_tps 全局事务 TPS < 100 持续 5 分钟
global_transaction_success_rate 事务成功率 < 99%
global_transaction_latency_avg 平均耗时 > 3 秒
global_transaction_rollback_count 回滚数量 > 10/小时
undo_log_size undo_log 表大小 > 100 万行

Grafana 看板

导入 Seata 官方 Dashboard:

  • Dashboard ID: 13393
  • 监控内容:
    • 全局事务状态分布
    • 分支事务执行情况
    • TP99/TP95 延迟
    • 回滚趋势图

示例告警规则:

groups:
  - name: seata_alerts
    rules:
      # 事务成功率低于 99%
      - alert: SeataTransactionSuccessRateLow
        expr: rate(seata_global_transaction_success_total[5m]) / rate(seata_global_transaction_total[5m]) < 0.99
        for: 5m
        annotations:
          summary: "Seata 事务成功率过低"

      # 回滚事务过多
      - alert: SeataRollbackCountHigh
        expr: increase(seata_global_transaction_rollback_total[1h]) > 10
        for: 1h
        annotations:
          summary: "Seata 回滚事务过多"

5.3 性能调优

JVM 参数优化

#!/bin/bash
# seata-server.sh

JAVA_OPTS="-server \
  -Xms4g -Xmx4g \           # 堆内存 (根据服务器配置调整)
  -Xmn2g \                  # 新生代
  -XX:MetaspaceSize=256m \  # 元空间初始值
  -XX:MaxMetaspaceSize=512m \ # 元空间最大值
  -XX:+UseG1GC \            # G1 垃圾回收器
  -XX:MaxGCPauseMillis=100 \ # 最大 GC 停顿时间
  -XX:+HeapDumpOnOutOfMemoryError \ # OOM 时 dump 堆
  -XX:HeapDumpPath=/opt/seata/logs/heapdump.hprof"

export JAVA_OPTS

参数说明:

  • -Xms/-Xmx: 设置为相同值,避免内存动态伸缩
  • -Xmn: 新生代大小为堆的 1/2
  • -XX:+UseG1GC: G1 适合大堆低延迟场景
  • -XX:MaxGCPauseMillis: 控制 GC 停顿时间

数据库连接池优化

spring:
  datasource:
    hikari:
      # 最小空闲连接数
      minimum-idle: 10
      # 最大连接池大小
      maximum-pool-size: 30
      # 连接超时时间 (毫秒)
      connection-timeout: 30000
      # 空闲连接超时 (毫秒)
      idle-timeout: 600000
      # 连接最大生命周期 (毫秒)
      max-lifetime: 1800000
      # 连接测试查询
      connection-test-query: SELECT 1

优化建议:

  • maximum-pool-size: 根据并发量调整 (CPU 核数 * 2 + 1)
  • connection-timeout: 30 秒足够
  • 定期监控连接池使用率 (建议 < 80%)

Undo Log 优化

seata:
  client:
    undo:
      # 仅记录更新的列 (减少日志大小)
      only-care-update-columns: true
      # 压缩配置
      compress:
        enable: true        # 启用压缩
        type: zip           # 压缩算法
        threshold: 64k      # 超过 64KB 才压缩

效果:

  • 减少 undo_log 存储空间 50%-70%
  • 降低网络传输开销
  • 提升回滚速度

5.4 数据清理

定期清理策略

方案 1: MySQL 定时任务
-- 创建存储过程
DELIMITER {mathJaxContainer[0]}

DELIMITER ;

-- 创建定时任务,每天凌晨 2 点执行
CREATE EVENT IF NOT EXISTS cleanup_undo_log_event
ON SCHEDULE EVERY 1 DAY STARTS '2024-01-01 02:00:00'
DO CALL cleanup_seata_undo_log();
方案 2: Spring Boot 定时任务
@Component
public class UndoLogCleaner {
   

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 每天凌晨 2 点清理 7 天前的 undo_log
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void cleanUndoLog() {
   
        try {
   
            int days = 7;
            String sql = "DELETE FROM undo_log WHERE log_created < DATE_SUB(NOW(), INTERVAL ? DAY)";
            int count = jdbcTemplate.update(sql, days);
            log.info("清理 undo_log 完成,删除 {} 条记录", count);
        } catch (Exception e) {
   
            log.error("清理 undo_log 失败", e);
        }
    }
}
方案 3: Shell 脚本 + Crontab
#!/bin/bash
# clean_undo_log.sh

DB_HOST="localhost"
DB_USER="root"
DB_PASS="password"
DB_NAME="seata"
DAYS=7

mysql -h${DB_HOST} -u${DB_USER} -p${DB_PASS} ${DB_NAME} <<EOF
DELETE FROM undo_log 
WHERE log_created < DATE_SUB(NOW(), INTERVAL ${DAYS} DAY)
LIMIT 10000;
EOF

if [ $? -eq 0 ]; then
    echo "$(date): 清理 undo_log 成功" >> /var/log/cleanup.log
else
    echo "$(date): 清理 undo_log 失败" >> /var/log/cleanup.log
fi

Crontab 配置:

# 每天凌晨 2 点执行
0 2 * * * /opt/scripts/clean_undo_log.sh

5.5 安全防护

1. Nacos 鉴权

seata:
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      username: seata_user      # 专用账号
      password: StrongP@ssw0rd  # 强密码
      namespace: dev-wdg

安全建议:

  • 为 Seata 创建专用 Nacos 账号
  • 密码长度 >= 12 位,包含大小写 + 数字 + 特殊字符
  • 定期更换密码 (90 天)

2. 数据库权限最小化

-- 创建 Seata 专用数据库用户
CREATE USER 'seata'@'%' IDENTIFIED BY 'StrongP@ssw0rd';

-- 仅授予必要权限
GRANT SELECT, INSERT, UPDATE, DELETE ON seata.* TO 'seata'@'%';
GRANT CREATE TABLE, DROP, INDEX ON seata.* TO 'seata'@'%';

-- 撤销危险权限
REVOKE DROP ON *.* FROM 'seata'@'%';
REVOKE ALTER ON *.* FROM 'seata'@'%';
REVOKE GRANT OPTION ON *.* FROM 'seata'@'%';

FLUSH PRIVILEGES;

权限原则:

  • 仅授予业务必需的权限
  • 禁止 DROP、ALTER 等危险操作
  • 禁止访问其他数据库

3. 敏感配置加密

# 使用 Jasypt 加密数据库密码
seata:
  store:
    db:
      user: root
      password: ENC(AbCdEfGhIjKlMnOpQrStUvWxYz)  # 加密后的密码

配置方法:

<!-- 添加依赖 -->
<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>
# 生成加密密码
java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI \
  input="my_password" \
  password="encryption_key" \
  algorithm=PBEWITHHMACSHA512ANDAES_256

5.6 故障处理

建立应急预案

预案 1: 事务卡住不提交
-- 1. 查看卡住的事务
SELECT xid, status, begin_time, timeout, gmt_modified
FROM global_table
WHERE status IN (2, 4)  -- Committing 或 Rollbacking
AND gmt_modified < NOW() - INTERVAL 5 MINUTE;

-- 2. 手动提交 (谨慎使用!)
UPDATE global_table 
SET status = 3, gmt_modified = NOW() 
WHERE xid = 'xxx' AND status = 2;

-- 3. 清理锁记录
DELETE FROM lock_table WHERE xid = 'xxx';

操作流程:

  1. 确认业务数据安全
  2. 记录操作日志
  3. 执行手动提交
  4. 通知业务方验证
  5. 事后分析根因
预案 2: 大量事务回滚

处理步骤:

  1. 立即停止相关服务
  2. 查看 Seata Server 日志
  3. 分析回滚原因
  4. 修复 Bug 后重启
  5. 数据对账修复
预案 3: Undo Log 表爆满
-- 紧急清理
DELETE FROM undo_log 
WHERE log_status = 1  -- 已处理状态
LIMIT 100000;

-- 优化表
OPTIMIZE TABLE undo_log;

故障演练

定期演练计划:

演练场景 频率 参与人员
事务超时回滚 每月 1 次 开发 + 运维
数据库宕机切换 每季度 1 次 运维 + DBA
Seata Server 故障 每季度 1 次 运维
数据不一致修复 每半年 1 次 开发 + 测试

演练流程:

  1. 制定演练方案
  2. 准备测试环境
  3. 执行故障注入
  4. 观察系统反应
  5. 记录问题清单
  6. 优化改进措施

六、总结

6.1 AT 模式优势

零侵入: 无需修改业务代码,仅添加注解
简单易用: 学习成本低,快速上手
高性能: 两阶段非阻塞提交,TPS 高
广泛支持: 兼容 MySQL、Oracle、PostgreSQL 等主流数据库
生态完善: 与 Spring Cloud、Dubbo 无缝集成

6.2 适用场景

推荐场景:

  • 90% 的微服务分布式事务需求
  • 对隔离性要求不苛刻的业务 (可接受短暂脏读)
  • 追求快速上线、降低开发成本的项目
  • 团队技术能力一般,希望降低维护成本

不推荐场景:

  • 高并发秒杀系统 (考虑 TCC 模式)
  • 强一致性要求 (考虑 XA 模式)
  • 非关系型数据库场景 (Redis/MongoDB 等)
  • 跨语言调用场景 (Go/Python 等)

6.3 与其他模式对比

特性 AT TCC Saga XA
侵入性
性能 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐
隔离性 读未提交 业务隔离 无隔离 完全隔离
开发成本
适用场景 通用 高并发 长流程 强一致

6.4 快速选型指南

需要分布式事务吗?
├─ 否 → 使用本地事务
└─ 是 → 对隔离性要求高吗?
    ├─ 是 → 选择 TCC 模式
    └─ 否 → 是长流程编排吗?
        ├─ 是 → 选择 Saga 模式
        └─ 否 → 需要强一致性吗?
            ├─ 是 → 选择 XA 模式
            └─ 否 → ✅ 选择 AT 模式 (推荐)

6.5 学习资源

--

目录
相关文章
|
1月前
|
存储 关系型数据库 Nacos
分布式事务解决方案Seata之SeataServer部署
本文以Seata 1.6.1为例,详解分布式事务一致性解决方案:涵盖Seata Server部署、Nacos配置中心与MySQL存储集成、核心配置说明、数据库表初始化及启动验证,助力开发者快速落地强/最终一致性事务实践。(238字)
353 1
分布式事务解决方案Seata之SeataServer部署
|
2月前
|
SQL 关系型数据库 Java
吃透 Seata 分布式事务:原理拆解 + 生产级落地 + 全场景避坑实战
本文深度解析阿里开源分布式事务框架Seata:剖析TC/TM/RM三大角色与全局事务流程,详解AT(零侵入)、TCC(强控制)、SAGA(长事务)、XA(强一致)四大模式原理、适用场景及核心对比,并通过电商下单实战演示AT模式落地,最后系统梳理生产环境高可用、SQL限制、幂等处理、XID传播等全链路避坑指南。
712 4
|
29天前
|
缓存 监控 Java
【分布式】分布式核心组件——分布式熔断降级:熔断器状态机、熔断策略、降级方案、Resilience4j/Sentinel实现
本文系统化梳理分布式熔断降级完整知识体系,涵盖核心定位、状态机模型、熔断策略(慢调用/异常比例/数)、降级方案、Resilience4j与Sentinel深度对比、生产落地实践及云原生进阶扩展,助力学习、开发与面试一站式掌握。
|
SQL 关系型数据库 数据库
学习分布式事务Seata看这一篇就够了,建议收藏
学习分布式事务Seata看这一篇就够了,建议收藏
24765 2
|
1月前
|
人工智能 安全 机器人
“养龙虾”全攻略|OpenClaw(龙虾AI)阿里云轻量服务器零基础部署+QQ等四大IM集成+千问API配置指南
2026年,一款名为OpenClaw的开源AI智能体(AI Agent)全网爆火,因其Logo是一只红色小龙虾,“Claw”意为“钳子”,象征着能动手操作电脑,被广大爱好者亲切称为“龙虾AI”,而部署、调教、使用它的全过程也被戏称为“养龙虾”。这款遵循MIT开源协议的AI自动化引擎,彻底打破了传统AI仅限于“对话框”的局限,核心是让大模型从“只会对话”变成“能执行真实任务”的数字员工,真正实现了从“对话式AI”向“行动式AI”的跨越。
460 4
|
1月前
|
人工智能 小程序 API
零基础保姆级图文教程:阿里云计算巢OpenClaw部署、微信小程序接入与千问大模型API配置及避坑指南
2026年,OpenClaw(原Clawdbot)作为开源AI代理自动化框架的标杆产品,凭借零代码部署、跨平台兼容、大模型生态完善、多渠道集成便捷的核心优势,成为个人与团队搭建专属智能助手的首选方案。阿里云计算巢以一键部署、环境预置、自动运维、弹性扩展的特性,将OpenClaw复杂的环境配置与服务部署流程简化为分钟级可视化操作,彻底解决新手技术门槛问题,同时支持7×24小时稳定运行与自动扩缩容。搭配微信小程序集成,用户可通过微信随时随地与AI助手交互;配合阿里云千问Qwen3.6-Plus高性能大模型,实现智能对话、代码生成、任务自动化、文件处理、长文本分析等全场景能力。本文全程提供可直接复制
373 3
|
5月前
|
SQL 关系型数据库 数据库
分布式事务
本文介绍了分布式事务的概念、典型场景及解决方案。在微服务架构下,一次业务操作需跨多个数据库和远程调用协作完成,传统本地事务无法保证整体一致性。通过Seata框架可实现分布式事务控制,其AT模式无侵入、高性能,基于两阶段提交与undo log实现最终一致;XA模式则提供强一致性但性能较低。文章还结合下单、支付等场景演示了Seata的集成与应用。
|
12月前
|
Arthas 存储 监控
Arthas heapdump(dump java heap, 类似 jmap 命令的 heap dump 功能)
Arthas heapdump(dump java heap, 类似 jmap 命令的 heap dump 功能)
915 8
seata是怎么进行分布式事务控制的
seata是怎么进行分布式事务控制的
|
Apache 开发者
Apache Seata 如何解决 TCC 模式的幂等、悬挂和空回滚问题
【6月更文挑战第8天】Apache Seata 是一款分布式事务框架,解决TCC模式下的幂等、悬挂和空回滚问题。通过记录事务状态处理幂等,设置超时机制避免悬挂,明确标记Try操作成功来处理空回滚。Seata 提供丰富配置和管理功能,确保分布式事务的可靠性和效率,支持复杂事务处理场景,为企业业务发展提供支持。
933 7

热门文章

最新文章