从零单排之分布式事务

0 阅读13分钟

分布式事务

一、分布式事务基本概念

1.1 定义与特点

graph TB
    subgraph 单体应用事务
        A1[Service] -->|本地事务| D1[(MySQL)]
        style A1 fill:#c8e6c9
        style D1 fill:#c8e6c9
    end
    
    subgraph 分布式事务
        A2[订单服务] -->|事务1| D2[(订单库)]
        A3[库存服务] -->|事务2| D3[(库存库)]
        A4[账户服务] -->|事务3| D4[(账户库)]
        
        A2 -.->|网络调用| A3
        A3 -.->|网络调用| A4
        
        style A2 fill:#ffcdd2
        style A3 fill:#ffcdd2
        style A4 fill:#ffcdd2
    end
    
    Note["多个数据库 + 网络通信<br/>ACID难以保证"]
    Note --> D4

分布式事务的核心挑战:

挑战说明示例
网络延迟节点间通信存在延迟订单服务调用库存服务,RT=200ms
网络分区网络故障导致节点间无法通信机房之间网络中断
节点故障部分节点随时可能宕机库存服务突然重启
数据一致性多个数据源状态需保持一致订单创建成功但库存未扣减

1.2 事务的ACID特性

graph LR
    A[原子性<br/>Atomicity] -->|保证| B[一致性<br/>Consistency]
    B -->|保证| C[隔离性<br/>Isolation]
    C -->|保证| D[持久性<br/>Durability]
    
    style A fill:#e3f2fd
    style B fill:#e8f5e9
    style C fill:#fff3e0
    style D fill:#fce4ec

ACID详解:

特性定义分布式场景下的难点解决思路
原子性操作要么全做,要么全不做多个节点操作,如何保证同时成功或同时回滚两阶段提交、事务协调器
一致性事务执行前后数据状态一致网络延迟导致各节点数据不一致最终一致性、强一致性协议
隔离性事务间互不干扰分布式锁实现复杂,性能开销大乐观锁、悲观锁、MVCC
持久性事务一旦提交,结果永久保存节点宕机可能丢失未持久化数据WAL日志、多副本复制

代码示例:本地事务 vs 分布式事务

// ========== 本地事务(单体应用)==========
@Service
public class LocalOrderService {
    
    @Transactional
    public void createOrder(Order order) {
        // 同一个数据库连接,同一个事务
        orderDao.insert(order);           // 插入订单
        orderItemDao.insert(orderItems);  // 插入订单项
        // 要么都成功,要么都回滚(数据库保证)
    }
}

// ========== 分布式事务(微服务)==========
@Service
public class DistributedOrderService {
    
    // 涉及3个服务 + 3个数据库,无全局事务控制
    public void createOrder(Order order) {
        orderService.createOrder(order);           // 订单库
        stockService.deductStock(order.getSkuId()); // 库存库(远程调用)
        accountService.deductBalance(order.getUserId(), order.getAmount()); // 账户库(远程调用)
        // 问题:如果库存扣减失败,订单已创建,如何回滚?
    }
}

二、分布式事务管理策略

2.1 两阶段提交协议(2PC)

sequenceDiagram
    participant C as Coordinator<br/>协调者
    participant P1 as Participant 1<br/>订单库
    participant P2 as Participant 2<br/>库存库
    participant P3 as Participant 3<br/>账户库
    
    Note over C,P3: 阶段一:准备阶段(Prepare Phase)
    C->>P1: 1. CanCommit?
    P1->>P1: 执行本地事务(不提交)
    P1-->>C: 2. Yes/No(Vote)
    
    C->>P2: 3. CanCommit?
    P2->>P2: 执行本地事务(不提交)
    P2-->>C: 4. Yes/No(Vote)
    
    C->>P3: 5. CanCommit?
    P3->>P3: 执行本地事务(不提交)
    P3-->>C: 6. Yes/No(Vote)
    
    Note over C,P3: 阶段二:提交阶段(Commit Phase)
    
    alt 所有参与者返回Yes
        C->>P1: 7. DoCommit
        P1->>P1: 正式提交本地事务
        P1-->>C: 8. ACK
        
        C->>P2: 9. DoCommit
        P2->>P2: 正式提交本地事务
        P2-->>C: 10. ACK
        
        C->>P3: 11. DoCommit
        P3->>P3: 正式提交本地事务
        P3-->>C: 12. ACK
        
        Note right of C: 事务完成!
    else 任一参与者返回No
        C->>P1: 7. DoRollback
        P1->>P1: 回滚本地事务
        C->>P2: 8. DoRollback
        P2->>P2: 回滚本地事务
        C->>P3: 9. DoRollback
        P3->>P3: 回滚本地事务
        Note right of C: 全部回滚!
    end

2PC 核心问题分析:

image.png

2PC 代码实现(Seata AT模式底层):

// 模拟2PC协调者
public class TwoPhaseCommitCoordinator {
    
    private List<Participant> participants = new ArrayList<>();
    
    /**
     * 阶段一:准备阶段
     */
    public boolean preparePhase() {
        boolean allYes = true;
        
        for (Participant p : participants) {
            try {
                // 询问参与者是否可以提交
                boolean vote = p.prepare();
                if (!vote) {
                    allYes = false;
                    break; // 有一个No就结束
                }
            } catch (Exception e) {
                allYes = false;
                break;
            }
        }
        
        return allYes;
    }
    
    /**
     * 阶段二:提交或回滚
     */
    public void commitPhase(boolean shouldCommit) {
        if (shouldCommit) {
            // 所有参与者都同意,发送DoCommit
            for (Participant p : participants) {
                p.commit(); // 正式提交
            }
        } else {
            // 有参与者不同意,发送DoRollback
            for (Participant p : participants) {
                p.rollback(); // 回滚
            }
        }
    }
}

// 参与者接口
public interface Participant {
    boolean prepare() throws Exception;  // 执行本地事务,不提交,返回Vote
    void commit();                       // 提交本地事务
    void rollback();                     // 回滚本地事务
}

2.2 三阶段提交协议(3PC)

sequenceDiagram
    participant C as Coordinator
    participant P1 as Participant 1
    participant P2 as Participant 2
    
    Note over C,P2: 阶段一:CanCommit(预检查)
    C->>P1: 1. CanCommit?
    P1->>P1: 检查资源是否可用(不执行事务)
    P1-->>C: 2. Yes/No
    
    C->>P2: 3. CanCommit?
    P2->>P2: 检查资源是否可用
    P2-->>C: 4. Yes/No
    
    Note over C,P2: 阶段二:PreCommit(预提交)
    alt 所有参与者返回Yes
        C->>P1: 5. PreCommit
        P1->>P1: 执行本地事务(不提交)
        P1-->>C: 6. ACK
        
        C->>P2: 7. PreCommit
        P2->>P2: 执行本地事务(不提交)
        P2-->>C: 8. ACK
    else 有参与者返回No
        C->>P1: 5. DoAbort
        C->>P2: 6. DoAbort
    end
    
    Note over C,P2: 阶段三:DoCommit(正式提交)
    alt 所有参与者PreCommit成功
        C->>P1: 9. DoCommit
        P1->>P1: 正式提交
        P1-->>C: 10. ACK
        
        C->>P2: 11. DoCommit
        P2->>P2: 正式提交
        P2-->>C: 12. ACK
    else 协调者超时或参与者未收到DoCommit
        P1->>P1: 超时自动提交(默认提交策略)
        P2->>P2: 超时自动提交
    end
    
    

2PC vs 3PC 对比:

特性2PC3PC
阶段数2个阶段3个阶段
阻塞性参与者阻塞等待协调者指令引入超时机制,减少阻塞
单点故障协调者宕机,参与者一直阻塞引入超时自动提交,降低阻塞风险
数据一致性协调者宕机可能不一致超时默认提交,仍可能不一致
性能较少网络交互多一次网络交互
实现复杂度相对简单更复杂

3PC的改进与局限:

graph LR
    A[2PC问题] -->|解决思路| B[3PC改进]
    
    A1[协调者宕机<br/>参与者阻塞] -->|引入超时| B1[参与者超时自动提交]
    A2[无法判断<br/>参与者状态] -->|增加CanCommit| B2[预检查阶段]
    
    B -->|新问题| C[3PC局限]
    B1 -->|网络分区时| C1[部分提交部分回滚<br/>数据不一致]
    B2 -->|多一轮交互| C2[性能更差]
    
    style A1 fill:#ffcdd2
    style A2 fill:#ffcdd2
    style C1 fill:#ffcdd2
    style C2 fill:#ffcdd2

2.3 补偿事务(TCC)

image.png

TCC 完整代码示例:

// ========== TCC接口定义 ==========
public interface OrderTccService {
    
    /**
     * Try阶段:预留资源
     * 创建订单,但状态为"冻结",不真正扣减资源
     */
    @TwoPhaseBusinessAction(name = "orderTccAction", 
        commitMethod = "commit", 
        rollbackMethod = "rollback")
    boolean tryCreateOrder(@BusinessActionContextParameter(paramName = "orderId") String orderId,
                           @BusinessActionContextParameter(paramName = "userId") String userId,
                           @BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
    
    /**
     * Confirm阶段:确认操作
     * 将订单状态改为"已支付",正式生效
     */
    boolean commit(BusinessActionContext context);
    
    /**
     * Cancel阶段:取消操作
     * 删除订单或改为"已取消",释放预留资源
     */
    boolean rollback(BusinessActionContext context);
}

// ========== 订单服务TCC实现 ==========
@Service
public class OrderTccServiceImpl implements OrderTccService {
    
    @Autowired
    private OrderDao orderDao;
    
    @Override
    public boolean tryCreateOrder(String orderId, String userId, BigDecimal amount) {
        // Try: 创建冻结订单
        Order order = new Order();
        order.setOrderId(orderId);
        order.setUserId(userId);
        order.setAmount(amount);
        order.setStatus("FREEZE"); // 冻结状态
        order.setCreateTime(new Date());
        
        // 插入订单记录(预留资源)
        int result = orderDao.insert(order);
        return result > 0;
    }
    
    @Override
    public boolean commit(BusinessActionContext context) {
        String orderId = context.getActionContext("orderId");
        
        // Confirm: 将冻结订单改为已支付
        Order order = orderDao.selectById(orderId);
        if ("FREEZE".equals(order.getStatus())) {
            order.setStatus("PAID");
            orderDao.update(order);
            log.info("订单{}确认成功", orderId);
            return true;
        }
        // 幂等性处理:如果已经是PAID,直接返回成功
        return "PAID".equals(order.getStatus());
    }
    
    @Override
    public boolean rollback(BusinessActionContext context) {
        String orderId = context.getActionContext("orderId");
        
        // Cancel: 取消订单,释放资源
        Order order = orderDao.selectById(orderId);
        if (order != null && "FREEZE".equals(order.getStatus())) {
            order.setStatus("CANCELLED");
            orderDao.update(order);
            log.info("订单{}已取消", orderId);
            return true;
        }
        return true; // 幂等性:已取消或不存在也算成功
    }
}

// ========== 库存服务TCC实现 ==========
@Service
public class StockTccServiceImpl {
    
    @Autowired
    private StockDao stockDao;
    @Autowired
    private StockFreezeDao freezeDao;
    
    public boolean tryDeduct(String skuId, Integer count) {
        // 检查库存是否充足
        Stock stock = stockDao.selectBySkuId(skuId);
        if (stock.getAvailable() < count) {
            return false; // 库存不足
        }
        
        // 冻结库存(不真正扣减)
        StockFreeze freeze = new StockFreeze();
        freeze.setSkuId(skuId);
        freeze.setCount(count);
        freeze.setStatus("FREEZE");
        freezeDao.insert(freeze);
        
        // 预扣减可用库存
        stockDao.deductAvailable(skuId, count);
        return true;
    }
    
    public boolean commit(String skuId, Integer count) {
        // 正式扣减:将冻结转为实际扣减
        freezeDao.updateStatus(skuId, "CONFIRMED");
        // 扣减总库存(可用已在Try阶段扣减)
        stockDao.deductTotal(skuId, count);
        return true;
    }
    
    public boolean rollback(String skuId, Integer count) {
        // 释放冻结:恢复可用库存
        freezeDao.updateStatus(skuId, "CANCELLED");
        stockDao.addAvailable(skuId, count);
        return true;
    }
}

TCC 事务协调流程:

sequenceDiagram
    participant TM as Transaction Manager
    participant O as Order服务
    participant S as Stock服务
    participant A as Account服务
    
    Note over TM,A: TCC事务开始
    TM->>O: 1. tryCreateOrder()
    O-->>TM: 2. 返回true(预留成功)
    
    TM->>S: 3. tryDeduct()
    S-->>TM: 4. 返回true(预留成功)
    
    TM->>A: 5. tryDeductBalance()
    alt 账户余额不足
        A-->>TM: 6a. 返回false
        TM->>O: 7a. rollback()
        TM->>S: 8a. rollback()
        Note right of TM: 全部回滚
    else 余额充足
        A-->>TM: 6b. 返回true
        
        Note over TM,A: 所有Try成功,执行Confirm
        TM->>O: 7b. commit()
        O-->>TM: 8b. 确认成功
        
        TM->>S: 9b. commit()
        S-->>TM: 10b. 确认成功
        
        TM->>A: 11b. commit()
        A-->>TM: 12b. 确认成功
        
        Note right of TM: 事务完成!
    end
    

TCC 核心要点:

要点说明实现注意
幂等性Confirm/Cancel可能被多次调用记录事务状态,已处理过的直接返回成功
空回滚Try未执行或失败,Cancel被调用Cancel需判断资源是否被预留
悬挂Try超时后执行,此时事务已回滚Try执行前检查事务是否已结束
业务侵入每个操作需拆分为3个方法业务逻辑需配合改造

三、分布式事务应用场景

3.1 微服务架构中的分布式服务调用

graph TB
    subgraph 电商下单场景
        GW[API网关]
        OS[订单服务<br/>order-service]
        SS[库存服务<br/>stock-service]
        PS[支付服务<br/>pay-service]
        US[用户服务<br/>user-service]
        
        DB1[(订单库)]
        DB2[(库存库)]
        DB3[(支付库)]
        DB4[(用户库)]
        
        GW --> OS
        OS -->|1.创建订单| DB1
        OS -->|2.扣减库存| SS
        SS -->|2.1| DB2
        OS -->|3.查询用户信息| US
        US -->|3.1| DB4
        OS -->|4.发起支付| PS
        PS -->|4.1| DB3
    end
    
    style OS fill:#ffcdd2
    style SS fill:#ffcdd2
    style PS fill:#ffcdd2
    style DB1 fill:#e8f5e9
    style DB2 fill:#e8f5e9

场景分析:

sequenceDiagram
    participant C as 用户
    participant OS as 订单服务
    participant SS as 库存服务
    participant PS as 支付服务
    
    C->>OS: 1. 提交订单请求
    OS->>OS: 2. 本地事务:创建订单(状态=待支付)
    
    OS->>SS: 3. 远程调用:扣减库存
    alt 库存充足
        SS->>SS: 3.1 扣减库存成功
        SS-->>OS: 3.2 返回成功
    else 库存不足
        SS-->>OS: 3.3 返回库存不足
        OS->>OS: 3.4 回滚本地订单
        OS-->>C: 3.5 下单失败
    end
    
    OS->>PS: 4. 远程调用:创建支付单
    PS->>PS: 4.1 创建支付记录
    PS-->>OS: 4.2 返回支付链接
    
    OS-->>C: 5. 返回订单信息+支付链接
    
    Note over C,PS: 问题:如果步骤3成功,步骤4失败,库存已扣但订单未支付<br/>需要分布式事务保证一致性

3.2 分布式缓存与数据库的一致性同步

graph TB
    subgraph 缓存一致性策略
        direction TB
        
        subgraph 先更新数据库再更新缓存
            A1[更新数据库] -->|成功| A2[更新缓存]
            A2 -->|失败| A3[缓存不一致]
            style A3 fill:#ffcdd2
        end
        
        subgraph "先更新数据库再删缓存(Cache Aside)"
            B1[更新数据库] -->|成功| B2[删除缓存]
            B2 -->|下次读取时| B3[从DB加载到缓存]
            style B2 fill:#c8e6c9
            style B3 fill:#c8e6c9
        end
        
        subgraph 延时双删
            C1[删除缓存] -->|更新数据库| C2[延时500ms]
            C2 -->|再次删除缓存| C3[保证最终一致]
            style C3 fill:#fff3e0
        end
    end

缓存一致性方案对比:

方案流程优点缺点适用场景
先更新DB再更新缓存更新DB → 更新Cache简单直观并发时数据不一致低并发读场景
Cache Aside(推荐)读:先Cache,miss则读DB写Cache
写:更新DB → 删Cache
简单可靠短暂不一致绝大多数场景
延时双删删Cache → 更新DB → 延时删Cache减少不一致窗口延时时间难确定高一致性要求
基于消息队列更新DB → 发送消息 → 异步更新Cache解耦最终一致高并发场景

基于消息队列的缓存同步代码:

@Service
public class ProductService {
    
    @Autowired
    private ProductDao productDao;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    /**
     * 更新商品信息:先更新DB,再发送消息异步更新缓存
     */
    @Transactional
    public void updateProduct(Product product) {
        // 1. 更新数据库(本地事务)
        productDao.update(product);
        
        // 2. 发送消息通知缓存更新(事务消息保证一致性)
        Message<ProductChangeEvent> message = MessageBuilder
            .withPayload(new ProductChangeEvent(product.getId(), "UPDATE"))
            .build();
        
        // 发送事务消息:保证消息发送和本地事务同时成功或失败
        TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
            "cache-update-topic", 
            message, 
            product.getId()
        );
        
        if (!result.getLocalTransactionState().equals(LocalTransactionState.COMMIT_MESSAGE)) {
            throw new BusinessException("缓存同步消息发送失败");
        }
    }
}

// 缓存更新消费者
@Service
@RocketMQMessageListener(topic = "cache-update-topic", consumerGroup = "cache-consumer")
public class CacheUpdateConsumer implements RocketMQListener<ProductChangeEvent> {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ProductDao productDao;
    
    @Override
    public void onMessage(ProductChangeEvent event) {
        Long productId = event.getProductId();
        
        switch (event.getAction()) {
            case "UPDATE":
                // 删除缓存,下次读取时从DB加载
                redisTemplate.delete("product:" + productId);
                break;
            case "DELETE":
                redisTemplate.delete("product:" + productId);
                break;
        }
    }
}

3.3 分布式消息队列的事务处理

image.png

RocketMQ事务消息完整代码:

@Service
public class OrderTransactionService {
    
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    /**
     * 创建订单并发送消息(事务消息保证一致性)
     */
    public void createOrderWithMessage(Order order) {
        
        // 构建消息
        Message<OrderCreatedEvent> message = MessageBuilder
            .withPayload(new OrderCreatedEvent(order.getOrderId(), order.getUserId()))
            .setHeader("KEYS", order.getOrderId())
            .build();
        
        // 发送事务消息
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
            "order-topic",
            message,
            order  // 本地事务参数
        );
        
        if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
            throw new RuntimeException("事务消息发送失败");
        }
    }
    
    /**
     * 事务监听器
     */
    @RocketMQTransactionListener
    class OrderTransactionListener implements RocketMQLocalTransactionListener {
        
        @Autowired
        private OrderDao orderDao;
        
        /**
         * 执行本地事务
         */
        @Override
        public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            Order order = (Order) arg;
            try {
                // 执行本地事务:创建订单
                orderDao.insert(order);
                
                // 本地事务成功,提交Half消息
                return RocketMQLocalTransactionState.COMMIT;
            } catch (Exception e) {
                // 本地事务失败,回滚Half消息
                return RocketMQLocalTransactionState.ROLLBACK;
            }
        }
        
        /**
         * 回查本地事务状态
         */
        @Override
        public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
            String orderId = msg.getHeaders().get("KEYS", String.class);
            
            // 查询订单是否存在
            Order order = orderDao.selectById(orderId);
            
            if (order != null) {
                // 订单存在,本地事务已提交
                return RocketMQLocalTransactionState.COMMIT;
            } else {
                // 订单不存在,本地事务未提交或已回滚
                return RocketMQLocalTransactionState.ROLLBACK;
            }
        }
    }
}

四、分布式事务挑战与解决方案

4.1 数据一致性问题

image.png

一致性保障方案对比:

方案原理优点缺点适用场景
分布式锁通过锁保证操作串行化强一致性能差,死锁风险库存扣减等强一致场景
分布式事务协调器2PC/3PC/TCC/Saga标准方案复杂度高金融交易
异步补偿失败时发送补偿消息最终一致,性能高补偿逻辑复杂长事务场景
本地消息表本地事务+消息表+定时扫描可靠,不依赖外部组件需要额外开发大多数业务场景

本地消息表实现:

graph TB
    subgraph 本地消息表架构
        A[业务服务] -->|1. 本地事务| B[(业务表)]
        A -->|1. 同一事务| C[(消息表)]
        
        D[定时任务] -->|2. 扫描未发送消息| C
        D -->|3. 发送消息| E[消息队列]
        E -->|4. 消费| F[下游服务]
        
        F -->|5. 业务处理| G[(下游库)]
        F -->|6. 发送ACK| E
        E -->|7. 更新消息状态| C
    end
    
    style C fill:#fff3e0
    style D fill:#e3f2fd
@Service
public class LocalMessageTableService {
    
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private MessageRecordDao messageDao;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 本地消息表模式:保证业务操作和消息发送的原子性
     */
    @Transactional
    public void createOrder(Order order) {
        // 1. 保存订单
        orderDao.insert(order);
        
        // 2. 同一事务中,插入消息记录
        MessageRecord record = new MessageRecord();
        record.setId(UUID.randomUUID().toString());
        record.setTopic("order-created");
        record.setBody(JsonUtils.toJson(order));
        record.setStatus("PENDING"); // 待发送
        record.setCreateTime(new Date());
        record.setRetryCount(0);
        messageDao.insert(record);
        
        // 事务提交后,订单和消息记录同时成功或同时失败
    }
    
    /**
     * 定时任务:扫描并发送未处理消息
     */
    @Scheduled(fixedRate = 5000) // 每5秒执行
    public void scanPendingMessages() {
        // 查询待发送消息(限制数量,防止积压)
        List<MessageRecord> pendingList = messageDao
            .selectByStatus("PENDING", 100);
        
        for (MessageRecord record : pendingList) {
            try {
                // 发送消息到MQ
                rabbitTemplate.convertAndSend(
                    record.getTopic(), 
                    record.getBody()
                );
                
                // 更新状态为已发送
                record.setStatus("SENT");
                record.setSendTime(new Date());
                messageDao.update(record);
                
            } catch (Exception e) {
                // 发送失败,增加重试次数
                record.setRetryCount(record.getRetryCount() + 1);
                
                if (record.getRetryCount() > 5) {
                    record.setStatus("FAILED"); // 进入死信队列人工处理
                }
                messageDao.update(record);
            }
        }
    }
}

4.2 事务超时与故障恢复

graph TB
    subgraph 超时处理策略
        A[设置合理超时时间] --> B[订单创建超时: 30秒]
        A --> C[支付超时: 15分钟]
        A --> D[库存冻结超时: 30分钟]
        
        B -->|超时后| E[自动回滚]
        C -->|超时后| F[订单关闭]
        D -->|超时后| G[自动释放库存]
    end
    
    subgraph 故障恢复机制
        H[日志记录] --> I[操作日志<br/>undo_log / redo_log]
        J[持久化记录] --> K[事务状态表]
        L[定时扫描] --> M[恢复未完成事务]
    end
    
    style E fill:#ffcdd2
    style F fill:#ffcdd2
    style G fill:#ffcdd2
    style M fill:#c8e6c9

超时与恢复代码示例:

@Service
public class TransactionRecoveryService {
    
    @Autowired
    private TransactionRecordDao transactionDao;
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private StockService stockService;
    
    /**
     * 定时任务:恢复超时未完成的事务
     */
    @Scheduled(fixedRate = 60000) // 每分钟执行
    public void recoverTimeoutTransactions() {
        // 查询超时事务(创建时间超过30分钟且状态未完成)
        Date timeout = new Date(System.currentTimeMillis() - 30 * 60 * 1000);
        List<TransactionRecord> timeoutList = transactionDao
            .selectTimeoutTransactions("INIT", timeout);
        
        for (TransactionRecord tx : timeoutList) {
            try {
                // 根据事务类型执行恢复
                switch (tx.getType()) {
                    case "ORDER_CREATE":
                        recoverOrderTransaction(tx);
                        break;
                    case "STOCK_DEDUCT":
                        recoverStockTransaction(tx);
                        break;
                }
            } catch (Exception e) {
                log.error("事务恢复失败: {}", tx.getId(), e);
                // 记录失败,人工介入
                tx.setStatus("RECOVER_FAILED");
                transactionDao.update(tx);
            }
        }
    }
    
    private void recoverOrderTransaction(TransactionRecord tx) {
        // 查询订单状态
        Order order = orderDao.selectByTxId(tx.getId());
        
        if (order == null) {
            // 订单未创建,直接标记事务为已回滚
            tx.setStatus("ROLLBACK");
        } else if ("PENDING".equals(order.getStatus())) {
            // 订单待支付但已超时,关闭订单并回滚库存
            order.setStatus("CLOSED");
            orderDao.update(order);
            
            // 发送库存回滚消息
            stockService.rollbackDeduct(order.getOrderId());
            
            tx.setStatus("ROLLBACK");
        }
        
        transactionDao.update(tx);
    }
}

4.3 性能瓶颈与优化

graph TB
    subgraph 性能优化策略
        A[减少网络通信开销] --> B[合并多个小事务]
        A --> C[批量操作<br/>Batch Insert/Update]
        A --> D[本地缓存热点数据]
        
        E[优化事务协调器性能] --> F[异步提交]
        E --> G[并行Prepare]
        E --> H[减少锁粒度]
        
        I[其他优化] --> J[读写分离]
        I --> K[分库分表]
        I --> L[异步化非核心操作]
    end
    
    style B fill:#c8e6c9
    style F fill:#c8e6c9
    style L fill:#c8e6c9

批量操作优化示例:

@Service
public class BatchOperationService {
    
    @Autowired
    private OrderDao orderDao;
    
    /**
     * 批量创建订单(减少事务数量)
     */
    @Transactional
    public void batchCreateOrders(List<Order> orders) {
        // 批量插入,减少数据库交互次数
        orderDao.batchInsert(orders);
        
        // 批量发送消息(而非逐条发送)
        List<Message> messages = orders.stream()
            .map(order -> MessageBuilder
                .withPayload(new OrderCreatedEvent(order.getOrderId()))
                .build())
            .collect(Collectors.toList());
        
        // 批量发送,减少网络开销
        rocketMQTemplate.syncSend("order-topic", messages, 3000);
    }
    
    /**
     * 异步化非核心操作
     */
    public void createOrderAsync(Order order) {
        // 核心操作:同步执行(创建订单)
        orderDao.insert(order);
        
        // 非核心操作:异步执行(发送通知、更新统计等)
        CompletableFuture.runAsync(() -> {
            sendNotification(order);
            updateStatistics(order);
        });
    }
}

五、分布式事务实践案例

5.1 电商平台的订单处理

graph TB
    subgraph 电商订单分布式事务
        U[用户] -->|1. 提交订单| GW[网关]
        GW -->|2. 创建订单| OS[订单服务]
        
        OS -->|3. 扣减库存| SS[库存服务]
        OS -->|4. 查询用户信息| US[用户服务]
        OS -->|5. 创建支付单| PS[支付服务]
        OS -->|6. 发送延迟消息<br/>15分钟后检查| MQ[消息队列]
        
        SS -->|3.1| SDB[(库存库)]
        US -->|4.1| UDB[(用户库)]
        PS -->|5.1| PDB[(支付库)]
        OS -->|2.1| ODB[(订单库)]
        
        MQ -->|7. 超时未支付| OS
        OS -->|8. 关闭订单| ODB
        OS -->|9. 释放库存| SS
    end
    
    style OS fill:#ffcdd2
    style SS fill:#ffcdd2
    style PS fill:#ffcdd2
    style MQ fill:#fff3e0

电商订单TCC完整实现:

@Service
public class EcommerceOrderService {
    
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private StockTccService stockTccService;
    @Autowired
    private AccountTccService accountTccService;
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    /**
     * 创建订单(TCC分布式事务)
     */
    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public OrderResult createOrder(CreateOrderRequest request) {
        String orderId = generateOrderId();
        String userId = request.getUserId();
        BigDecimal amount = request.getAmount();
        String skuId = request.getSkuId();
        Integer count = request.getCount();
        
        try {
            // ========== Try阶段:预留所有资源 ==========
            
            // 1. 订单服务Try:创建冻结订单
            boolean orderTry = orderTccService.tryCreateOrder(orderId, userId, amount);
            if (!orderTry) throw new BusinessException("订单创建失败");
            
            // 2. 库存服务Try:冻结库存
            boolean stockTry = stockTccService.tryDeduct(skuId, count);
            if (!stockTry) throw new BusinessException("库存不足");
            
            // 3. 账户服务Try:冻结金额
            boolean accountTry = accountTccService.tryDeduct(userId, amount);
            if (!accountTry) throw new BusinessException("余额不足");
            
            // ========== 所有Try成功,进入Confirm阶段 ==========
            // 由@GlobalTransactional自动触发各服务的commit
            
            // 发送延时消息:15分钟后检查支付状态
            sendDelayCheckMessage(orderId, 15 * 60 * 1000);
            
            return new OrderResult(orderId, "CREATED");
            
        } catch (Exception e) {
            // 任一Try失败,自动触发Cancel回滚所有预留资源
            throw e;
        }
    }
    
    /**
     * 支付回调(Confirm阶段)
     */
    public void onPaymentSuccess(String orderId) {
        // 将冻结订单改为已支付
        orderDao.updateStatus(orderId, "PAID");
        
        // 触发各服务Confirm
        // 库存:冻结转实际扣减
        // 账户:冻结转实际扣减
    }
    
    /**
     * 超时未支付处理(Cancel阶段)
     */
    @RocketMQMessageListener(topic = "order-timeout-check")
    public class OrderTimeoutListener implements RocketMQListener<String> {
        @Override
        public void onMessage(String orderId) {
            Order order = orderDao.selectById(orderId);
            
            if ("FREEZE".equals(order.getStatus())) {
                // 超时未支付,触发Cancel
                // 1. 取消订单
                orderDao.updateStatus(orderId, "CANCELLED");
                
                // 2. 释放库存
                stockTccService.cancelDeduct(order.getSkuId(), order.getCount());
                
                // 3. 释放金额
                accountTccService.cancelDeduct(order.getUserId(), order.getAmount());
            }
        }
    }
}

5.2 金融系统的转账操作

sequenceDiagram
    participant F as 转账服务
    participant A as 账户A服务
    participant B as 账户B服务
    participant L as 事务日志服务
    
    Note over F,L: 跨行转账分布式事务
    
    F->>F: 1. 生成全局事务ID
    
    F->>A: 2. Try:冻结账户A金额
    A->>A: 2.1 检查余额
    A->>A: 2.2 冻结金额(状态=PENDING)
    A->>L: 2.3 记录操作日志
    A-->>F: 2.4 返回成功
    
    F->>B: 3. Try:预增加账户B金额
    B->>B: 3.1 预增加金额(状态=PENDING)
    B->>L: 3.2 记录操作日志
    B-->>F: 3.3 返回成功
    
    alt 所有Try成功
        F->>A: 4a. Confirm:正式扣减
        A->>A: 4a.1 冻结转扣减(状态=CONFIRMED)
        A->>L: 4a.2 更新日志
        
        F->>B: 5a. Confirm:正式增加
        B->>B: 5a.1 预增加转正(状态=CONFIRMED)
        B->>L: 5a.2 更新日志
        
        Note over F,L: 转账成功!
    else 任一Try失败
        F->>A: 4b. Cancel:释放冻结
        A->>A: 4b.1 解冻金额
        A->>L: 4b.2 更新日志
        
        F->>B: 5b. Cancel:取消预增加
        B->>B: 5b.1 删除预增加记录
        B->>L: 5b.2 更新日志
        
        Note over F,L: 转账失败,全部回滚!
    end
    
    

金融转账TCC实现:

@Service
public class TransferService {
    
    @Autowired
    private AccountTccService accountTccService;
    @Autowired
    private TransferLogDao transferLogDao;
    
    /**
     * 跨行转账(强一致性要求)
     */
    @GlobalTransactional(name = "bank-transfer", timeoutMills = 30000)
    public TransferResult transfer(TransferRequest request) {
        String txId = request.getTransactionId();
        String fromAccount = request.getFromAccount();
        String toAccount = request.getToAccount();
        BigDecimal amount = request.getAmount();
        
        // 记录转账日志
        TransferLog log = new TransferLog();
        log.setTxId(txId);
        log.setFromAccount(fromAccount);
        log.setToAccount(toAccount);
        log.setAmount(amount);
        log.setStatus("INIT");
        transferLogDao.insert(log);
        
        try {
            // Try阶段
            boolean fromTry = accountTccService.tryDeduct(fromAccount, amount, txId);
            if (!fromTry) throw new InsufficientBalanceException("转出账户余额不足");
            
            boolean toTry = accountTccService.tryAdd(toAccount, amount, txId);
            if (!toTry) throw new AccountException("转入账户异常");
            
            // 所有Try成功,自动进入Confirm
            log.setStatus("SUCCESS");
            transferLogDao.update(log);
            
            return new TransferResult(txId, "SUCCESS");
            
        } catch (Exception e) {
            log.setStatus("FAILED");
            transferLogDao.update(log);
            throw e;
        }
    }
}

@Service
public class AccountTccServiceImpl {
    
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private AccountFreezeDao freezeDao;
    
    public boolean tryDeduct(String accountNo, BigDecimal amount, String txId) {
        Account account = accountDao.selectByAccountNo(accountNo);
        
        // 检查余额
        if (account.getBalance().compareTo(amount) < 0) {
            return false;
        }
        
        // 创建冻结记录
        AccountFreeze freeze = new AccountFreeze();
        freeze.setTxId(txId);
        freeze.setAccountNo(accountNo);
        freeze.setAmount(amount);
        freeze.setType("DEDUCT");
        freeze.setStatus("FREEZE");
        freezeDao.insert(freeze);
        
        // 扣减可用余额
        accountDao.deductAvailable(accountNo, amount);
        
        return true;
    }
    
    public boolean confirmDeduct(String txId) {
        AccountFreeze freeze = freezeDao.selectByTxId(txId);
        if (freeze == null || !"FREEZE".equals(freeze.getStatus())) {
            return true; // 幂等性处理
        }
        
        // 正式扣减:减少总余额
        accountDao.deductTotal(freeze.getAccountNo(), freeze.getAmount());
        
        // 更新冻结状态
        freeze.setStatus("CONFIRMED");
        freezeDao.update(freeze);
        
        return true;
    }
    
    public boolean cancelDeduct(String txId) {
        AccountFreeze freeze = freezeDao.selectByTxId(txId);
        if (freeze == null) return true;
        
        // 恢复可用余额
        accountDao.addAvailable(freeze.getAccountNo(), freeze.getAmount());
        
        // 更新冻结状态
        freeze.setStatus("CANCELLED");
        freezeDao.update(freeze);
        
        return true;
    }
}

5.3 分布式数据库的数据迁移与同步

graph TB
    subgraph 数据库迁移架构
        A[源数据库<br/>MySQL主库] -->|1. 全量迁移| B[目标数据库<br/>TiDB/分库分表]
        A -->|2. 增量同步| C[Canal/Debezium<br/>Binlog解析]
        C -->|3. 消息队列| D[Kafka]
        D -->|4. 消费同步| B
        
        E[迁移校验服务] -->|5. 数据一致性校验| A
        E -->|5. 数据一致性校验| B
        
        F[双写阶段] -->|6. 同时写入新旧库| A
        F -->|6. 同时写入新旧库| B
        
        G[灰度切换] -->|7. 逐步切流量| H[新库]
    end
    
    style C fill:#e3f2fd
    style D fill:#fff3e0
    style E fill:#c8e6c9
    style F fill:#ffcdd2

数据迁移事务一致性保障:

@Service
public class DataMigrationService {
    
    @Autowired
    private JdbcTemplate oldDbTemplate;
    @Autowired
    private JdbcTemplate newDbTemplate;
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    /**
     * 双写阶段:保证新旧库数据一致
     */
    @Transactional(transactionManager = "oldDbTxManager")
    public void dualWrite(Order order) {
        try {
            // 1. 写入旧库(主库,事务控制)
            oldDbTemplate.update(
                "INSERT INTO orders (id, user_id, amount, status) VALUES (?, ?, ?, ?)",
                order.getId(), order.getUserId(), order.getAmount(), order.getStatus()
            );
            
            // 2. 发送消息到Kafka,异步写入新库
            // 使用事务消息保证:旧库写入成功,消息一定发送
            kafkaTemplate.send("order-sync-topic", JsonUtils.toJson(order));
            
        } catch (Exception e) {
            // 旧库写入失败,事务回滚,消息不会发送
            throw e;
        }
    }
    
    /**
     * 增量同步消费者
     */
    @KafkaListener(topics = "order-sync-topic", groupId = "sync-consumer")
    public void syncToNewDb(String message) {
        Order order = JsonUtils.fromJson(message, Order.class);
        
        try {
            // 写入新库
            newDbTemplate.update(
                "INSERT INTO orders (id, user_id, amount, status) VALUES (?, ?, ?, ?) " +
                "ON DUPLICATE KEY UPDATE status = ?",
                order.getId(), order.getUserId(), order.getAmount(), 
                order.getStatus(), order.getStatus()
            );
        } catch (Exception e) {
            // 写入失败,进入死信队列人工处理
            kafkaTemplate.send("order-sync-dlq", message);
        }
    }
    
    /**
     * 数据一致性校验
     */
    public void verifyConsistency() {
        // 抽样对比新旧库数据
        List<Order> oldOrders = oldDbTemplate.query(
            "SELECT * FROM orders WHERE id > ? LIMIT 1000",
            new BeanPropertyRowMapper<>(Order.class),
            lastCheckedId
        );
        
        for (Order oldOrder : oldOrders) {
            Order newOrder = newDbTemplate.queryForObject(
                "SELECT * FROM orders WHERE id = ?",
                new BeanPropertyRowMapper<>(Order.class),
                oldOrder.getId()
            );
            
            if (!oldOrder.equals(newOrder)) {
                // 记录不一致,触发修复
                log.error("数据不一致: old={}, new={}", oldOrder, newOrder);
                repairInconsistency(oldOrder);
            }
        }
    }
}

六、主流分布式事务框架对比

graph TB
    subgraph 分布式事务框架
        A[Seata<br/>阿里开源] -->|AT模式| A1[自动补偿<br/>零侵入]
        A -->|TCC模式| A2[业务侵入<br/>高性能]
        A -->|Saga模式| A3[长事务<br/>状态机]
        A -->|XA模式| A4[强一致<br/>性能差]
        
        B[ByteTCC<br/>开源] -->|TCC| B1[纯TCC实现]
        
        C[ShardingSphere<br/>Apache] -->|XA| C1[集成Atomikos]
        C -->|BASE| C2[柔性事务]
        
        D[自研方案] -->|本地消息表| D1[可靠消息]
        D -->|Saga| D2[事件溯源]
    end
    
    style A fill:#e3f2fd
    style A1 fill:#c8e6c9
    style A2 fill:#fff3e0
    style D1 fill:#ffcdd2
框架模式侵入性性能适用场景学习成本
Seata AT自动补偿低(代理数据源)大多数业务场景
Seata TCC手动补偿高(需实现3个方法)高性能要求场景
Seata Saga状态机中(需定义状态图)长事务、业务流程复杂
Seata XA2PC强一致性要求
本地消息表异步补偿高(需自建)不依赖外部组件

总结图谱

mindmap
  root((分布式事务))
    基本概念
      定义与特点
        多数据源
        网络复杂性
      ACID特性
        原子性
        一致性
        隔离性
        持久性
    管理策略
      2PC
        准备阶段
        提交阶段
        同步阻塞问题
      3PC
        CanCommit
        PreCommit
        DoCommit
        超时机制
      TCC
        Try预留
        Confirm确认
        Cancel回滚
        幂等性要求
    应用场景
      微服务调用
      缓存一致性
      消息队列事务
    挑战与方案
      数据一致性
        强一致
        最终一致
        本地消息表
      超时故障
        超时设置
        故障恢复
      性能优化
        批量操作
        异步化
    实践案例
      电商订单
      金融转账
      数据迁移

分布式事务的核心在于根据业务场景选择合适的一致性模型和实现方案。强一致性方案(2PC/XA)性能较差但数据可靠,最终一致性方案(TCC/Saga/消息队列)性能高但需处理补偿逻辑。实际生产中,BASE理论(基本可用、软状态、最终一致)指导下的柔性事务方案更为常用。