问题
分布式事务的解决方案有哪些?
答案
1. 核心概念
分布式事务解决方案可以分为两大类:
- 刚性事务:追求强一致性,遵循ACID原则(2PC、3PC、XA)
- 柔性事务:追求最终一致性,遵循BASE理论(TCC、Saga、消息方案)
2. 刚性事务方案
方案1:2PC(两阶段提交)
原理:
协调者(TC) 参与者1 参与者2
| | |
|---准备(Prepare)---->| |
|---准备(Prepare)--------------------> |
| | |
|<---准备成功---------| |
|<---准备成功-----------------------------|
| | |
|---提交(Commit)----->| |
|---提交(Commit)---------------------> |
| | |
|<---提交成功---------| |
|<---提交成功-----------------------------|
阶段说明:
- 阶段1(准备阶段):协调者询问所有参与者是否可以执行事务,参与者执行事务但不提交
- 阶段2(提交阶段):如果所有参与者都准备成功,协调者发送提交命令;否则发送回滚命令
实现示例:
// 协调者
public class TwoPhaseCommitCoordinator {
private List<Participant> participants;
public boolean executeTransaction() {
// 阶段1:准备阶段
List<Boolean> prepareResults = new ArrayList<>();
for (Participant participant : participants) {
boolean result = participant.prepare();
prepareResults.add(result);
}
// 阶段2:提交或回滚
boolean allSuccess = prepareResults.stream().allMatch(r -> r);
if (allSuccess) {
// 所有参与者准备成功,提交
for (Participant participant : participants) {
participant.commit();
}
return true;
} else {
// 有参与者准备失败,回滚
for (Participant participant : participants) {
participant.rollback();
}
return false;
}
}
}
优点:
- 强一致性保证
- 实现相对简单
缺点:
- 同步阻塞:参与者在准备阶段会锁定资源
- 单点故障:协调者故障导致参与者阻塞
- 数据不一致:网络分区可能导致部分提交部分回滚
适用场景:对一致性要求极高、并发量不大的场景(如银行转账)
方案2:3PC(三阶段提交)
原理:在2PC基础上增加超时机制和CanCommit阶段。
阶段1:CanCommit(询问)
协调者询问参与者是否可以执行事务(不执行)
阶段2:PreCommit(预提交)
参与者执行事务但不提交
阶段3:DoCommit(提交)
参与者提交事务
改进点:
- 引入超时机制:参与者超时后自动提交
- CanCommit阶段:减少阻塞时间
优点:
- 降低阻塞时间
- 降低单点故障影响
缺点:
- 复杂度更高
- 仍无法完全解决数据不一致问题
- 性能较差
适用场景:实际生产环境很少使用,理论意义大于实际意义
方案3:XA协议
原理:基于2PC的分布式事务规范,由数据库厂商实现。
// 使用JTA实现XA事务
@Transactional
public void xaTransaction() throws Exception {
// 创建事务管理器
UserTransaction userTransaction =
(UserTransaction) new InitialContext().lookup("java:comp/UserTransaction");
try {
userTransaction.begin();
// 操作数据库1
DataSource ds1 = getDataSource1();
Connection conn1 = ds1.getConnection();
// ... 执行SQL
// 操作数据库2
DataSource ds2 = getDataSource2();
Connection conn2 = ds2.getConnection();
// ... 执行SQL
userTransaction.commit();
} catch (Exception e) {
userTransaction.rollback();
throw e;
}
}
优点:
- 标准化,数据库原生支持
- 强一致性
缺点:
- 性能差:锁定资源时间长
- 只支持关系型数据库
- 对业务侵入性强
3. 柔性事务方案
方案4:TCC(Try-Confirm-Cancel)
原理:业务层面的两阶段提交。
阶段1:Try(尝试)
- 完成业务检查
- 预留必要的业务资源
阶段2:Confirm/Cancel
- Confirm: 确认执行业务操作
- Cancel: 释放预留的业务资源
实现示例:
// 账户服务TCC实现
public class AccountServiceTCC {
// Try阶段:冻结金额
@Transactional
public boolean tryDeduct(String accountId, BigDecimal amount) {
Account account = accountDao.getById(accountId);
if (account.getBalance().compareTo(amount) < 0) {
return false; // 余额不足
}
// 冻结金额
account.setBalance(account.getBalance().subtract(amount));
account.setFrozenAmount(account.getFrozenAmount().add(amount));
accountDao.update(account);
return true;
}
// Confirm阶段:扣减冻结金额
@Transactional
public void confirmDeduct(String accountId, BigDecimal amount) {
Account account = accountDao.getById(accountId);
account.setFrozenAmount(account.getFrozenAmount().subtract(amount));
accountDao.update(account);
}
// Cancel阶段:解冻金额
@Transactional
public void cancelDeduct(String accountId, BigDecimal amount) {
Account account = accountDao.getById(accountId);
account.setBalance(account.getBalance().add(amount));
account.setFrozenAmount(account.getFrozenAmount().subtract(amount));
accountDao.update(account);
}
}
优点:
- 性能较好:不长时间锁定资源
- 数据一致性好
缺点:
- 实现复杂:需要实现Try、Confirm、Cancel三个方法
- 对业务侵入性强
- 需要考虑幂等性
适用场景:对一致性和性能都有要求的场景(如电商下单)
方案5:Saga模式
原理:长事务拆分为多个本地短事务,每个本地事务都有对应的补偿操作。
正向流程:T1 -> T2 -> T3 -> T4
补偿流程:C4 -> C3 -> C2 -> C1
实现方式:
方式1:协同式Saga(事件驱动)
// 订单服务
public void createOrder(Order order) {
// 1. 创建订单
orderDao.insert(order);
// 2. 发布OrderCreated事件
eventBus.publish(new OrderCreatedEvent(order));
}
// 库存服务监听事件
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
try {
// 扣减库存
inventoryService.decrease(event.getOrder());
// 发布InventoryDecreased事件
eventBus.publish(new InventoryDecreasedEvent(event.getOrder()));
} catch (Exception e) {
// 发布InventoryDecreaseFailed事件
eventBus.publish(new InventoryDecreaseFailedEvent(event.getOrder()));
}
}
// 订单服务监听失败事件
@EventListener
public void onInventoryDecreaseFailed(InventoryDecreaseFailedEvent event) {
// 补偿:取消订单
orderService.cancelOrder(event.getOrder().getId());
}
方式2:编排式Saga(中央协调器)
// Saga编排器
public class OrderSagaOrchestrator {
public void executeOrderSaga(Order order) {
try {
// 步骤1:创建订单
orderService.createOrder(order);
// 步骤2:扣减库存
inventoryService.decreaseStock(order);
// 步骤3:扣减余额
accountService.deductBalance(order);
// 步骤4:发货
shippingService.ship(order);
} catch (Exception e) {
// 执行补偿流程
compensate(order, e);
}
}
private void compensate(Order order, Exception cause) {
// 倒序执行补偿
try {
shippingService.cancelShipping(order);
} catch (Exception e) {}
try {
accountService.refund(order);
} catch (Exception e) {}
try {
inventoryService.restoreStock(order);
} catch (Exception e) {}
try {
orderService.cancelOrder(order);
} catch (Exception e) {}
}
}
优点:
- 实现相对简单
- 不需要锁定资源
- 适合长流程
缺点:
- 无法保证隔离性
- 需要实现补偿逻辑
- 补偿可能失败
适用场景:长流程、跨多个服务的业务(如旅游订单:机票+酒店+门票)
方案6:本地消息表
原理:利用本地事务保证业务操作和消息发送的原子性。
@Transactional
public void createOrder(Order order) {
// 1. 创建订单(业务操作)
orderDao.insert(order);
// 2. 插入本地消息表(同一个本地事务)
LocalMessage message = new LocalMessage();
message.setContent(JSON.toJSONString(order));
message.setStatus(MessageStatus.PENDING);
messageDao.insert(message);
// 本地事务保证以上两步的原子性
}
// 定时任务扫描消息表,发送待发送的消息
@Scheduled(fixedDelay = 1000)
public void sendPendingMessages() {
List<LocalMessage> messages = messageDao.getPendingMessages();
for (LocalMessage message : messages) {
try {
// 发送消息到MQ
mqProducer.send(message.getContent());
// 更新消息状态为已发送
message.setStatus(MessageStatus.SENT);
messageDao.update(message);
} catch (Exception e) {
// 发送失败,下次继续尝试
}
}
}
优点:
- 实现简单
- 利用本地事务保证可靠性
- 最终一致性保证
缺点:
- 与业务耦合
- 需要额外的消息表
- 消息可能重复(需要下游幂等)
适用场景:对一致性要求不太严格、可接受最终一致的场景
方案7:事务消息(RocketMQ)
原理:RocketMQ提供的事务消息机制。
public class OrderService {
@Autowired
private TransactionMQProducer producer;
public void createOrder(Order order) {
// 发送事务消息
producer.sendMessageInTransaction(
new Message("order-topic", JSON.toJSONString(order).getBytes()),
order
);
}
// 事务监听器
@Component
class OrderTransactionListener implements TransactionListener {
// 执行本地事务
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
Order order = (Order) arg;
// 执行本地事务
orderDao.insert(order);
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
// 回查本地事务状态
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
// 查询本地事务是否执行成功
String orderId = // 从消息中解析orderId
Order order = orderDao.getById(orderId);
if (order != null) {
return LocalTransactionState.COMMIT_MESSAGE;
}
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
}
优点:
- RocketMQ原生支持
- 实现相对简单
- 可靠性高
缺点:
- 依赖RocketMQ
- 只能保证最终一致性
适用场景:使用RocketMQ的系统,需要保证消息和本地事务一致性
方案8:最大努力通知
原理:主动通知+定期校对。
public class OrderNotificationService {
// 创建订单后通知下游
public void notifyDownstream(Order order) {
// 尝试通知(最多重试N次)
int maxRetry = 3;
for (int i = 0; i < maxRetry; i++) {
try {
restTemplate.postForObject(
"http://downstream/api/order",
order,
String.class
);
return; // 成功则返回
} catch (Exception e) {
// 失败则重试
sleep(1000 * (i + 1)); // 递增延迟
}
}
// 记录失败日志,人工介入或定期对账
log.error("Failed to notify downstream after {} retries", maxRetry);
}
// 定期对账
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void reconcile() {
// 查询本地订单
List<Order> localOrders = orderDao.getOrdersOfYesterday();
// 查询下游订单
List<Order> downstreamOrders = restTemplate.getForObject(
"http://downstream/api/orders/yesterday",
List.class
);
// 对比差异,补发通知
// ...
}
}
优点:
- 实现简单
- 对下游系统要求低
缺点:
- 可靠性最低
- 需要定期对账
适用场景:对一致性要求不高的场景(如通知类消息)
4. 分布式事务框架
Seata(阿里开源)
支持模式:
- AT模式:自动补偿的2PC(类似XA,但无锁)
- TCC模式:手动补偿
- Saga模式:状态机模式
- XA模式:传统XA协议
AT模式示例:
// 无需修改业务代码,只需加注解
@GlobalTransactional
public void createOrder(Order order) {
orderService.create(order); // 订单服务
inventoryService.decrease(order); // 库存服务
accountService.deduct(order); // 账户服务
}
5. 方案对比与选型
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC/XA | 强一致 | 低 | 中 | 金融、对一致性要求极高 |
| TCC | 最终一致 | 高 | 高 | 电商、对性能和一致性都有要求 |
| Saga | 最终一致 | 高 | 中 | 长流程、跨多服务 |
| 本地消息表 | 最终一致 | 高 | 低 | 简单场景、可接受最终一致 |
| 事务消息 | 最终一致 | 高 | 低 | 使用RocketMQ的系统 |
| 最大努力通知 | 弱一致 | 高 | 低 | 通知类场景 |
6. 总结
选型建议:
- 金融支付:2PC/XA(强一致性)
- 电商下单:TCC或Seata AT模式(性能+一致性)
- 长流程:Saga模式
- 简单场景:本地消息表或事务消息
- 通知类:最大努力通知
最佳实践:
- 优先考虑业务层面避免分布式事务
- 根据业务特性选择合适的一致性级别
- 刚性事务优先选Seata AT,柔性事务优先选事务消息
- 注意幂等性和补偿逻辑的设计