分布式事务深度解析

70 阅读9分钟

微服务时代,分布式事务是每个后端工程师的必修课!本文带你彻底搞懂分布式事务的所有解决方案!

 开篇先问:你遇到过这些问题吗?

场景1:电商下单

  • 订单创建成功了
  • 库存也扣减了
  • 但支付失败了...

现在订单和库存怎么办?

场景2:转账操作

  • A账户扣款成功
  • 但B账户加款失败
  • 钱去哪了?

如果你遇到过这些问题,恭喜你,已经踏入了分布式事务的世界!


第1章:什么是事务?从ACID说起

本地事务

1.1 回顾经典的ACID特性

还记得数据库课上学的ACID吗?

  • A - 原子性:要么全成功,要么全失败
  • C - 一致性:数据前后状态一致
  • I - 隔离性:事务之间互不干扰
  • D - 持久性:提交后永久保存

1.2 单体应用的本地事务

在传统单体应用中,事务非常简单:

@Transactional  // 一个注解搞定!
public void createOrder(Order order) {
    // 1. 插入订单
    orderDao.insert(order);
    
    // 2. 插入订单明细
    orderItemDao.insertBatch(order.getItems());
    
    // 3. 扣减库存
    stockDao.deduct(order.getProductId(), order.getQuantity());
    
    // 任何一步失败 → 全部回滚 ✅
}

优点显而易见:  ✅ 实现简单 - 一个注解解决 ✅ 性能极高 - 数据库原生支持 ✅ 强一致性 - ACID完全保证

但局限也很明显:  ❌ 只能单库使用 ❌ 无法跨服务调用


第2章:分布式场景下的"灾难"

分布式事务场景图

2.1 微服务架构下的电商下单

现在我们把系统拆成了微服务:

public void placeOrder(OrderRequest req) {
    // 1. 订单服务 - 创建订单(数据库A)
    Order order = orderService.create(req);
    
    // 2. 库存服务 - 扣减库存(数据库B)
    stockService.deduct(req.getProductId(), req.getQuantity());
    
    // 3. 账户服务 - 扣款(数据库C)
    accountService.deduct(req.getUserId(), req.getAmount());
    
    // ❓ 如果第3步失败,前两步已经提交了,怎么办?
}

2.2 面临的核心问题

网络不可靠 - 服务调用可能超时、失败 部分成功 - 有的服务成功,有的失败 数据不一致 - 各服务数据库状态不同步 回滚困难 - 每个服务的事务独立提交

CAP定理告诉我们:  在分布式系统中,一致性(C)、可用性(A)、分区容错性(P),最多只能同时满足两个!


第3章:刚性事务 - 2PC/3PC强一致性方案

2PC两阶段提交

3.1 2PC(两阶段提交)

执行流程:

阶段1:准备阶段

  • 协调者问:大家能提交事务吗?
  • 参与者:执行事务但不提交,回答YES/NO

阶段2:提交阶段

  • 如果都是YES → 协调者:大家提交!

  • 如果有NO → 协调者:大家回滚!

    @Transactional public void placeOrder(OrderRequest req) { UserTransaction tx = getUserTransaction();

    try {
        tx.begin();  // 开启全局事务
        
        // 操作订单库
        Connection conn1 = orderDataSource.getConnection();
        conn1.execute("INSERT INTO orders ...");
        
        // 操作库存库
        Connection conn2 = stockDataSource.getConnection();
        conn2.execute("UPDATE stock ...");
        
        tx.commit();  // 全局提交
        
    } catch (Exception e) {
        tx.rollback();  // 全局回滚
    }
    

    }

优点:  ✅ 强一致性,符合ACID ✅ 实现相对简单

缺点:  ❌ 同步阻塞 - 所有参与者等待期间会阻塞 ❌ 单点故障 - 协调者挂了,整个系统不可用 ❌ 数据不一致 - 部分参与者可能收不到指令 ❌ 性能极差 - 锁定资源时间长

3.2 3PC(三阶段提交)

3PC三阶段提交

3PC在2PC基础上增加了预提交阶段:

阶段1:CanCommit(询问)

  • 只做预检查,不锁资源

阶段2:PreCommit(预提交)

  • 执行事务并锁资源,但不提交

阶段3:DoCommit(提交)

  • 执行最终提交或回滚

改进:  ✅ 引入超时机制,减少阻塞 ✅ 提前发现问题

仍然存在:  ❌ 实现更复杂 ❌ 性能开销更大 ❌ 仍有数据不一致风险


第4章:柔性事务之TCC模式 ⚡

TCC事务补偿模式

4.1 TCC核心思想

TCC将事务分为三个阶段:

  • Try(尝试)  - 预留资源,检查并锁定
  • Confirm(确认)  - 执行真正的业务逻辑
  • Cancel(取消)  - 释放资源,回滚操作

4.2 实战代码

// 库存服务 - TCC接口实现
@Service
public class StockTccService {
    
    /**
     * Try阶段:冻结库存
     */
    public boolean tryDeduct(Long productId, Integer quantity, String orderId) {
        Stock stock = stockMapper.selectById(productId);
        
        // 检查库存是否充足
        if (stock.getAvailable() < quantity) {
            return false;
        }
        
        // 冻结库存:available - quantity, frozen + quantity
        stockMapper.freezeStock(productId, quantity);
        
        // 记录冻结日志(重要!用于回滚)
        stockMapper.insertFreezeLog(orderId, productId, quantity, &#34;TRY&#34;);
        
        return true;
    }
    
    /**
     * Confirm阶段:确认扣减
     */
    public boolean confirmDeduct(String orderId) {
        FreezeLog log = stockMapper.selectFreezeLog(orderId);
        
        // 扣减冻结的库存:frozen - quantity
        stockMapper.deductFrozenStock(log.getProductId(), log.getQuantity());
        
        // 更新日志状态
        stockMapper.updateFreezeLog(orderId, &#34;CONFIRM&#34;);
        
        return true;
    }
    
    /**
     * Cancel阶段:释放库存
     */
    public boolean cancelDeduct(String orderId) {
        FreezeLog log = stockMapper.selectFreezeLog(orderId);
        
        // 解冻库存:available + quantity, frozen - quantity
        stockMapper.unfreezeStock(log.getProductId(), log.getQuantity());
        
        // 更新日志状态
        stockMapper.updateFreezeLog(orderId, &#34;CANCEL&#34;);
        
        return true;
    }
}

// 订单服务 - TCC事务协调
@Service
public class OrderTccCoordinator {
    
    public void placeOrder(OrderRequest req) {
        String orderId = UUID.randomUUID().toString();
        
        try {
            // Try阶段 - 预留资源
            boolean stockOk = stockTccService.tryDeduct(
                req.getProductId(), req.getQuantity(), orderId
            );
            
            boolean accountOk = accountTccService.tryDeduct(
                req.getUserId(), req.getAmount(), orderId
            );
            
            if (stockOk && accountOk) {
                // Confirm阶段 - 确认执行
                stockTccService.confirmDeduct(orderId);
                accountTccService.confirmDeduct(orderId);
            } else {
                // Cancel阶段 - 回滚
                if (stockOk) stockTccService.cancelDeduct(orderId);
                if (accountOk) accountTccService.cancelDeduct(orderId);
            }
            
        } catch (Exception e) {
            // 异常时Cancel
            stockTccService.cancelDeduct(orderId);
            accountTccService.cancelDeduct(orderId);
        }
    }
}

4.3 TCC的优缺点

优点:  ✅ 不依赖数据库事务,性能更好 ✅ 可以跨数据库、跨服务 ✅ 业务逻辑清晰

缺点:  ❌ 代码侵入性强,需要实现三个方法 ❌ 开发成本高 ❌ 需要考虑幂等性和悬挂问题

⚠️ 关键点:

  • 必须实现幂等性(重复调用结果一致)
  • 防止资源悬挂(Try成功但Confirm/Cancel失败)
  • 需要完善的日志记录

第5章:柔性事务之Saga模式

Saga长事务模式

5.1 Saga核心思想

Saga将长事务拆分为多个本地短事务,每个事务都有对应的补偿事务。

5.2 实战代码

@Service
public class OrderSagaService {
    
    public void placeOrder(OrderRequest req) {
        List compensations = new ArrayList<>();
        
        try {
            // 步骤1:创建订单
            Order order = orderService.createOrder(req);
            compensations.add(() -> orderService.cancelOrder(order.getId()));
            
            // 步骤2:扣减库存
            stockService.deductStock(req.getProductId(), req.getQuantity());
            compensations.add(() -> 
                stockService.addStock(req.getProductId(), req.getQuantity())
            );
            
            // 步骤3:扣减余额
            accountService.deductBalance(req.getUserId(), req.getAmount());
            compensations.add(() -> 
                accountService.addBalance(req.getUserId(), req.getAmount())
            );
            
            // 所有步骤成功
            orderService.confirmOrder(order.getId());
            
        } catch (Exception e) {
            // 逆序执行补偿操作
            for (int i = compensations.size() - 1; i >= 0; i--) {
                try {
                    compensations.get(i).compensate();
                } catch (Exception ex) {
                    // 记录补偿失败,人工介入
                    log.error(&#34;补偿失败: {}&#34;, ex.getMessage());
                }
            }
            throw new BusinessException(&#34;下单失败&#34;);
        }
    }
}

@FunctionalInterface
interface Compensation {
    void compensate();
}

5.3 Saga的优缺点

优点:  ✅ 长事务拆分,不会长时间锁定资源 ✅ 业务流程灵活 ✅ 适合复杂业务场景

缺点:  ❌ 不保证隔离性,可能出现脏读 ❌ 补偿逻辑复杂 ❌ 需要处理补偿失败的情况


第6章:可靠消息最终一致性

可靠消息最终一致性

6.1 核心思想

通过消息队列实现异步事务,保证最终一致性。

6.2 实战代码

// 订单服务 - 发送可靠消息
@Service
public class OrderMessageService {
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    @Transactional
    public void createOrderWithMessage(OrderRequest req) {
        // 1. 创建订单(本地事务)
        Order order = new Order();
        order.setUserId(req.getUserId());
        order.setAmount(req.getAmount());
        order.setStatus(&#34;PENDING&#34;);
        orderMapper.insert(order);
        
        // 2. 发送事务消息(确保消息和订单在同一事务中)
        rocketMQTemplate.sendMessageInTransaction(
            &#34;order-topic&#34;,
            MessageBuilder.withPayload(order).build(),
            null
        );
    }
    
    // 事务消息监听器
    @RocketMQTransactionListener
    class OrderTransactionListener implements RocketMQLocalTransactionListener {
        
        @Override
        public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            try {
                // 本地事务已在createOrderWithMessage中执行
                return RocketMQLocalTransactionState.COMMIT;
            } catch (Exception e) {
                return RocketMQLocalTransactionState.ROLLBACK;
            }
        }
        
        @Override
        public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
            // 回查本地事务状态
            Order order = JSON.parseObject(msg.getPayload(), Order.class);
            Order dbOrder = orderMapper.selectById(order.getId());
            
            return dbOrder != null ? 
                RocketMQLocalTransactionState.COMMIT : 
                RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}

// 库存服务 - 消费消息
@Service
@RocketMQMessageListener(topic = &#34;order-topic&#34;, consumerGroup = &#34;stock-consumer&#34;)
public class StockMessageConsumer implements RocketMQListener {
    
    @Override
    public void onMessage(Order order) {
        try {
            // 执行扣减库存(需要保证幂等性!)
            stockService.deductStock(order.getProductId(), order.getQuantity());
            
        } catch (Exception e) {
            // 消费失败,消息会重试
            log.error(&#34;扣减库存失败: {}&#34;, e.getMessage());
            throw new RuntimeException(e);
        }
    }
}

6.3 关键要点

三大核心机制:  事务消息 - 确保消息发送和本地事务绑定 消息回查 - MQ定期回查本地事务状态 幂等性 - 消费者需要实现幂等,防止重复消费

优点:  ✅ 性能高,异步处理 ✅ 解耦服务间依赖 ✅ 吞吐量大

缺点:  ❌ 最终一致性,存在延迟 ❌ 消息可能丢失或重复 ❌ 需要额外的消息中间件


第7章:最大努力通知

最大努力通知

7.1 适用场景

适用于对一致性要求不高的场景:

  • 支付结果通知
  • 订单状态通知
  • 对账场景

7.2 实战代码

@Service
public class NotificationService {
    
    /**
     * 发送通知,失败后定时重试
     */
    public void sendNotification(String targetUrl, Object data) {
        // 记录通知日志
        NotificationLog log = new NotificationLog();
        log.setTargetUrl(targetUrl);
        log.setData(JSON.toJSONString(data));
        log.setRetryCount(0);
        log.setStatus(&#34;PENDING&#34;);
        logMapper.insert(log);
        
        // 异步发送
        executeNotification(log);
    }
    
    private void executeNotification(NotificationLog log) {
        try {
            // 发送HTTP请求
            ResponseEntity response = restTemplate.postForEntity(
                log.getTargetUrl(),
                log.getData(),
                String.class
            );
            
            if (response.getStatusCode().is2xxSuccessful()) {
                // 成功
                log.setStatus(&#34;SUCCESS&#34;);
                logMapper.updateById(log);
            } else {
                // 失败,等待重试
                scheduleRetry(log);
            }
            
        } catch (Exception e) {
            // 异常,等待重试
            scheduleRetry(log);
        }
    }
    
    /**
     * 定时重试任务
     */
    @Scheduled(fixedDelay = 60000) // 每分钟执行一次
    public void retryFailedNotifications() {
        List pendingLogs = logMapper.selectPendingLogs();
        
        for (NotificationLog log : pendingLogs) {
            if (log.getRetryCount() >= 5) {
                // 超过最大重试次数,标记为失败
                log.setStatus(&#34;FAILED&#34;);
                logMapper.updateById(log);
                
                // 发送告警,人工介入
                alertService.sendAlert(&#34;通知失败: &#34; + log.getTargetUrl());
                
            } else {
                // 增加重试次数
                log.setRetryCount(log.getRetryCount() + 1);
                logMapper.updateById(log);
                
                // 重新发送
                executeNotification(log);
            }
        }
    }
}

第8章:Seata - 阿里开源的分布式事务神器

Seata AT模式

8.1 Seata架构

Seata包含三大角色:

  • TC(事务协调器)  - 维护全局事务和分支事务的状态
  • TM(事务管理器)  - 定义全局事务的范围
  • RM(资源管理器)  - 管理分支事务的资源

8.2 AT模式实战

AT模式是Seata的默认模式,对业务代码几乎无侵入

// 1. 引入依赖
/*

    com.alibaba.cloud
    spring-cloud-starter-alibaba-seata

*/

// 2. 配置文件
/*
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_tx_group
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
*/

// 3. 业务代码 - 订单服务
@Service
public class OrderServiceImpl {
    
    @Autowired
    private StockServiceClient stockService;
    @Autowired
    private AccountServiceClient accountService;
    
    @GlobalTransactional(name = &#34;create-order&#34;, rollbackFor = Exception.class)
    public void createOrder(OrderRequest req) {
        // 1. 创建订单
        Order order = new Order();
        order.setUserId(req.getUserId());
        order.setAmount(req.getAmount());
        orderMapper.insert(order);
        
        // 2. 远程调用库存服务
        stockService.deduct(req.getProductId(), req.getQuantity());
        
        // 3. 远程调用账户服务
        accountService.deduct(req.getUserId(), req.getAmount());
        
        // 任何一步失败,Seata会自动回滚所有操作 ✅
    }
}

// 4. 库存服务(只需要本地事务)
@Service
public class StockServiceImpl {
    
    @Transactional
    public void deduct(Long productId, Integer quantity) {
        stockMapper.deduct(productId, quantity);
    }
}

8.3 AT模式原理

一阶段:

  1. 解析SQL,生成前后镜像(before image & after image)
  2. 执行业务SQL
  3. 提交本地事务,释放本地锁
  4. 向TC注册分支事务

二阶段:

  • 如果成功:删除undo log,完成
  • 如果失败:根据undo log生成反向SQL,回滚数据

优势:  ✅ 无代码侵入 ✅ 性能高,一阶段即提交 ✅ 自动生成回滚SQL


第9章:生产实践与选型建议

9.1 方案对比表

方案一致性性能复杂度适用场景
2PC/3PC强一致金融核心系统
TCC最终一致⭐⭐⭐⭐⭐⭐⭐⭐⭐资金交易
Saga最终一致⭐⭐⭐⭐⭐⭐⭐长流程业务
可靠消息最终一致⭐⭐⭐⭐⭐⭐⭐⭐高并发场景
最大努力通知弱一致⭐⭐⭐⭐⭐⭐⭐通知类场景
Seata AT最终一致⭐⭐⭐⭐⭐⭐通用场景

9.2 选型建议

根据一致性要求选择:

  • 强一致性要求 → 选择2PC或Seata XA模式
  • 最终一致性可接受 → 选择柔性事务

根据业务特点选择:

  • 高性能要求 → 可靠消息或Saga
  • 快速落地 → Seata AT模式
  • 资金类业务 → TCC
  • 长流程业务 → Saga
  • 通知场景 → 最大努力通知

根据团队能力选择:

  • 新手团队 → Seata AT(简单易用)
  • 资深团队 → TCC(灵活可控)

 总结:分布式事务的本质

核心要点

1. 没有银弹

  • 不同场景需要不同方案
  • 一致性、可用性、性能需要权衡

2. 优先本地事务

  • 不要为了分布式而分布式
  • 能用本地事务就不要用分布式事务

3. 柔性事务是主流

  • 互联网场景更适合柔性事务
  • 追求最终一致性,提升性能

4. Seata是快速方案

  • 低侵入,易落地
  • 适合大部分业务场景