💳 分布式事务的解决方案:转账的艺术!

39 阅读15分钟

📖 开场:跨行转账

想象你用支付宝给朋友的银行卡转账 💰:

单体应用(本地事务)

同一个数据库:
你的账户 -100元
朋友的账户 +100元
    ↓
事务:要么都成功,要么都失败 ✅

分布式系统(分布式事务)

支付宝数据库:
你的账户 -100元 ✅
    ↓
【网络故障】💔
    ↓
银行数据库:
朋友的账户 +100元 ❌ 失败

结果:
你扣了100元,朋友没收到 😱
钱凭空消失了!

这就是分布式事务问题:如何保证跨系统、跨数据库的操作一致性!


🤔 什么是分布式事务?

本地事务 vs 分布式事务

本地事务(ACID)

┌────────────────┐
│   一个数据库    │
│                │
│  BEGIN;        │
│  UPDATE A...   │ ← 事务内操作
│  UPDATE B...   │ ← 事务内操作
│  COMMIT;       │
│                │
└────────────────┘

特点:数据库保证ACID ✅

分布式事务

┌────────────────┐        ┌────────────────┐
│   服务A        │        │   服务B        │
│   数据库A      │        │   数据库B      │
│                │        │                │
│  UPDATE A...   │───?───→│  UPDATE B...   │
│                │        │                │
└────────────────┘        └────────────────┘

问题:如何保证两个操作的一致性?🤔
- A成功,B失败 → 不一致 ❌
- A失败,B成功 → 不一致 ❌
- 都成功或都失败 ✅

CAP理论

CAP = 分布式系统的三个特性

特性说明
C (Consistency)一致性:所有节点看到的数据相同
A (Availability)可用性:每个请求都能得到响应
P (Partition tolerance)分区容错性:网络分区时系统仍能运行

定理:分布式系统最多只能同时满足两个 ⚠️

CP:一致性 + 分区容错(牺牲可用性)
    例如:Zookeeper
    
AP:可用性 + 分区容错(牺牲一致性)
    例如:Eureka
    
CA:一致性 + 可用性(牺牲分区容错)
    例如:单机数据库(不是分布式)

BASE理论

BASE = CAP的延伸(最终一致性)

特性说明
BA (Basically Available)基本可用:允许部分不可用
S (Soft state)软状态:中间状态
E (Eventually consistent)最终一致性:最终达到一致

比喻

转账:
- CP方式:转账立即成功或失败(强一致性)
- BASE方式:转账可能延迟,但最终会成功(最终一致性)

银行转账就是BASE:
10:00 你转账100元 → 立即扣款
10:05 朋友收到款 → 延迟到账
最终一致 ✅

🎯 分布式事务的6种解决方案

方案1:2PC(两阶段提交)🔄

原理

两个阶段

  1. 准备阶段(Prepare):协调者询问参与者是否可以提交
  2. 提交阶段(Commit):所有参与者都同意,则提交;否则回滚
角色:
- 协调者(Coordinator):事务管理器
- 参与者(Participant):各个服务

流程:

阶段1:准备阶段
┌────────────┐
│  协调者    │ ── "准备提交" ──→ ┌──────────┐
└────────────┘                   │ 参与者A  │
      ↓                          └──────────┘
 等待所有参与者响应                    ↓
      ↓                          执行事务但不提交
      ↓                               ↓
      ↓                          "准备好了" ✅
      ↓                               ↓
      ↓                    ┌──────────┐
      ↓                    │ 参与者B  │
      ↓                    └──────────┘
      ↓                          ↓
      ↓                     执行事务但不提交
      ↓                          ↓
      ↓                     "准备好了" ✅
      ↓
   所有参与者都准备好 ✅

阶段2:提交阶段
┌────────────┐
│  协调者    │ ── "提交" ──→ ┌──────────┐
└────────────┘               │ 参与者A  │
                             └──────────┘
                                  ↓
                              COMMIT ✅
                                  
                             ┌──────────┐
                             │ 参与者B  │
                             └──────────┘
                                  ↓
                              COMMIT

代码示例(JTA)

import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private UserTransaction userTransaction;  // JTA事务管理器
    
    @Autowired
    private OrderDao orderDao;  // 订单数据库
    
    @Autowired
    private InventoryDao inventoryDao;  // 库存数据库
    
    /**
     * 创建订单(2PC)
     */
    public void createOrder(Order order) {
        try {
            // ⭐ 开启分布式事务
            userTransaction.begin();
            
            // ⭐ 参与者1:创建订单
            orderDao.insert(order);
            log.info("订单创建成功: orderId={}", order.getId());
            
            // ⭐ 参与者2:扣减库存
            inventoryDao.deduct(order.getProductId(), order.getQuantity());
            log.info("库存扣减成功: productId={}", order.getProductId());
            
            // ⭐ 提交事务(2PC)
            // 阶段1:准备阶段(询问所有参与者)
            // 阶段2:提交阶段(所有参与者提交)
            userTransaction.commit();
            
            log.info("分布式事务提交成功");
            
        } catch (Exception e) {
            log.error("分布式事务失败,回滚", e);
            
            try {
                // ⭐ 回滚事务(所有参与者回滚)
                userTransaction.rollback();
            } catch (Exception ex) {
                log.error("回滚失败", ex);
            }
        }
    }
}

优缺点

优点 ✅:

  • 强一致性(ACID)
  • 实现简单(JTA提供支持)

缺点 ❌:

  • 同步阻塞(等待所有参与者响应)
  • 单点故障(协调者挂了,整个系统不可用)
  • 性能差(等待时间长)
  • 数据库锁时间长(准备阶段到提交阶段)

适用场景

  • 对一致性要求极高
  • 并发量不高
  • 短事务

方案2:3PC(三阶段提交)🔄🔄

原理

三个阶段(改进2PC):

  1. CanCommit:询问是否可以提交(不执行事务)
  2. PreCommit:预提交(执行事务但不提交)
  3. DoCommit:正式提交
阶段1:CanCommit
协调者 → 参与者:"你能提交吗?"
参与者 → 协调者:"可以" or "不可以"

阶段2:PreCommit
协调者 → 参与者:"预提交"
参与者:执行事务,锁定资源,但不提交

阶段3:DoCommit
协调者 → 参与者:"正式提交"
参与者:COMMIT

改进点

  • 增加超时机制(避免无限等待)
  • 阶段1不锁定资源(减少锁时间)

问题

  • 依然有同步阻塞
  • 依然有单点故障
  • 实现更复杂

结论:实际应用很少,了解即可 ⚠️


方案3:TCC(Try-Confirm-Cancel)⭐⭐⭐

原理

三个阶段

  1. Try:尝试执行,预留资源
  2. Confirm:确认执行,使用预留的资源
  3. Cancel:取消执行,释放预留的资源
比喻:订酒店

Try:预定房间,冻结房间(不能给别人)
    ↓
所有服务的Try都成功
    ↓
Confirm:正式入住,扣款
    ↓
成功 ✅

或者:

Try:预定房间
    ↓
某个服务的Try失败
    ↓
Cancel:取消预定,释放房间
    ↓
回滚 ✅

代码示例

/**
 * 订单服务
 */
@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private AccountService accountService;  // 账户服务
    
    @Autowired
    private InventoryService inventoryService;  // 库存服务
    
    /**
     * 创建订单(TCC)
     */
    public boolean createOrder(Order order) {
        try {
            // ⭐ Try阶段:预留资源
            
            // 1. 冻结账户金额
            boolean accountTry = accountService.tryFreeze(
                order.getUserId(), order.getAmount());
            if (!accountTry) {
                return false;
            }
            
            // 2. 冻结库存
            boolean inventoryTry = inventoryService.tryFreeze(
                order.getProductId(), order.getQuantity());
            if (!inventoryTry) {
                // 库存冻结失败,取消账户冻结
                accountService.cancel(order.getUserId(), order.getAmount());
                return false;
            }
            
            // 3. 创建订单(状态:待确认)
            order.setStatus("PENDING");
            orderDao.insert(order);
            
            log.info("Try阶段成功");
            
            // ⭐ Confirm阶段:确认提交
            
            // 1. 扣款(使用冻结的金额)
            accountService.confirm(order.getUserId(), order.getAmount());
            
            // 2. 扣减库存(使用冻结的库存)
            inventoryService.confirm(order.getProductId(), order.getQuantity());
            
            // 3. 更新订单状态
            order.setStatus("CONFIRMED");
            orderDao.update(order);
            
            log.info("Confirm阶段成功");
            
            return true;
            
        } catch (Exception e) {
            log.error("订单创建失败,执行Cancel", e);
            
            // ⭐ Cancel阶段:取消,释放资源
            
            try {
                // 1. 解冻账户
                accountService.cancel(order.getUserId(), order.getAmount());
                
                // 2. 解冻库存
                inventoryService.cancel(order.getProductId(), order.getQuantity());
                
                // 3. 删除订单
                orderDao.delete(order.getId());
                
                log.info("Cancel阶段成功");
                
            } catch (Exception ex) {
                log.error("Cancel失败", ex);
            }
            
            return false;
        }
    }
}

账户服务

@Service
@Slf4j
public class AccountService {
    
    @Autowired
    private AccountDao accountDao;
    
    /**
     * ⭐ Try:冻结金额
     */
    public boolean tryFreeze(Long userId, BigDecimal amount) {
        Account account = accountDao.findByUserId(userId);
        
        // 检查余额
        if (account.getAvailable().compareTo(amount) < 0) {
            log.warn("余额不足: available={}, need={}", account.getAvailable(), amount);
            return false;
        }
        
        // 冻结金额:可用余额 → 冻结余额
        account.setAvailable(account.getAvailable().subtract(amount));
        account.setFrozen(account.getFrozen().add(amount));
        
        accountDao.update(account);
        
        log.info("冻结金额成功: userId={}, amount={}", userId, amount);
        return true;
    }
    
    /**
     * ⭐ Confirm:扣款(使用冻结的金额)
     */
    public void confirm(Long userId, BigDecimal amount) {
        Account account = accountDao.findByUserId(userId);
        
        // 冻结余额 → 扣除
        account.setFrozen(account.getFrozen().subtract(amount));
        account.setTotal(account.getTotal().subtract(amount));
        
        accountDao.update(account);
        
        log.info("扣款成功: userId={}, amount={}", userId, amount);
    }
    
    /**
     * ⭐ Cancel:解冻金额
     */
    public void cancel(Long userId, BigDecimal amount) {
        Account account = accountDao.findByUserId(userId);
        
        // 冻结余额 → 可用余额
        account.setFrozen(account.getFrozen().subtract(amount));
        account.setAvailable(account.getAvailable().add(amount));
        
        accountDao.update(account);
        
        log.info("解冻金额成功: userId={}, amount={}", userId, amount);
    }
}

库存服务

@Service
@Slf4j
public class InventoryService {
    
    @Autowired
    private InventoryDao inventoryDao;
    
    /**
     * ⭐ Try:冻结库存
     */
    public boolean tryFreeze(Long productId, int quantity) {
        Inventory inventory = inventoryDao.findByProductId(productId);
        
        // 检查库存
        if (inventory.getAvailable() < quantity) {
            log.warn("库存不足: available={}, need={}", inventory.getAvailable(), quantity);
            return false;
        }
        
        // 冻结库存:可用库存 → 冻结库存
        inventory.setAvailable(inventory.getAvailable() - quantity);
        inventory.setFrozen(inventory.getFrozen() + quantity);
        
        inventoryDao.update(inventory);
        
        log.info("冻结库存成功: productId={}, quantity={}", productId, quantity);
        return true;
    }
    
    /**
     * ⭐ Confirm:扣减库存(使用冻结的库存)
     */
    public void confirm(Long productId, int quantity) {
        Inventory inventory = inventoryDao.findByProductId(productId);
        
        // 冻结库存 → 扣除
        inventory.setFrozen(inventory.getFrozen() - quantity);
        inventory.setTotal(inventory.getTotal() - quantity);
        
        inventoryDao.update(inventory);
        
        log.info("扣减库存成功: productId={}, quantity={}", productId, quantity);
    }
    
    /**
     * ⭐ Cancel:解冻库存
     */
    public void cancel(Long productId, int quantity) {
        Inventory inventory = inventoryDao.findByProductId(productId);
        
        // 冻结库存 → 可用库存
        inventory.setFrozen(inventory.getFrozen() - quantity);
        inventory.setAvailable(inventory.getAvailable() + quantity);
        
        inventoryDao.update(inventory);
        
        log.info("解冻库存成功: productId={}, quantity={}", productId, quantity);
    }
}

优缺点

优点 ✅:

  • 不依赖数据库的分布式事务(2PC需要)
  • 性能较好(无长时间锁)
  • 强一致性

缺点 ❌:

  • 实现复杂(每个服务都要实现Try/Confirm/Cancel三个方法)
  • 对业务侵入性强(需要改造业务代码)
  • 幂等性要求高(Confirm/Cancel可能重复调用)

适用场景

  • 对一致性要求高
  • 业务允许改造
  • 互联网金融等场景 ⭐

方案4:本地消息表 📝

原理

核心思想:利用消息和本地事务保证最终一致性

服务A:
1. 开启本地事务
2. 执行业务操作(如扣款)
3. 插入消息表(状态:待发送)
4. 提交本地事务
    ↓
定时任务扫描消息表
    ↓
发送消息到MQ
    ↓
服务B消费消息
    ↓
执行业务操作(如加款)
    ↓
成功后通知服务A
    ↓
服务A更新消息状态(已发送)

代码示例

订单服务(发送方)

@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private OrderDao orderDao;
    
    @Autowired
    private MessageDao messageDao;  // 本地消息表
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 创建订单
     */
    @Transactional
    public void createOrder(Order order) {
        // ⭐ 1. 执行业务操作
        orderDao.insert(order);
        log.info("订单创建成功: orderId={}", order.getId());
        
        // ⭐ 2. 插入本地消息表(同一个本地事务)
        LocalMessage message = new LocalMessage();
        message.setId(UUID.randomUUID().toString());
        message.setContent(JSON.toJSONString(order));
        message.setStatus("PENDING");  // 待发送
        message.setCreateTime(new Date());
        
        messageDao.insert(message);
        log.info("消息保存成功: messageId={}", message.getId());
        
        // ⭐ 3. 提交本地事务(订单和消息同时提交)✅
    }
    
    /**
     * ⭐ 定时任务:扫描待发送的消息
     */
    @Scheduled(fixedRate = 5000)  // 每5秒执行一次
    public void sendPendingMessages() {
        // 查询待发送的消息
        List<LocalMessage> messages = messageDao.findByStatus("PENDING");
        
        for (LocalMessage message : messages) {
            try {
                // 发送到MQ
                rabbitTemplate.convertAndSend("order.exchange", "order.created", message.getContent());
                
                // 更新消息状态
                message.setStatus("SENT");
                message.setSendTime(new Date());
                messageDao.update(message);
                
                log.info("消息发送成功: messageId={}", message.getId());
                
            } catch (Exception e) {
                log.error("消息发送失败: messageId={}", message.getId(), e);
                // 失败了,下次继续重试
            }
        }
    }
}

库存服务(接收方)

@Service
@Slf4j
public class InventoryService {
    
    @Autowired
    private InventoryDao inventoryDao;
    
    /**
     * ⭐ 消费订单消息
     */
    @RabbitListener(queues = "inventory.queue")
    public void handleOrderCreated(String messageContent) {
        Order order = JSON.parseObject(messageContent, Order.class);
        
        log.info("收到订单消息: orderId={}", order.getId());
        
        try {
            // 扣减库存
            inventoryDao.deduct(order.getProductId(), order.getQuantity());
            
            log.info("库存扣减成功: productId={}", order.getProductId());
            
        } catch (Exception e) {
            log.error("库存扣减失败", e);
            // ⭐ 失败后,消息会重新投递(MQ的重试机制)
            throw new RuntimeException(e);
        }
    }
}

优缺点

优点 ✅:

  • 实现相对简单
  • 不依赖第三方框架
  • 性能较好(异步)

缺点 ❌:

  • 需要额外的消息表
  • 需要定时任务扫描
  • 最终一致性(有延迟)

适用场景

  • 对一致性要求不是特别高
  • 允许短时间不一致
  • 大部分互联网业务 ⭐

方案5:MQ事务消息(RocketMQ)📮

原理

利用MQ的半消息机制

1. 发送半消息(Half Message)到MQ
    ↓
2. MQ返回成功
    ↓
3. 执行本地事务
    ↓
4. 本地事务成功 → 提交消息(Commit)
   本地事务失败 → 回滚消息(Rollback)
    ↓
5. 消费者消费消息

代码示例

订单服务(发送方)

@Service
@Slf4j
public class OrderService implements RocketMQLocalTransactionListener {
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    @Autowired
    private OrderDao orderDao;
    
    /**
     * 创建订单
     */
    public void createOrder(Order order) {
        // ⭐ 1. 发送事务消息
        Message<Order> message = MessageBuilder.withPayload(order).build();
        
        rocketMQTemplate.sendMessageInTransaction(
            "order-producer-group",
            "OrderTopic:TagA",
            message,
            order  // 传递给本地事务的参数
        );
    }
    
    /**
     * ⭐ 2. 执行本地事务(RocketMQ回调)
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        Order order = (Order) arg;
        
        try {
            // 执行本地事务
            orderDao.insert(order);
            
            log.info("本地事务执行成功: orderId={}", order.getId());
            
            // ⭐ 返回COMMIT,消息会被消费者消费
            return RocketMQLocalTransactionState.COMMIT;
            
        } catch (Exception e) {
            log.error("本地事务执行失败", e);
            
            // ⭐ 返回ROLLBACK,消息会被删除
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
    
    /**
     * ⭐ 3. 事务回查(MQ定期回查事务状态)
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        // 从数据库查询订单是否存在
        Order order = JSON.parseObject(new String((byte[]) msg.getPayload()), Order.class);
        Order dbOrder = orderDao.findById(order.getId());
        
        if (dbOrder != null) {
            // 订单存在,提交消息
            return RocketMQLocalTransactionState.COMMIT;
        } else {
            // 订单不存在,回滚消息
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}

库存服务(接收方)

@Service
@Slf4j
@RocketMQMessageListener(
    topic = "OrderTopic",
    consumerGroup = "inventory-consumer-group"
)
public class InventoryService implements RocketMQListener<Order> {
    
    @Autowired
    private InventoryDao inventoryDao;
    
    /**
     * ⭐ 消费消息
     */
    @Override
    public void onMessage(Order order) {
        log.info("收到订单消息: orderId={}", order.getId());
        
        try {
            // 扣减库存
            inventoryDao.deduct(order.getProductId(), order.getQuantity());
            
            log.info("库存扣减成功");
            
        } catch (Exception e) {
            log.error("库存扣减失败", e);
            // ⭐ 抛异常,MQ会重试
            throw new RuntimeException(e);
        }
    }
}

优缺点

优点 ✅:

  • 实现简单(MQ封装了复杂逻辑)
  • 性能好(异步)
  • 可靠性高(MQ保证)

缺点 ❌:

  • 依赖RocketMQ(Kafka不支持)
  • 最终一致性(有延迟)

适用场景

  • 使用RocketMQ的项目
  • 对一致性要求不是特别高
  • 互联网业务 ⭐⭐⭐

方案6:Saga模式 📜

原理

Saga = 长事务拆分成多个短事务 + 补偿机制

正向操作:T1 → T2 → T3 → ... → Tn
补偿操作:C1 ← C2 ← C3 ← ... ← Cn

场景1:全部成功
T1 成功 → T2 成功 → T3 成功 ✅

场景2:T3失败
T1 成功 → T2 成功 → T3 失败 ❌
    ↓
C2补偿 ← C1补偿 ← 回滚完成 ✅

代码示例

@Service
@Slf4j
public class OrderSagaService {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private AccountService accountService;
    
    @Autowired
    private InventoryService inventoryService;
    
    /**
     * Saga模式:创建订单
     */
    public boolean createOrderSaga(Order order) {
        boolean step1 = false;
        boolean step2 = false;
        boolean step3 = false;
        
        try {
            // ⭐ 步骤1:创建订单
            step1 = orderService.createOrder(order);
            if (!step1) {
                throw new Exception("创建订单失败");
            }
            log.info("✅ 步骤1:创建订单成功");
            
            // ⭐ 步骤2:扣款
            step2 = accountService.deduct(order.getUserId(), order.getAmount());
            if (!step2) {
                throw new Exception("扣款失败");
            }
            log.info("✅ 步骤2:扣款成功");
            
            // ⭐ 步骤3:扣减库存
            step3 = inventoryService.deduct(order.getProductId(), order.getQuantity());
            if (!step3) {
                throw new Exception("扣减库存失败");
            }
            log.info("✅ 步骤3:扣减库存成功");
            
            return true;
            
        } catch (Exception e) {
            log.error("Saga事务失败,开始补偿", e);
            
            // ⭐ 补偿:按相反顺序回滚
            
            if (step3) {
                // 补偿步骤3:恢复库存
                inventoryService.compensateAdd(order.getProductId(), order.getQuantity());
                log.info("补偿:恢复库存");
            }
            
            if (step2) {
                // 补偿步骤2:退款
                accountService.compensateAdd(order.getUserId(), order.getAmount());
                log.info("补偿:退款");
            }
            
            if (step1) {
                // 补偿步骤1:删除订单
                orderService.compensateCancel(order.getId());
                log.info("补偿:删除订单");
            }
            
            return false;
        }
    }
}

优缺点

优点 ✅:

  • 适合长事务(T1→T2→...→Tn)
  • 不需要锁资源
  • 性能好

缺点 ❌:

  • 实现复杂(需要为每个操作编写补偿逻辑)
  • 不保证隔离性(中间状态可见)
  • 补偿逻辑复杂

适用场景

  • 长流程业务(如订单流程)
  • 对性能要求高
  • 可以容忍中间状态

📊 六种方案对比

方案一致性性能复杂度适用场景
2PC⭐⭐⭐ 强一致⭐ 差⭐⭐ 中短事务、低并发
3PC⭐⭐⭐ 强一致⭐ 差⭐⭐⭐ 高很少用
TCC⭐⭐⭐ 强一致⭐⭐ 好⭐⭐⭐ 高金融、支付
本地消息表⭐⭐ 最终一致⭐⭐⭐ 好⭐⭐ 中互联网业务
MQ事务消息⭐⭐ 最终一致⭐⭐⭐ 好⭐ 低推荐 ⭐⭐⭐
Saga⭐⭐ 最终一致⭐⭐⭐ 好⭐⭐⭐ 高长流程业务

🎓 面试题速答

Q1: 分布式事务有哪些解决方案?

A: 六种方案

  1. 2PC:两阶段提交,强一致性但性能差
  2. 3PC:三阶段提交,改进2PC但很少用
  3. TCC:Try-Confirm-Cancel,强一致性,适合金融
  4. 本地消息表:最终一致性,实现简单
  5. MQ事务消息:RocketMQ支持,推荐 ⭐⭐⭐
  6. Saga:长事务拆分+补偿,适合长流程

最常用:MQ事务消息(RocketMQ)


Q2: 什么是2PC?有什么问题?

A: 2PC = 两阶段提交

两个阶段

  1. 准备阶段:协调者询问所有参与者是否可以提交
  2. 提交阶段:所有参与者都同意,则提交;否则回滚

问题

  1. 同步阻塞(等待所有参与者响应)
  2. 单点故障(协调者挂了)
  3. 性能差(锁资源时间长)

Q3: TCC是什么?如何实现?

A: TCC = Try-Confirm-Cancel

三个阶段

  1. Try:尝试执行,预留资源(如冻结余额)
  2. Confirm:确认执行,使用预留的资源(如扣款)
  3. Cancel:取消执行,释放预留的资源(如解冻)

示例

// Try:冻结余额
account.setAvailable(account.getAvailable().subtract(amount));
account.setFrozen(account.getFrozen().add(amount));

// Confirm:扣款
account.setFrozen(account.getFrozen().subtract(amount));
account.setTotal(account.getTotal().subtract(amount));

// Cancel:解冻
account.setFrozen(account.getFrozen().subtract(amount));
account.setAvailable(account.getAvailable().add(amount));

Q4: MQ事务消息如何保证一致性?

A: 原理:半消息机制

1. 发送半消息到MQ(消费者看不到)
2. 执行本地事务
3. 本地事务成功 → 提交消息(消费者可以消费)
   本地事务失败 → 回滚消息(删除)
4. MQ定期回查事务状态

关键点

  • 半消息:消费者看不到
  • 事务回查:MQ主动查询本地事务状态
  • 保证本地事务和消息发送的一致性 ✅

Q5: 如何选择分布式事务方案?

A: 根据场景选择

场景推荐方案理由
金融支付TCC强一致性 ⭐⭐⭐
电商订单MQ事务消息最终一致性,性能好 ⭐⭐⭐
长流程业务Saga适合长事务 ⭐⭐
低并发业务2PC简单,强一致 ⭐
简单业务本地消息表实现简单 ⭐⭐

推荐:MQ事务消息(RocketMQ)⭐⭐⭐


Q6: 强一致性和最终一致性的区别?

A: 强一致性(CP)

  • 所有操作立即生效
  • 读到的都是最新数据
  • 例如:2PC、TCC

最终一致性(BASE)

  • 操作可能有延迟
  • 短时间内可能不一致
  • 最终会达到一致
  • 例如:MQ事务消息、本地消息表

选择

  • 金融交易:强一致性 ⭐
  • 互联网业务:最终一致性 ⭐⭐⭐

🎬 总结

        分布式事务解决方案

┌────────────────────────────────────────┐
│ 2PC:两阶段提交                        │
│ 强一致性,性能差 ⭐                    │
└────────────────────────────────────────┘

┌────────────────────────────────────────┐
│ TCC:Try-Confirm-Cancel               │
│ 强一致性,适合金融 ⭐⭐                │
└────────────────────────────────────────┘

┌────────────────────────────────────────┐
│ MQ事务消息(推荐)⭐⭐⭐                │
│ 最终一致性,性能好                     │
└────────────────────────────────────────┘

┌────────────────────────────────────────┐
│ 本地消息表                             │
│ 最终一致性,实现简单 ⭐⭐              │
└────────────────────────────────────────┘

┌────────────────────────────────────────┐
│ Saga:长事务拆分+补偿                  │
│ 最终一致性,适合长流程 ⭐⭐            │
└────────────────────────────────────────┘

      大部分场景用MQ事务消息!✅

🎉 恭喜你!

你已经完全掌握了分布式事务的6种解决方案!🎊

核心要点

  1. 2PC:强一致,性能差
  2. TCC:强一致,适合金融
  3. MQ事务消息:最终一致,推荐 ⭐⭐⭐
  4. 本地消息表:最终一致,简单
  5. Saga:长事务,补偿机制

下次面试,这样回答

"分布式事务有6种解决方案,其中最常用的是MQ事务消息。

MQ事务消息利用RocketMQ的半消息机制,先发送半消息到MQ,执行本地事务,成功则提交消息,失败则回滚消息。MQ还会定期回查事务状态,保证本地事务和消息发送的一致性。

对于金融等对一致性要求极高的场景,会使用TCC方案,分为Try(预留资源)、Confirm(确认提交)、Cancel(取消释放)三个阶段,实现强一致性。

我们项目的订单系统使用RocketMQ事务消息,订单创建成功后发送消息,库存服务消费消息扣减库存,实现最终一致性,性能和可靠性都很好。"

面试官:👍 "很好!你对分布式事务理解很全面!"


本文完 🎬

上一篇: 197-Zookeeper实现分布式锁的原理.md
下一篇: 199-Seata分布式事务框架.md

作者注:写完这篇,我都想去银行当转账员了!💰
如果这篇文章对你有帮助,请给我一个Star⭐!