41-分布式事务详解

分布式事务详解

一、知识概述

在分布式系统中,一个业务操作可能涉及多个数据库、多个服务,如何保证这些操作要么全部成功、要么全部失败,这就是分布式事务要解决的问题。分布式事务是分布式系统中最复杂的问题之一,需要在一致性、可用性、性能之间做出权衡。

本文将深入讲解分布式事务的理论基础、主流实现方案(2PC/3PC、TCC、Saga、本地消息表),以及各方案的适用场景和最佳实践。

二、事务基础

2.1 ACID 特性

java 复制代码
/**
 * 数据库事务 ACID 特性
 */
public class ACIDProperties {
    
    /**
     * A - Atomicity(原子性)
     * 事务中的所有操作要么全部完成,要么全部不完成
     */
    
    /**
     * C - Consistency(一致性)
     * 事务执行前后,数据库状态保持一致
     */
    
    /**
     * I - Isolation(隔离性)
     * 并发事务之间相互隔离
     */
    
    /**
     * D - Durability(持久性)
     * 事务完成后,数据永久保存
     */
}

/**
 * 本地事务示例
 */
@Service
public class LocalTransaction {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private InventoryMapper inventoryMapper;
    
    /**
     * 本地事务 - 单数据源
     */
    @Transactional(rollbackFor = Exception.class)
    public void placeOrder(Order order) {
        // 1. 创建订单
        orderMapper.insert(order);
        
        // 2. 扣减库存
        inventoryMapper.decreaseStock(order.getProductId(), order.getQuantity());
        
        // 如果任一步骤失败,整个事务回滚
    }
}

2.2 分布式事务问题

java 复制代码
/**
 * 分布式事务问题场景
 */
@Service
public class DistributedTransactionProblem {
    
    @Autowired
    private OrderService orderService;      // 订单服务
    
    @Autowired
    private InventoryService inventoryService;  // 库存服务
    
    @Autowired
    private PaymentService paymentService;  // 支付服务
    
    /**
     * 跨服务事务问题
     */
    public void placeOrder(Order order) {
        // 1. 创建订单(订单库)
        orderService.createOrder(order);
        
        // 2. 扣减库存(库存库)
        inventoryService.decreaseStock(order.getProductId(), order.getQuantity());
        
        // 3. 扣款支付(支付库)
        paymentService.pay(order.getUserId(), order.getAmount());
        
        // 问题:
        // - 步骤1成功,步骤2失败 → 订单创建了但库存未扣减
        // - 步骤1、2成功,步骤3失败 → 订单创建、库存扣减,但支付失败
        // - 如何保证三个操作要么全成功,要么全失败?
    }
    
    /**
     * 分布式事务挑战
     */
    // 1. 网络问题:网络延迟、超时、分区
    // 2. 节点故障:服务宕机、数据库崩溃
    // 3. 数据一致性:多个数据源状态不一致
    // 4. 性能问题:分布式事务开销大
}

三、2PC/3PC 协议

3.1 两阶段提交(2PC)

java 复制代码
/**
 * 两阶段提交协议(Two-Phase Commit)
 * 
 * 阶段一:准备阶段(Prepare)
 * 阶段二:提交阶段(Commit)
 */
public class TwoPhaseCommit {
    
    /**
     * 参与者角色
     */
    // Coordinator(协调者):事务协调者
    // Participant(参与者):执行实际操作的节点
    
    /**
     * 阶段一:准备阶段
     */
    /*
    Coordinator                    Participant1              Participant2
         │                              │                          │
         │───Prepare───────────────────►│                          │
         │                              │                          │
         │───Prepare────────────────────────────────────────────►│
         │                              │                          │
         │◄──Ready──────────────────────│                          │
         │                              │                          │
         │◄──Ready────────────────────────────────────────────────│
         │                              │                          │
    */
    
    /**
     * 阶段二:提交阶段
     */
    /*
    Coordinator                    Participant1              Participant2
         │                              │                          │
         │───Commit────────────────────►│                          │
         │                              │                          │
         │───Commit────────────────────────────────────────────►│
         │                              │                          │
         │◄──Ack───────────────────────│                          │
         │                              │                          │
         │◄──Ack─────────────────────────────────────────────────│
         │                              │                          │
    */
    
    /**
     * 2PC 实现
     */
    @Service
    public class TwoPhaseCommitService {
        
        @Autowired
        private List<TransactionParticipant> participants;
        
        /**
         * 执行分布式事务
         */
        public boolean execute(Transaction transaction) {
            // 阶段一:准备
            boolean allPrepared = preparePhase(transaction);
            
            if (allPrepared) {
                // 阶段二:提交
                return commitPhase(transaction);
            } else {
                // 回滚
                rollbackPhase(transaction);
                return false;
            }
        }
        
        /**
         * 准备阶段
         */
        private boolean preparePhase(Transaction transaction) {
            for (TransactionParticipant participant : participants) {
                try {
                    PrepareResult result = participant.prepare(transaction);
                    if (result != PrepareResult.READY) {
                        return false;
                    }
                } catch (Exception e) {
                    // 准备失败
                    return false;
                }
            }
            return true;
        }
        
        /**
         * 提交阶段
         */
        private boolean commitPhase(Transaction transaction) {
            boolean allCommitted = true;
            
            for (TransactionParticipant participant : participants) {
                try {
                    participant.commit(transaction);
                } catch (Exception e) {
                    allCommitted = false;
                    // 需要重试或人工介入
                }
            }
            
            return allCommitted;
        }
        
        /**
         * 回滚阶段
         */
        private void rollbackPhase(Transaction transaction) {
            for (TransactionParticipant participant : participants) {
                try {
                    participant.rollback(transaction);
                } catch (Exception e) {
                    // 记录日志,人工介入
                }
            }
        }
    }
    
    /**
     * 2PC 问题
     */
    // 1. 同步阻塞:所有参与者等待协调者
    // 2. 单点故障:协调者故障导致所有参与者阻塞
    // 3. 数据不一致:部分参与者收到Commit,部分没收到
    // 4. 事务日志:需要持久化事务状态
}

3.2 三阶段提交(3PC)

java 复制代码
/**
 * 三阶段提交协议(Three-Phase Commit)
 * 
 * 阶段一:CanCommit(询问)
 * 阶段二:PreCommit(预提交)
 * 阶段三:DoCommit(最终提交)
 */
public class ThreePhaseCommit {
    
    /**
     * 3PC 改进点
     */
    // 1. 引入超时机制:参与者等待超时后可自行决策
    // 2. 增加询问阶段:减少不必要的锁定
    // 3. 分割提交阶段:PreCommit + DoCommit
    
    /**
     * 阶段一:CanCommit
     */
    /*
    Coordinator                    Participant1              Participant2
         │                              │                          │
         │───CanCommit─────────────────►│                          │
         │                              │                          │
         │───CanCommit──────────────────────────────────────────►│
         │                              │                          │
         │◄──Yes───────────────────────│                          │
         │                              │                          │
         │◄──Yes─────────────────────────────────────────────────│
         │                              │                          │
    */
    
    /**
     * 阶段二:PreCommit
     */
    /*
    Coordinator                    Participant1              Participant2
         │                              │                          │
         │───PreCommit─────────────────►│                          │
         │                              │                          │
         │───PreCommit──────────────────────────────────────────►│
         │                              │                          │
         │◄──Ack───────────────────────│                          │
         │                              │                          │
         │◄──Ack─────────────────────────────────────────────────│
         │                              │                          │
    */
    
    /**
     * 阶段三:DoCommit
     */
    /*
    Coordinator                    Participant1              Participant2
         │                              │                          │
         │───DoCommit──────────────────►│                          │
         │                              │                          │
         │───DoCommit───────────────────────────────────────────►│
         │                              │                          │
         │◄──Ack───────────────────────│                          │
         │                              │                          │
         │◄──Ack─────────────────────────────────────────────────│
         │                              │                          │
    */
    
    /**
     * 3PC 服务实现
     */
    @Service
    public class ThreePhaseCommitService {
        
        private final List<TransactionParticipant> participants;
        private final int timeout = 5000;  // 超时时间
        
        public boolean execute(Transaction transaction) {
            // 阶段一:询问
            if (!canCommitPhase(transaction)) {
                return false;
            }
            
            // 阶段二:预提交
            if (!preCommitPhase(transaction)) {
                rollbackPhase(transaction);
                return false;
            }
            
            // 阶段三:提交
            return doCommitPhase(transaction);
        }
        
        private boolean canCommitPhase(Transaction transaction) {
            for (TransactionParticipant participant : participants) {
                try {
                    if (!participant.canCommit(transaction)) {
                        return false;
                    }
                } catch (Exception e) {
                    return false;
                }
            }
            return true;
        }
        
        private boolean preCommitPhase(Transaction transaction) {
            for (TransactionParticipant participant : participants) {
                try {
                    participant.preCommit(transaction);
                } catch (Exception e) {
                    return false;
                }
            }
            return true;
        }
        
        private boolean doCommitPhase(Transaction transaction) {
            // ...
            return true;
        }
    }
}

四、TCC 模式

4.1 TCC 原理

java 复制代码
/**
 * TCC(Try-Confirm-Cancel)模式
 * 
 * Try:尝试执行,预留资源
 * Confirm:确认执行,真正执行业务
 * Cancel:取消执行,释放预留资源
 */
public class TCCPattern {
    
    /**
     * TCC 流程
     */
    /*
    ┌─────────────────────────────────────────────────────────────────────┐
    │                         TCC 执行流程                                  │
    ├─────────────────────────────────────────────────────────────────────┤
    │                                                                     │
    │    ┌─────────┐                                                    │
    │    │   Try   │ ────── 预留资源,检查可行性                          │
    │    └────┬────┘                                                    │
    │         │                                                         │
    │    ┌────┴────┐                                                    │
    │    │         │                                                    │
    │    ▼         ▼                                                    │
    │ ┌──────┐  ┌──────┐                                                │
    │ │Confirm│  │Cancel│                                               │
    │ └──────┘  └──────┘                                                │
    │  成功提交   失败回滚                                                │
    │                                                                     │
    └─────────────────────────────────────────────────────────────────────┘
    */
    
    /**
     * TCC 接口定义
     */
    public interface TccTransactionService {
        
        /**
         * Try:预留资源
         */
        void tryPrepare(TransactionContext context);
        
        /**
         * Confirm:确认提交
         */
        void confirm(TransactionContext context);
        
        /**
         * Cancel:取消回滚
         */
        void cancel(TransactionContext context);
    }
}

4.2 TCC 实现

java 复制代码
/**
 * TCC 模式实现 - 转账场景
 */
@Service
public class TransferTccService {
    
    @Autowired
    private AccountTccService accountTccService;
    
    /**
     * 转账事务
     */
    public void transfer(String fromAccount, String toAccount, 
                         BigDecimal amount) {
        // 生成事务ID
        String transactionId = generateTransactionId();
        
        TransactionContext context = new TransactionContext();
        context.setTransactionId(transactionId);
        context.setFromAccount(fromAccount);
        context.setToAccount(toAccount);
        context.setAmount(amount);
        
        try {
            // 阶段一:Try
            accountTccService.tryTransfer(context);
            
            // 阶段二:Confirm
            accountTccService.confirmTransfer(context);
            
        } catch (Exception e) {
            // 阶段二:Cancel
            accountTccService.cancelTransfer(context);
            throw e;
        }
    }
}

/**
 * 账户 TCC 服务
 */
@Service
public class AccountTccService {
    
    @Autowired
    private AccountMapper accountMapper;
    
    @Autowired
    private FreezeRecordMapper freezeRecordMapper;
    
    /**
     * Try:冻结转出账户金额
     */
    @Transactional
    public void tryTransfer(TransactionContext context) {
        String fromAccount = context.getFromAccount();
        BigDecimal amount = context.getAmount();
        String txId = context.getTransactionId();
        
        // 1. 检查余额是否充足
        Account account = accountMapper.selectForUpdate(fromAccount);
        if (account.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }
        
        // 2. 冻结金额(预扣)
        accountMapper.freeze(fromAccount, amount);
        
        // 3. 记录冻结日志
        FreezeRecord record = new FreezeRecord();
        record.setTransactionId(txId);
        record.setAccount(fromAccount);
        record.setAmount(amount);
        record.setStatus(FreezeStatus.FREEZED);
        freezeRecordMapper.insert(record);
    }
    
    /**
     * Confirm:真正扣款和入账
     */
    @Transactional
    public void confirmTransfer(TransactionContext context) {
        String fromAccount = context.getFromAccount();
        String toAccount = context.getToAccount();
        BigDecimal amount = context.getAmount();
        String txId = context.getTransactionId();
        
        // 1. 扣减转出账户冻结金额
        accountMapper.deductFreeze(fromAccount, amount);
        
        // 2. 增加转入账户余额
        accountMapper.addBalance(toAccount, amount);
        
        // 3. 更新冻结记录状态
        freezeRecordMapper.updateStatus(txId, FreezeStatus.CONFIRMED);
    }
    
    /**
     * Cancel:解冻金额
     */
    @Transactional
    public void cancelTransfer(TransactionContext context) {
        String fromAccount = context.getFromAccount();
        BigDecimal amount = context.getAmount();
        String txId = context.getTransactionId();
        
        // 1. 解冻金额
        accountMapper.unfreeze(fromAccount, amount);
        
        // 2. 更新冻结记录状态
        freezeRecordMapper.updateStatus(txId, FreezeStatus.CANCELLED);
    }
}

/**
 * 数据库表设计
 */
/*
-- 账户表
CREATE TABLE `account` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `account_no` varchar(64) NOT NULL COMMENT '账户号',
    `balance` decimal(20,2) NOT NULL COMMENT '可用余额',
    `freeze_amount` decimal(20,2) NOT NULL DEFAULT 0 COMMENT '冻结金额',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_account_no` (`account_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 冻结记录表(用于幂等性和回滚)
CREATE TABLE `freeze_record` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `transaction_id` varchar(64) NOT NULL COMMENT '事务ID',
    `account_no` varchar(64) NOT NULL COMMENT '账户号',
    `amount` decimal(20,2) NOT NULL COMMENT '冻结金额',
    `status` tinyint(4) NOT NULL COMMENT '状态:1-冻结,2-已确认,3-已取消',
    `create_time` datetime NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*/

4.3 TCC 框架(Seata)

java 复制代码
/**
 * Seata TCC 模式使用
 */
@Service
public class SeataTccService {
    
    /**
     * 定义 TCC 接口
     */
    @LocalTCC
    public interface StockTccAction {
        
        /**
         * Try:预留库存
         */
        @TwoPhaseBusinessAction(
            name = "stockTccAction",
            commitMethod = "commit",
            rollbackMethod = "rollback"
        )
        boolean prepare(@BusinessActionContextParameter(paramName = "productId") 
                        Long productId,
                        @BusinessActionContextParameter(paramName = "quantity")
                        Integer quantity);
        
        /**
         * Confirm:确认扣减
         */
        boolean commit(BusinessActionContext context);
        
        /**
         * Cancel:回滚预留
         */
        boolean rollback(BusinessActionContext context);
    }
    
    /**
     * TCC 实现
     */
    @Service
    public class StockTccActionImpl implements StockTccAction {
        
        @Autowired
        private StockMapper stockMapper;
        
        @Autowired
        private StockFreezeMapper stockFreezeMapper;
        
        @Override
        public boolean prepare(Long productId, Integer quantity) {
            // 1. 检查库存
            Stock stock = stockMapper.selectForUpdate(productId);
            if (stock.getAvailable() < quantity) {
                throw new RuntimeException("库存不足");
            }
            
            // 2. 预留库存
            stockMapper.freeze(productId, quantity);
            
            // 3. 记录预留日志
            StockFreeze freeze = new StockFreeze();
            freeze.setProductId(productId);
            freeze.setQuantity(quantity);
            freeze.setStatus(FreezeStatus.FREEZED);
            stockFreezeMapper.insert(freeze);
            
            return true;
        }
        
        @Override
        public boolean commit(BusinessActionContext context) {
            Long productId = context.getActionContext("productId", Long.class);
            Integer quantity = context.getActionContext("quantity", Integer.class);
            
            // 真正扣减库存
            stockMapper.deductFreeze(productId, quantity);
            
            // 更新预留记录
            stockFreezeMapper.updateStatus(
                productId, quantity, FreezeStatus.CONFIRMED);
            
            return true;
        }
        
        @Override
        public boolean rollback(BusinessActionContext context) {
            Long productId = context.getActionContext("productId", Long.class);
            Integer quantity = context.getActionContext("quantity", Integer.class);
            
            // 释放预留库存
            stockMapper.releaseFreeze(productId, quantity);
            
            // 更新预留记录
            stockFreezeMapper.updateStatus(
                productId, quantity, FreezeStatus.CANCELLED);
            
            return true;
        }
    }
    
    /**
     * 使用 TCC
     */
    @Service
    public class OrderService {
        
        @Autowired
        private StockTccAction stockTccAction;
        
        @GlobalTransactional
        public void placeOrder(Order order) {
            // Try:预留库存
            stockTccAction.prepare(order.getProductId(), order.getQuantity());
            
            // 其他业务操作...
            
            // 如果成功,Seata 自动调用 commit
            // 如果失败,Seata 自动调用 rollback
        }
    }
}

五、Saga 模式

5.1 Saga 原理

java 复制代码
/**
 * Saga 模式
 * 
 * 将长事务拆分为多个本地短事务
 * 每个短事务都有对应的补偿事务
 */
public class SagaPattern {
    
    /**
     * Saga 执行流程
     */
    /*
    ┌─────────────────────────────────────────────────────────────────────┐
    │                         Saga 执行流程                                 │
    ├─────────────────────────────────────────────────────────────────────┤
    │                                                                     │
    │  正向流程(成功):                                                   │
    │  T1 ──► T2 ──► T3 ──► Tn                                            │
    │                                                                     │
    │  补偿流程(失败):                                                   │
    │  T1 ──► T2 ──► T3(失败)                                             │
    │                    │                                                │
    │                    ▼                                                │
    │              C2 ◄──┘  C1                                            │
    │              (补偿T2) (补偿T1)                                       │
    │                                                                     │
    └─────────────────────────────────────────────────────────────────────┘
    */
    
    /**
     * Saga 协调方式
     */
    // 1. 编排式(Choreography):事件驱动,无中央协调器
    // 2. 协调式(Orchestration):中央协调器控制流程
}

5.2 Saga 实现

java 复制代码
/**
 * Saga 模式实现 - 订单创建场景
 */
@Service
public class OrderSagaOrchestrator {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private SagaLogService sagaLogService;
    
    /**
     * 执行订单创建 Saga
     */
    public void createOrder(Order order) {
        String sagaId = generateSagaId();
        
        // 记录 Saga 开始
        sagaLogService.startSaga(sagaId);
        
        try {
            // 步骤1:创建订单
            executeStep(sagaId, "create-order", () -> {
                orderService.createOrder(order);
            }, () -> {
                orderService.cancelOrder(order.getId());
            });
            
            // 步骤2:扣减库存
            executeStep(sagaId, "deduct-inventory", () -> {
                inventoryService.deduct(order.getProductId(), order.getQuantity());
            }, () -> {
                inventoryService.restore(order.getProductId(), order.getQuantity());
            });
            
            // 步骤3:扣款
            executeStep(sagaId, "payment", () -> {
                paymentService.pay(order.getUserId(), order.getAmount());
            }, () -> {
                paymentService.refund(order.getUserId(), order.getAmount());
            });
            
            // Saga 完成
            sagaLogService.completeSaga(sagaId);
            
        } catch (Exception e) {
            // Saga 失败,执行补偿
            compensate(sagaId);
            throw e;
        }
    }
    
    /**
     * 执行单个步骤
     */
    private void executeStep(String sagaId, String stepName,
                             Runnable action, Runnable compensation) {
        // 检查是否已执行(幂等性)
        if (sagaLogService.isStepCompleted(sagaId, stepName)) {
            return;
        }
        
        try {
            // 执行正向操作
            action.run();
            
            // 记录步骤完成
            sagaLogService.completeStep(sagaId, stepName, compensation);
            
        } catch (Exception e) {
            // 记录步骤失败
            sagaLogService.failStep(sagaId, stepName, e.getMessage());
            throw e;
        }
    }
    
    /**
     * 执行补偿
     */
    private void compensate(String sagaId) {
        // 获取已完成的步骤(逆序)
        List<SagaStep> completedSteps = 
            sagaLogService.getCompletedStepsReverse(sagaId);
        
        for (SagaStep step : completedSteps) {
            try {
                // 执行补偿操作
                step.getCompensation().run();
                
                // 记录补偿完成
                sagaLogService.compensateStep(sagaId, step.getName());
                
            } catch (Exception e) {
                // 补偿失败,记录日志,需要人工介入
                sagaLogService.compensateFailed(
                    sagaId, step.getName(), e.getMessage());
            }
        }
    }
}

/**
 * Saga 日志服务
 */
@Service
public class SagaLogService {
    
    @Autowired
    private SagaLogMapper sagaLogMapper;
    
    public void startSaga(String sagaId) {
        SagaLog log = new SagaLog();
        log.setSagaId(sagaId);
        log.setStatus(SagaStatus.RUNNING);
        log.setCreateTime(new Date());
        sagaLogMapper.insert(log);
    }
    
    public void completeStep(String sagaId, String stepName, 
                             Runnable compensation) {
        SagaStepLog stepLog = new SagaStepLog();
        stepLog.setSagaId(sagaId);
        stepLog.setStepName(stepName);
        stepLog.setStatus(StepStatus.COMPLETED);
        stepLog.setCompensation(serializeCompensation(compensation));
        sagaLogMapper.insertStep(stepLog);
    }
    
    public List<SagaStep> getCompletedStepsReverse(String sagaId) {
        return sagaLogMapper.selectCompletedStepsReverse(sagaId);
    }
}

/**
 * Saga 日志表设计
 */
/*
-- Saga 日志表
CREATE TABLE `saga_log` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `saga_id` varchar(64) NOT NULL COMMENT 'Saga ID',
    `status` tinyint(4) NOT NULL COMMENT '状态:1-运行中,2-已完成,3-已补偿',
    `create_time` datetime NOT NULL,
    `update_time` datetime NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_saga_id` (`saga_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Saga 步骤日志表
CREATE TABLE `saga_step_log` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `saga_id` varchar(64) NOT NULL,
    `step_name` varchar(64) NOT NULL COMMENT '步骤名称',
    `status` tinyint(4) NOT NULL COMMENT '状态:1-待执行,2-已完成,3-已补偿',
    `compensation` text COMMENT '补偿信息',
    `create_time` datetime NOT NULL,
    PRIMARY KEY (`id`),
    KEY `idx_saga_id` (`saga_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*/

六、本地消息表

6.1 原理

java 复制代码
/**
 * 本地消息表方案
 * 
 * 原理:
 * 1. 业务操作和消息记录在同一个本地事务中
 * 2. 定时任务扫描消息表,发送消息
 * 3. 消费者消费消息,执行业务逻辑
 * 4. 通过消息确认机制保证可靠性
 */
public class LocalMessageTablePattern {
    
    /*
    ┌─────────────────────────────────────────────────────────────────────┐
    │                     本地消息表执行流程                                 │
    ├─────────────────────────────────────────────────────────────────────┤
    │                                                                     │
    │  生产者服务:                                                         │
    │  ┌─────────┐      ┌─────────┐      ┌─────────┐                     │
    │  │ 业务操作 │ ───► │ 写入消息 │ ───► │ 本地事务 │                     │
    │  └─────────┘      │ 表      │      │ 提交    │                     │
    │                   └─────────┘      └─────────┘                     │
    │                         │                                          │
    │                         ▼                                          │
    │                   ┌─────────┐                                      │
    │                   │ 定时任务 │                                      │
    │                   │ 扫描发送│                                      │
    │                   └────┬────┘                                      │
    │                        │                                           │
    └────────────────────────┼───────────────────────────────────────────┘
                             │
                             ▼
    ┌─────────────────────────────────────────────────────────────────────┐
    │                         消息队列                                      │
    └─────────────────────────────────────────────────────────────────────┘
                             │
                             ▼
    ┌─────────────────────────────────────────────────────────────────────┐
    │  消费者服务:                                                         │
    │  ┌─────────┐      ┌─────────┐      ┌─────────┐                     │
    │  │ 消费消息 │ ───► │ 业务处理 │ ───► │ 确认消息 │                     │
    │  └─────────┘      └─────────┘      └─────────┘                     │
    │                                                                     │
    └─────────────────────────────────────────────────────────────────────┘
    */
}

6.2 实现

java 复制代码
/**
 * 本地消息表实现
 */
@Service
public class LocalMessageTableService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private LocalMessageMapper localMessageMapper;
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    /**
     * 本地消息表
     */
    /*
    CREATE TABLE `local_message` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `message_id` varchar(64) NOT NULL COMMENT '消息唯一ID',
        `message_type` varchar(64) NOT NULL COMMENT '消息类型',
        `content` text NOT NULL COMMENT '消息内容',
        `status` tinyint(4) NOT NULL COMMENT '状态:0-待发送,1-已发送,2-发送失败',
        `retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '重试次数',
        `create_time` datetime NOT NULL,
        `update_time` datetime NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uk_message_id` (`message_id`),
        KEY `idx_status` (`status`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    */
    
    /**
     * 创建订单(带本地消息)
     */
    @Transactional
    public void createOrder(Order order) {
        // 1. 创建订单
        orderMapper.insert(order);
        
        // 2. 写入本地消息(同一事务)
        LocalMessage message = new LocalMessage();
        message.setMessageId(UUID.randomUUID().toString());
        message.setMessageType("ORDER_CREATED");
        message.setContent(JSON.toJSONString(order));
        message.setStatus(MessageStatus.PENDING);
        message.setRetryCount(0);
        
        localMessageMapper.insert(message);
    }
    
    /**
     * 定时任务:发送消息
     */
    @Scheduled(fixedDelay = 5000)
    public void sendPendingMessages() {
        // 查询待发送消息
        List<LocalMessage> messages = localMessageMapper
            .selectByStatus(MessageStatus.PENDING, 100);
        
        for (LocalMessage message : messages) {
            try {
                // 发送到 MQ
                SendResult result = rocketMQTemplate.syncSend(
                    "order-topic", message.getContent());
                
                if (result.getSendStatus() == SendStatus.SEND_OK) {
                    // 更新状态为已发送
                    localMessageMapper.updateStatus(
                        message.getMessageId(), MessageStatus.SENT);
                }
            } catch (Exception e) {
                // 发送失败,增加重试次数
                localMessageMapper.incrementRetryCount(
                    message.getMessageId());
                
                // 重试次数超过阈值
                if (message.getRetryCount() >= 5) {
                    localMessageMapper.updateStatus(
                        message.getMessageId(), MessageStatus.FAILED);
                }
            }
        }
    }
    
    /**
     * 消费者:处理订单消息
     */
    @Service
    @RocketMQMessageListener(topic = "order-topic", 
                              consumerGroup = "order-consumer")
    public class OrderMessageConsumer implements RocketMQListener<String> {
        
        @Autowired
        private InventoryService inventoryService;
        
        @Override
        public void onMessage(String message) {
            Order order = JSON.parseObject(message, Order.class);
            
            // 检查是否已处理(幂等性)
            if (isProcessed(order.getId())) {
                return;
            }
            
            try {
                // 处理业务逻辑
                inventoryService.deduct(
                    order.getProductId(), order.getQuantity());
                
                // 标记为已处理
                markProcessed(order.getId());
                
            } catch (Exception e) {
                // 处理失败,抛出异常,MQ 会重试
                throw e;
            }
        }
    }
}

七、方案对比与选型

7.1 方案对比

java 复制代码
/**
 * 分布式事务方案对比
 */
public class TransactionSchemesComparison {
    
    /*
    ┌─────────────────────────────────────────────────────────────────────────────────┐
    │                          分布式事务方案对比                                        │
    ├──────────────┬──────────┬──────────┬──────────┬──────────┬─────────────────────┤
    │     方案     │ 一致性   │  性能    │  复杂度  │  适用性  │      适用场景       │
    ├──────────────┼──────────┼──────────┼──────────┼──────────┼─────────────────────┤
    │ 2PC/XA       │   强     │   低     │   低     │   低     │ 单数据库、低并发    │
    │ TCC          │   最终   │   高     │   高     │   中     │ 高并发、短事务      │
    │ Saga         │   最终   │   中     │   中     │   高     │ 长事务、复杂流程    │
    │ 本地消息表   │   最终   │   高     │   中     │   高     │ 高可靠、可异步      │
    │ 事务消息     │   最终   │   高     │   低     │   高     │ 异步场景            │
    └──────────────┴──────────┴──────────┴──────────┴──────────┴─────────────────────┘
    */
}

7.2 选型建议

java 复制代码
/**
 * 分布式事务选型建议
 */
public class TransactionSelectionGuide {
    
    /**
     * 选型决策
     */
    public String selectScheme(Requirement req) {
        
        // 1. 强一致性需求
        if (req.needStrongConsistency()) {
            if (req.getConcurrency() < 1000) {
                return "2PC/XA";
            } else {
                // 高并发强一致性,需要从架构层面优化
                return "单库优化 + 缓存";
            }
        }
        
        // 2. 最终一致性需求
        if (req.needEventualConsistency()) {
            if (req.isAsync()) {
                // 异步场景
                return "本地消息表 或 事务消息";
            } else {
                // 同步场景
                if (req.getTransactionDuration() < 3) {
                    // 短事务
                    return "TCC";
                } else {
                    // 长事务
                    return "Saga";
                }
            }
        }
        
        // 3. 默认推荐
        return "本地消息表";
    }
    
    /**
     * 各方案最佳实践
     */
    // 2PC/XA:
    // - 使用场景较少,性能瓶颈明显
    // - 尽量在单库内完成事务
    
    // TCC:
    // - 需要业务代码支持
    // - 做好幂等性设计
    // - 处理好悬挂、空回滚问题
    
    // Saga:
    // - 适合长事务
    // - 补偿逻辑要完整
    // - 记录好事务日志
    
    // 本地消息表:
    // - 实现简单
    // - 可靠性高
    // - 适合大多数场景
}

八、总结

8.1 核心要点

vbnet 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                        分布式事务核心要点                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. CAP 权衡                                                            │
│     - 强一致性 vs 最终一致性                                             │
│     - 根据业务场景选择                                                   │
│                                                                         │
│  2. 方案选型                                                            │
│     - 2PC/XA:强一致、低并发                                             │
│     - TCC:高并发、短事务                                                │
│     - Saga:长事务、复杂流程                                             │
│     - 本地消息表:高可靠、异步场景                                        │
│                                                                         │
│  3. 关键问题                                                            │
│     - 幂等性:重复执行结果一致                                           │
│     - 补偿机制:失败后的回滚处理                                         │
│     - 悬挂问题:Try、Cancel 顺序颠倒                                     │
│     - 空回滚:Try 未执行就执行 Cancel                                    │
│                                                                         │
│  4. 最佳实践                                                            │
│     - 尽量避免分布式事务                                                 │
│     - 优先使用最终一致性                                                 │
│     - 做好监控和告警                                                     │
│     - 设计好补偿机制                                                     │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

六、思考与练习

思考题

  1. 基础题:为什么说2PC协议在分布式环境下存在"同步阻塞"和"单点故障"问题?3PC协议如何改进这些问题?

  2. 进阶题:TCC模式中的"悬挂问题"和"空回滚问题"是什么?如何通过业务设计和技术手段避免这些问题?

  3. 实战题:分析一个电商订单系统(下单→扣库存→扣款),如果选择TCC方案,每个服务的Try、Confirm、Cancel应该如何设计?如果选择Saga方案,补偿流程是什么?

编程练习

练习:实现一个基于本地消息表的分布式事务框架,包含:(1) 消息记录表的完整设计;(2) 定时任务扫描与消息发送;(3) 消费者的幂等处理;(4) 消息对账与补偿机制。

章节关联

  • 前置章节:分布式锁实现详解、CAP与BASE理论详解
  • 后续章节:RabbitMQ核心原理与实战(消息队列系列)
  • 扩展阅读:Seata官方文档、 Patterns for Distributed Transactions - Martin Fowler

📝 下一章预告

分布式事务的实现离不开消息队列的支持。从下一章开始,我们将进入消息队列专题,首先讲解RabbitMQ的核心原理与实战应用,包括消息确认、死信队列、延迟队列等核心特性。


本章完


参考资料:

  • Seata 官方文档:seata.io/
  • 《分布式系统原理》
  • "Patterns for Distributed Transactions" - Martin Fowler
相关推荐
IT_陈寒2 小时前
Java集合的这个坑,我调试了整整3小时才爬出来
前端·人工智能·后端
落木萧萧8252 小时前
MyBatisGX 示例工程:CRUD + 关联查询完整演示
java·后端
fliter2 小时前
一个徽章坏了,顺带扯出了 2.3 万个 feature
后端·架构
tumeng07112 小时前
Spring详解
java·后端·spring
jwt7939279372 小时前
Spring之DataSource配置
java·后端·spring
黑牛儿2 小时前
Swoole协程 vs Go协程:PHP开发者一看就懂的实战对比
后端·golang·php·swoole
Rust研习社2 小时前
深入理解 Rust 裸指针:内存操作的双刃剑
开发语言·后端·rust
j_xxx404_3 小时前
深入理解Linux底层存储:从物理磁盘架构到文件系统(inode/Block)原理
linux·运维·服务器·后端
小江的记录本3 小时前
【网络安全】《网络安全与数据安全核心知识体系》(包括数据脱敏、数据加密、隐私合规、等保2.0)
java·网络·后端·python·算法·安全·web安全