分布式事务详解
一、知识概述
在分布式系统中,一个业务操作可能涉及多个数据库、多个服务,如何保证这些操作要么全部成功、要么全部失败,这就是分布式事务要解决的问题。分布式事务是分布式系统中最复杂的问题之一,需要在一致性、可用性、性能之间做出权衡。
本文将深入讲解分布式事务的理论基础、主流实现方案(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. 最佳实践 │
│ - 尽量避免分布式事务 │
│ - 优先使用最终一致性 │
│ - 做好监控和告警 │
│ - 设计好补偿机制 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
六、思考与练习
思考题
-
基础题:为什么说2PC协议在分布式环境下存在"同步阻塞"和"单点故障"问题?3PC协议如何改进这些问题?
-
进阶题:TCC模式中的"悬挂问题"和"空回滚问题"是什么?如何通过业务设计和技术手段避免这些问题?
-
实战题:分析一个电商订单系统(下单→扣库存→扣款),如果选择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