【架构实战】分布式事务Seata-AT:解决跨服务数据一致性的终极方案

一、从一次"扣钱成功但没发货"说起

2018年双十一,我经历了职业生涯中最难忘的一次故障。

当时的订单下单流程是:

  1. 订单服务:创建订单,状态=待支付
  2. 支付服务:扣减用户余额
  3. 库存服务:扣减商品库存
  4. 订单服务:更新订单状态=已支付,发货

有一天,监控系统报警:有个用户的账户扣了500块,但订单显示"待支付",商品也没发货!

钱没了,东西没到手。用户打电话投诉,客服吓坏了。

我们排查了整整3个小时,发现问题出在:

复制代码
T1: 订单服务创建订单成功
T2: 支付服务扣款成功
T3: 库存服务扣库存成功
T4: 订单服务更新状态时------网络抖动,更新失败!

结果:钱扣了,货没发,订单状态还是"待支付"。

这是典型的分布式事务问题:一个操作涉及多个服务,要么全部成功,要么全部失败。但在分布式系统里,保证这一点非常困难。


二、什么是分布式事务

2.1 从本地事务说起

传统关系型数据库的事务(ACID):

sql 复制代码
BEGIN TRANSACTION;
-- 扣钱
UPDATE account SET balance = balance - 500 WHERE user_id = 1;
-- 减库存
UPDATE inventory SET stock = stock - 1 WHERE product_id = 100;
-- 创建订单
INSERT INTO orders (user_id, product_id, amount) VALUES (1, 100, 500);
COMMIT;

本地事务的保障:

  • Atomic(原子性):要么全部执行,要么全部回滚
  • Consistent(一致性):事务前后数据总量不变
  • Isolated(隔离性):并发事务互不干扰
  • Durable(持久性):提交后数据持久保存

2.2 分布式事务的问题

sql 复制代码
-- 分布式场景下,这3个操作可能在3个不同的数据库!

BEGIN TRANSACTION;  -- 谁来管理这个跨数据库的事务?
-- 订单库
INSERT INTO orders ...;  -- 在数据库A
-- 支付库
UPDATE account ...;  -- 在数据库B  
-- 库存库
UPDATE inventory ...;  -- 在数据库C
COMMIT;  -- 谁来提交/回滚?

没有分布式事务管理时:

  • T1成功,T2成功,T3失败 → 数据不一致
  • T1成功,T2失败 → 订单有了,钱没扣
  • T1失败,T2成功 → 钱扣了,没订单

2.3 CAP定理与BASE理论

CAP定理:

一个分布式系统不能同时满足 Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性)。

复制代码
分布式系统必须保证P(分区容错性)
所以只能在C(一致性)和A(可用性)之间选择

CP系统:优先保证一致性(如ZooKeeper、HBase)
AP系统:优先保证可用性(如Cassandra、Eureka)

BASE理论:

  • Basically Available:基本可用,允许部分失败
  • Soft state:软状态,数据可以暂时不一致
  • Eventually consistent:最终一致性,系统最终会达到一致状态

2.4 分布式事务的解决方案

方案 特点 适用场景
2PC/3PC 强一致,性能差 对数据一致性要求极高
TCC 最终一致,性能较好 跨服务、跨库
Saga 最终一致,性能好 长事务
消息队列 最终一致,异步处理 可接受延迟
本地消息表 最终一致,可靠消息 准实时场景

三、Seata概述:分布式事务解决方案

3.1 Seata是什么

Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务解决方案,提供了AT、TCC、Saga、XA四种事务模式。

复制代码
Seata架构:

┌─────────────────────────────────────────────────────────────┐
│                        Seata Server                          │
│                    (事务协调器 TC)                           │
│                    TC = Transaction Coordinator               │
│                                                              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │ Global     │  │ Branch      │  │   Session   │        │
│  │ Transaction│  │ Transaction │  │   Manager   │        │
│  │ Handler    │  │ Handler     │  │             │        │
│  └─────────────┘  └─────────────┘  └─────────────┘        │
│                                                              │
└────────────────────────────┬────────────────────────────────┘
                             │ 协调通信
        ┌────────────────────┼────────────────────┐
        │                    │                    │
        ▼                    ▼                    ▼
┌──────────────┐      ┌──────────────┐      ┌──────────────┐
│  应用服务A    │      │  应用服务B    │      │  应用服务C    │
│              │      │              │      │              │
│  ┌────────┐ │      │  ┌────────┐ │      │  ┌────────┐ │
│  │ TM     │ │      │  │ TM     │ │      │  │ TM     │ │
│  │(事务管理器)│ │      │  │(事务管理器)│ │      │  │(事务管理器)│ │
│  └────┬───┘ │      │  └────┬───┘ │      │  └────┬───┘ │
│       │     │      │       │     │      │       │     │
│  ┌────▼───┐ │      │  ┌────▼───┐ │      │  ┌────▼───┐ │
│  │ RM     │ │      │  │ RM     │ │      │  │ RM     │ │
│  │(资源管理器)│ │      │  │(资源管理器)│ │      │  │(资源管理器)│ │
│  └────────┘ │      │  └────────┘ │      │  └────────┘ │
└──────────────┘      └──────────────┘      └──────────────┘

角色说明:
- TC (Transaction Coordinator): Seata服务端,协调全局事务
- TM (Transaction Manager): 事务管理器,发起/提交/回滚全局事务
- RM (Resource Manager): 资源管理器,管理分支事务的资源

3.2 Seata四种模式

复制代码
Seata事务模式:

1. AT模式(Auto Transaction)
   - 自动处理,高层API
   - 基于本地事务 + Undolog
   - 无需改业务代码
   - 适合:同库/跨库操作

2. TCC模式(Try-Confirm-Cancel)
   - 两阶段提交
   - 业务代码实现
   - 性能最好
   - 适合:跨服务调用

3. Saga模式
   - 长事务编排
   - 补偿机制
   - 可异步执行
   - 适合:长流程

4. XA模式
   - 数据库原生支持
   - 强一致性
   - 性能较低
   - 适合:对数据一致性要求极高

四、Seata AT模式实战

4.1 AT模式原理

AT模式的核心思想:利用本地事务的ACID特性,通过Undolog记录数据变化,实现自动补偿。

复制代码
AT模式执行流程:

全局事务开启
        ↓
┌─────────────────────────────────────────────────┐
│ 第一阶段(自动,无感知)                          │
│                                                   │
│ Branch Transaction 1:                            │
│   BEGIN;                                         │
│   UPDATE account SET balance = balance - 500;   │
│   INSERT INTO undo_log ...;  ← 记录修改前的镜像   │
│   COMMIT;                                        │
│                                                   │
│ Branch Transaction 2:                            │
│   BEGIN;                                         │
│   UPDATE inventory SET stock = stock - 1;        │
│   INSERT INTO undo_log ...;  ← 记录修改前的镜像   │
│   COMMIT;                                        │
│                                                   │
│ 全局事务提交/回滚?                               │
│   ↓                                             │
└─────────────────────────────────────────────────┘
        ↓
┌─────────────────────────────────────────────────┐
│ 第二阶段(根据结果自动处理)                       │
│                                                   │
│ 全局事务提交:                                    │
│   DELETE FROM undo_log;  ← 删除undolog           │
│   (所有分支事务都已提交,数据已生效)              │
│                                                   │
│ 全局事务回滚:                                    │
│   根据undo_log生成反向SQL                        │
│   UPDATE account SET balance = balance + 500;   │
│   UPDATE inventory SET stock = stock + 1;       │
│   DELETE FROM undo_log;  ← 删除undolog           │
│                                                   │
└─────────────────────────────────────────────────┘

4.2 Seata Server部署

yaml 复制代码
# docker-compose.yml
version: '3'
services:
  seata:
    image: seataio/seata-server:2.0.0
    container_name: seata-server
    environment:
      - SEATA_CONFIG_NAME=file
      - STORE_MODE=db
      - SERVER_NODE=1
    ports:
      - "8091:8091"
    volumes:
      - ./registry.conf:/root/seata-config/registry.conf
      - ./file.conf:/root/seata-config/file.conf
    depends_on:
      - mysql
    networks:
      - seata-net

  mysql:
    image: mysql:8.0
    container_name: seata-mysql
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: seata
    ports:
      - "3306:3306"
    networks:
      - seata-net
    command: --default-authentication-plugin=mysql_native_password

networks:
  seata-net:
    driver: bridge
properties 复制代码
# file.conf
store {
  mode = "db"
  
  db {
    datasource = "druid"
    db-type = "mysql"
    driver-class-name = "com.mysql.cj.jdbc.Driver"
    url = "jdbc:mysql://mysql:3306/seata?useUnicode=true&characterEncoding=utf8"
    username = "root"
    password = "root123"
    min-conn = 10
    max-conn = 100
    global-table = "global_table"
    branch-table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}

server {
  recovery {
    committing-retry-period = 1000
    asyn-committing-retry-period = 1000
  }
  
  undo {
    log-table = "undo_log"
  }
}
conf 复制代码
# registry.conf
registry {
  type = "nacos"
  
  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

config {
  type = "nacos"
  
  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
  }
}

4.3 数据库初始化

sql 复制代码
-- Seata需要的表
CREATE TABLE IF NOT EXISTS `global_table` (
  `xid` VARCHAR(128) NOT NULL,
  `transaction_id` BIGINT,
  `branch_id` BIGINT NOT NULL,
  `resource_id` VARCHAR(256),
  `status` TINYINT NOT NULL,
  `application_data` VARCHAR(2000),
  `gmt_create` DATETIME,
  `gmt_modified` DATETIME,
  PRIMARY KEY (`xid`),
  KEY `idx_status_gmt_modified` (`status`,`gmt_modified`),
  KEY `idx_branch_id` (`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `branch_table` (
  `xid` VARCHAR(128) NOT NULL,
  `transaction_id` BIGINT NOT NULL,
  `branch_id` BIGINT NOT NULL,
  `resource_id` VARCHAR(256) NOT NULL,
  `status` TINYINT NOT NULL,
  `application_data` VARCHAR(2000),
  `gmt_create` DATETIME(6),
  `gmt_modified` DATETIME(6),
  PRIMARY KEY (`xid`,`branch_id`),
  KEY `idx_xid_status` (`xid`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `lock_table` (
  `xid` VARCHAR(128) NOT NULL,
  `transaction_id` BIGINT,
  `branch_id` BIGINT NOT NULL,
  `resource_id` VARCHAR(256),
  `table_name` VARCHAR(32),
  `pk` VARCHAR(32),
  `status` TINYINT NOT NULL DEFAULT '0',
  `gmt_create` DATETIME,
  `gmt_modified` DATETIME,
  PRIMARY KEY (`xid`,`branch_id`),
  KEY `idx_status` (`status`),
  KEY `idx_branch_id` (`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 每个业务库需要undo_log表
CREATE TABLE IF NOT EXISTS `undo_log` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `branch_id` XIDARG NOT NULL,
  `xid` VARCHAR(100) NOT NULL,
  `context` VARCHAR(128) NOT NULL,
  `rollback_info` LONGBLOB NOT NULL,
  `log_status` INT NOT NULL,
  `log_created` DATETIME NOT NULL,
  `log_modified` DATETIME NOT NULL,
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`),
  KEY `ix_log_created` (`log_created`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

4.4 Spring Boot集成Seata

xml 复制代码
<!-- Maven依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2023.0.1.2</version>
</dependency>

<!-- Seata自动配置 -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>
yaml 复制代码
# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/order_db
    username: root
    password: root123
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 20

seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: my_test_tx_group  # 事务分组
  enable-auto-data-source-proxy: true  # 自动代理数据源
  
  # 服务注册中心
  registry:
    type: nacos
    nacos:
      server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}
      application: seata-server
      group: SEATA_GROUP
      
  # 配置中心
  config:
    type: nacos
    nacos:
      server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}
      group: SEATA_GROUP

# Seata事务分组配置(需要和Nacos中的配置一致)
service:
  vgroup-mapping:
    my_test_tx_group: default  # 事务分组 -> 集群名

4.5 代码中使用Seata

java 复制代码
// 方式1:全局事务(@GlobalTransactional)
// 在Controller或Service入口方法添加注解
@Service
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private AccountFeignClient accountFeignClient;
    
    @Autowired
    private InventoryFeignClient inventoryFeignClient;
    
    @Override
    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public Order createOrder(Long userId, Long productId, Integer count) {
        // 1. 创建订单(本地事务,Seata自动管理)
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        order.setCount(count);
        order.setStatus("PENDING");
        order.setCreateTime(new Date());
        orderMapper.insert(order);
        
        // 2. 扣减账户余额(远程调用,Seata自动开启分支事务)
        accountFeignClient.deduct(userId, order.getAmount());
        
        // 3. 扣减库存(远程调用,Seata自动开启分支事务)
        inventoryFeignClient.deduct(productId, count);
        
        // 4. 更新订单状态
        order.setStatus("PAID");
        orderMapper.updateById(order);
        
        return order;
    }
}

五、Seata TCC模式实战

5.1 TCC原理

TCC模式需要业务代码实现三个方法:

  • Try:预留资源,冻结数据

  • Confirm:确认执行,使用预留资源

  • Cancel:取消执行,释放预留资源

    TCC执行流程:

    全局事务开启

    ┌─────────────────────────────────────────────────┐
    │ Try阶段(所有服务都要成功) │
    │ │
    │ AccountService.try(): │
    │ 冻结用户500元(balance_frozen += 500) │
    │ │
    │ InventoryService.try(): │
    │ 冻结库存1件(stock_frozen += 1) │
    │ │
    │ 结果:资源已预留,要么确认,要么回滚 │
    └─────────────────────────────────────────────────┘

    ├─ 所有Try成功 → Confirm阶段
    └─ 任一Try失败 → Cancel阶段

    ┌─────────────────────────────────────────────────┐
    │ Confirm阶段(所有服务确认) │
    │ │
    │ AccountService.confirm(): │
    │ balance -= 500(扣真实余额) │
    │ balance_frozen -= 500(解冻) │
    │ │
    │ InventoryService.confirm(): │
    │ stock -= 1(扣真实库存) │
    │ stock_frozen -= 1(解冻) │
    │ │
    └─────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────┐
    │ Cancel阶段(所有服务回滚) │
    │ │
    │ AccountService.cancel(): │
    │ balance_frozen -= 500(解冻就相当于回滚) │
    │ │
    │ InventoryService.cancel(): │
    │ stock_frozen -= 1(解冻就相当于回滚) │
    │ │
    └─────────────────────────────────────────────────┘

5.2 TCC实现代码

java 复制代码
// TCC接口定义
@LocalTCC
public interface AccountTccService {
    
    @TwoPhaseBusinessAction(
        name = "deductAccount",
        commitMethod = "confirm",
        rollbackMethod = "cancel"
    )
    // Try方法:预留资源
    boolean try deduct(
        @BusinessActionContextParameter(paramName = "userId") Long userId,
        @BusinessActionContextParameter(paramName = "amount") BigDecimal amount
    );
    
    // Confirm方法:确认执行
    boolean confirm(BusinessActionContext context);
    
    // Cancel方法:回滚
    boolean cancel(BusinessActionContext context);
}
java 复制代码
// TCC接口实现
@Service
public class AccountTccServiceImpl implements AccountTccService {
    
    @Autowired
    private AccountMapper accountMapper;
    
    @Override
    @Transactional
    public boolean try deduct(Long userId, BigDecimal amount) {
        // 1. 查询账户
        Account account = accountMapper.selectByUserId(userId);
        if (account == null) {
            throw new RuntimeException("账户不存在");
        }
        
        // 2. 检查余额是否足够
        if (account.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }
        
        // 3. 冻结金额 = 余额 - 冻结部分 = 可用金额
        // 实际实现中,可以在freeze_balance字段记录
        accountMapper.freezeBalance(userId, amount);
        
        return true;
    }
    
    @Override
    public boolean confirm(BusinessActionContext context) {
        // 获取Try阶段保存的数据
        Long userId = Long.valueOf(context.getActionContext("userId").toString());
        BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
        
        // 真正扣减余额(余额已在Try阶段冻结,这里直接扣除)
        accountMapper.deductBalance(userId, amount);
        
        return true;
    }
    
    @Override
    public boolean cancel(BusinessActionContext context) {
        // 获取Try阶段保存的数据
        Long userId = Long.valueOf(context.getActionContext("userId").toString());
        BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
        
        // 释放冻结的金额
        accountMapper.unfreezeBalance(userId, amount);
        
        return true;
    }
}

5.3 TCC空回滚与悬挂问题

java 复制代码
// TCC常见问题的处理

// 问题1:空回滚
// Try方法没执行,但Cancel执行了
// 原因:Try超时了,TC认为Try失败,实际可能成功了

@Override
public boolean cancel(BusinessActionContext context) {
    // 检查是否已执行过Try
    Long userId = Long.valueOf(context.getActionContext("userId").toString());
    BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
    
    // 查询是否有冻结记录
    Account account = accountMapper.selectByUserId(userId);
    
    // 如果没有冻结记录,说明是空回滚,直接返回成功
    if (account.getFreezeBalance().compareTo(amount) < 0) {
        return true;  // 空回滚,幂等处理
    }
    
    // 有冻结记录,正常释放
    accountMapper.unfreezeBalance(userId, amount);
    return true;
}

// 问题2:悬挂
// Cancel比Try先执行了
// 原因:网络问题,Try超时返回失败,但Try实际上成功了

@Override
@Transactional
public boolean try deduct(Long userId, BigDecimal amount) {
    // 检查是否已经Cancel过(幂等检查)
    Account account = accountMapper.selectByUserId(userId);
    
    // 如果可用余额已经扣减了(说明被Cancel过了)
    if (account.getBalance().compareTo(amount) < 0) {
        throw new RuntimeException("重复扣款");
    }
    
    // 正常冻结
    accountMapper.freezeBalance(userId, amount);
    return true;
}

六、Seata Saga模式实战

6.1 Saga模式原理

Saga模式适合长流程事务,每个参与者执行自己的本地事务,如果某个步骤失败,则执行前面步骤的补偿事务。

复制代码
Saga执行流程:

下单流程:

  Step 1: 创建订单 (正向)     → Compensation: 删除订单
      ↓
  Step 2: 扣减余额 (正向)     → Compensation: 退还余额
      ↓
  Step 3: 扣减库存 (正向)     → Compensation: 恢复库存
      ↓
  Step 4: 发送消息通知 (正向) → Compensation: 撤回消息
      ↓
  完成

如果Step 3失败:
  Step 1: 补偿:删除订单
  Step 2: 补偿:退还余额
  结束

6.2 Saga状态机配置

json 复制代码
// saga_state_machine.json
{
  "Name": "order-saga",
  "Comment": "订单创建Saga流程",
  "StartState": "CreateOrder",
  "States": {
    "CreateOrder": {
      "Type": "ServiceTask",
      "ServiceName": "order-service",
      "ServiceMethod": "createOrder",
      "CompensateState": "CancelOrder",
      "Next": "DeductAccount",
      "Status": {
        "Success": "SU",
        "BusinessError": "FA"
      }
    },
    "CancelOrder": {
      "Type": "ServiceTask",
      "ServiceName": "order-service",
      "ServiceMethod": "cancelOrder",
      "Status": {
        "Success": "SU"
      }
    },
    "DeductAccount": {
      "Type": "ServiceTask",
      "ServiceName": "account-service",
      "ServiceMethod": "deduct",
      "CompensateState": "RefundAccount",
      "Next": "DeductInventory",
      "Input": [
        "$.[context.userId]",
        "$.[context.amount]"
      ],
      "Status": {
        "Success": "SU",
        "Fail": "FA"
      }
    },
    "RefundAccount": {
      "Type": "ServiceTask",
      "ServiceName": "account-service",
      "ServiceMethod": "refund",
      "Status": {
        "Success": "SU"
      }
    },
    "DeductInventory": {
      "Type": "ServiceTask",
      "ServiceName": "inventory-service",
      "ServiceMethod": "deduct",
      "CompensateState": "RestoreInventory",
      "Next": "SuccessEnd",
      "Input": [
        "$.[context.productId]",
        "$.[context.count]"
      ]
    },
    "RestoreInventory": {
      "Type": "ServiceTask",
      "ServiceName": "inventory-service",
      "ServiceMethod": "restore"
    },
    "SuccessEnd": {
      "Type": "Succeed"
    }
  }
}

七、生产环境最佳实践

7.1 事务分组与隔离

yaml 复制代码
# 不同业务使用不同事务分组,互不影响
seata:
  tx-service-group:
    # 订单相关服务
    order-service-group: order-tx-group
    # 支付相关服务
    payment-service-group: payment-tx-group
    # 库存相关服务
    inventory-service-group: inventory-tx-group

service:
  vgroup-mapping:
    order-tx-group: seata-cluster-order
    payment-tx-group: seata-cluster-payment
    inventory-tx-group: seata-cluster-inventory

7.2 超时配置

yaml 复制代码
# 全局事务超时时间(默认60秒)
seata:
  client:
    rm:
      report-retry-interval: 5
      report-success-enable: false
    
    tm:
      deploy:
        async-commit-timeout: 60000  # 异步提交超时
        rollback-retry-timeout: 60000  # 回滚重试超时

7.3 性能优化

yaml 复制代码
# 优化建议

# 1. Seata服务端调优
seata:
  server:
    session:
      mode: db  # 存储模式:file/redis/db
    reorder-queue-capacity: 10000  # 重排序队列容量
    max-branch-inquiry-limit: 100  # 最大分支查询数
    
# 2. 分支事务并行执行
@Transactional
@GlobalTransactional(name = "parallel-order", parallelThreadCount = 10)
public void createOrderParallel() {
    // 分支事务会并行执行,提高性能
    accountFeignClient.deduct(userId, amount);  // 并行1
    inventoryFeignClient.deduct(productId, count);  // 并行2
}

# 3. 读写分离支持
seata:
  client:
    support:
      spring:
        datasource:
          enable-auto-data-source-proxy: true

7.4 监控与告警

yaml 复制代码
# Seata监控指标(Prometheus)
management:
  metrics:
    export:
      prometheus:
        enabled: true
        
# Seata控制台查看事务状态
# http://seata-server:8091
yaml 复制代码
# Prometheus告警规则
groups:
  - name: seata-alerts
    rules:
      - alert: SeataTransactionTimeout
        expr: seata_transaction_timeout_total > 0
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "Seata事务超时"

八、踩坑实录

坑1:Seata事务不生效

配置了@GlobalTransactional,但异常时不回滚。

原因:try-catch吞掉了异常,没有抛出到Seata。

解决:确保异常能抛出,或在catch中手动throw。

java 复制代码
// 错误写法
try {
    accountFeignClient.deduct(userId, amount);
} catch (Exception e) {
    log.error("扣款失败", e);
    // 异常被吞掉了!
}

// 正确写法
try {
    accountFeignClient.deduct(userId, amount);
} catch (Exception e) {
    log.error("扣款失败", e);
    throw new RuntimeException("扣款失败", e);  // 重新抛出
}

坑2:TCC服务间调用超时

Try阶段超时,但实际执行成功了,导致空回滚把钱退回去了。

解决:TCC模式必须处理幂等和空回滚问题。

坑3:Seata Server单点问题

Seata Server只有一台挂了,所有分布式事务都失败了。

解决:Seata Server集群部署,使用db模式存储事务状态。

坑4:全局锁竞争导致性能下降

高并发下,全局锁竞争严重,TPS下降明显。

解决

  • 使用AT模式代替TCC,减少锁范围
  • 优化数据库锁表结构
  • 分库分表减少热点

九、总结

分布式事务是微服务架构的难题:

  • Seata AT:自动处理,低侵入,适合大多数场景
  • Seata TCC:性能最好,需要业务实现补偿逻辑
  • Seata Saga:适合长流程,支持异步编排
  • Seata XA:强一致性,性能较低

最佳实践:

  1. 优先使用AT模式,业务代码零改动
  2. TCC模式要注意幂等、空回滚、悬挂三个问题
  3. Saga模式适合编排长流程业务
  4. 事务分组隔离,不同业务互不影响
  5. Seata Server集群部署保证高可用
  6. 全局事务超时时间要合理设置

血的教训:

分布式事务不是万能药,它会带来性能损耗和复杂性。能不用分布式事务就尽量不用,可以通过:

  • 避免跨服务事务(合并服务)
  • 最终一致性代替强一致性(消息队列)
  • 补偿机制(定期对账)
    只有真正需要强一致性时,才考虑分布式事务。

思考题: 你们系统有没有遇到过分布式事务问题?是怎么解决的?如果让你设计一个下单流程,你会用Seata的哪种模式?


个人观点,仅供参考

相关推荐
小短腿的代码世界1 小时前
Qt SSH2 深度解析:安全远程通信架构与源码级实现
qt·安全·架构
Jahport3 小时前
当量子计算时代进入倒计时,智能汽车的安全体系该如何重构?
人工智能·安全·重构·架构·量子计算·物联网安全
摇滚侠11 小时前
Redis 秒杀功能 超卖问题 一人一单问题 分布式锁 精彩!精彩!
redis·分布式·bootstrap
身如柳絮随风扬13 小时前
商品服务架构实战:多数据源切换与分级缓存设计全解析
缓存·架构
笨鸟先飞的橘猫13 小时前
MMO游戏中的“跨服团队副本”匹配与状态同步系统
分布式·学习·游戏·lua·skynet
豆豆13 小时前
2026年主流CMS技术选型对比:从架构特性到适用场景的深度解析
ai·架构·cms·建站系统·建站平台·内容管理系统·网站管理系统
easy_coder14 小时前
云产品诊断架构设计:路由 + 分层加载方案实践
人工智能·架构·云计算
达达尼昂15 小时前
Claude 多 Agent 系统:从零搭建一个 4 Agent 团队
前端·架构·ai编程
xcLeigh16 小时前
Python开篇:撬动未来的万能钥匙 —— 从入门到架构的全链路指南
数据库·python·架构·教程·应用·网页