前一篇文章介绍了分布式事务2pc,本文先来作了一个简单对比,让大家有个整体感知。
一、2PC(两阶段提交协议)
核心思想:通过协调者统一管理所有参与者的提交或回滚操作,分为两个阶段:
- 准备阶段:协调者询问所有参与者是否可以提交。
- 提交阶段:根据参与者的反馈,协调者发送全局提交或回滚命令。
特点:
● 强一致性:所有参与者要么全部提交,要么全部回滚。
● 同步阻塞:参与者在准备阶段会锁定资源,直到协调者发出最终指令。
● 单点故障:协调者故障可能导致事务阻塞。
二、TCC(Try-Confirm-Cancel)
核心思想:通过业务逻辑补偿实现最终一致性,分为三个阶段:
- Try:预留资源,检查业务约束。
- Confirm:确认执行业务操作。
- Cancel:取消操作并释放预留资源。
特点:特点:
● 最终一致性:通过补偿操作保证最终一致性。
● 业务侵入性:需要显式实现补偿逻辑。
● 高并发:资源在 Try 阶段只是预留,而非锁定。
代码示例(电商场景):
// 订单服务
class OrderService {
@Try
public void tryCreateOrder(Order order) {
order.setStatus("TRY");
orderDao.save(order); // 创建预订单(未生效)
}
@Confirm
public void confirmOrder(Long orderId) {
orderDao.updateStatus(orderId, "CONFIRMED"); // 确认订单生效
}
@Cancel
public void cancelOrder(Long orderId) {
orderDao.delete(orderId); // 删除预订单
}
}
// 库存服务
class InventoryService {
@Try
public void tryDeductStock(Long productId, int count) {
// 预扣减库存(实际库存不变,冻结部分库存)
inventoryDao.freeze(productId, count);
}
@Confirm
public void confirmDeductStock(Long productId, int count) {
// 实际扣减库存
inventoryDao.deduct(productId, count);
inventoryDao.unfreeze(productId, count);
}
@Cancel
public void cancelDeductStock(Long productId, int count) {
// 释放冻结的库存
inventoryDao.unfreeze(productId, count);
}
}
三、TCC 与 2PC 对比
四、关键差异说明
- 资源管理方式:
○ 2PC 在准备阶段锁定资源,可能导致死锁。
○ TCC 通过 Try 阶段预留资源(如冻结库存),Confirm 阶段实际提交。
○ 异常处理:
○ 2PC 依赖数据库回滚,无法处理网络分区等复杂故障。
○ TCC 要求业务实现幂等性(例如重复调用 Cancel 不会导致错误):
@Cancel
public void cancelOrder(Long orderId) {
if (orderDao.exists(orderId)) { // 幂等检查
orderDao.delete(orderId);
}
}
○ 业务灵活性:
○ TCC 允许自定义补偿逻辑(例如订单取消时发送通知):
@Cancel
public void cancelOrder(Long orderId) {
orderDao.delete(orderId);
notificationService.sendCancelMsg(orderId); // 附加业务逻辑
}
五、选型建议
- 使用 2PC 的场景:
○ 简单的跨数据库操作(例如银行转账)。
○ 事务执行时间短,对一致性要求极高。
○ 基础设施支持 XA 协议(如 MySQL XA)。 - 使用 TCC 的场景:
○ 长事务(如电商下单涉及库存、优惠券、物流)。
○ 高并发场景(如秒杀活动)。
○ 需要自定义补偿逻辑(如订单取消后恢复积分)。
六、完整的电商TCC代码示例
1. 服务定义
// === 订单服务 ===
@Service
public class OrderService {
@Transactional
public boolean tryCreateOrder(Long txId, Order order) {
// 幂等检查:防止重复Try
if (orderDao.existsByTxId(txId)) {
return true;
}
order.setStatus("TRY");
order.setTxId(txId); // 绑定全局事务ID
orderDao.save(order);
return true;
}
@Transactional
public boolean confirmOrder(Long txId) {
Order order = orderDao.findByTxId(txId);
if (order.getStatus().equals("CONFIRMED")) {
return true; // 幂等处理
}
order.setStatus("CONFIRMED");
orderDao.save(order);
return true;
}
@Transactional
public boolean cancelOrder(Long txId) {
Order order = orderDao.findByTxId(txId);
if (order == null) return true; // 可能Try未执行
orderDao.delete(order);
return true;
}
}
// === 库存服务 ===
@Service
public class InventoryService {
@Transactional
public boolean tryDeductStock(Long txId, Long productId, int count) {
// 幂等检查
if (inventoryLogDao.existsByTxId(txId)) {
return true;
}
// 冻结库存(实际库存不变)
inventoryDao.freezeStock(productId, count);
inventoryLogDao.save(new InventoryLog(txId, productId, count));
return true;
}
@Transactional
public boolean confirmDeductStock(Long txId) {
InventoryLog log = inventoryLogDao.findByTxId(txId);
if (log == null) return false;
if (log.getStatus().equals("CONFIRMED")) return true; // 幂等
// 实际扣减库存并解冻
inventoryDao.deductStock(log.getProductId(), log.getCount());
inventoryDao.unfreezeStock(log.getProductId(), log.getCount());
log.setStatus("CONFIRMED");
inventoryLogDao.save(log);
return true;
}
@Transactional
public boolean cancelDeductStock(Long txId) {
InventoryLog log = inventoryLogDao.findByTxId(txId);
if (log == null) return true;
// 解冻库存
inventoryDao.unfreezeStock(log.getProductId(), log.getCount());
inventoryLogDao.delete(log);
return true;
}
}
// === 支付服务 ===
@Service
public class PaymentService {
@Transactional
public boolean tryPreAuth(Long txId, Long userId, BigDecimal amount) {
if (paymentLogDao.existsByTxId(txId)) return true;
// 预授权资金(冻结金额)
accountService.freezeAmount(userId, amount);
paymentLogDao.save(new PaymentLog(txId, userId, amount));
return true;
}
@Transactional
public boolean confirmPayment(Long txId) {
PaymentLog log = paymentLogDao.findByTxId(txId);
if (log.getStatus().equals("CONFIRMED")) return true;
// 实际扣款
accountService.deductAmount(log.getUserId(), log.getAmount());
accountService.unfreezeAmount(log.getUserId(), log.getAmount());
log.setStatus("CONFIRMED");
paymentLogDao.save(log);
return true;
}
@Transactional
public boolean cancelPayment(Long txId) {
PaymentLog log = paymentLogDao.findByTxId(txId);
if (log == null) return true;
// 释放预授权
accountService.unfreezeAmount(log.getUserId(), log.getAmount());
paymentLogDao.delete(log);
return true;
}
}
2. 事务协调器(核心逻辑)
@Service
public class TransactionCoordinator {
@Autowired
private OrderService orderService;
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Autowired
private TransactionLogDao transactionLogDao;
/**
* 开启全局事务
*/
public void executeGlobalTransaction(OrderRequest request) {
Long txId = generateTxId(); // 生成全局唯一事务ID
try {
// === Try阶段 ===
boolean tryResult =
orderService.tryCreateOrder(txId, request.getOrder()) &&
inventoryService.tryDeductStock(txId, request.getProductId(), request.getCount()) &&
paymentService.tryPreAuth(txId, request.getUserId(), request.getAmount());
if (!tryResult) {
throw new RuntimeException("Try阶段失败");
}
// 记录事务状态为TRY_SUCCESS
transactionLogDao.save(new TransactionLog(txId, "TRY_SUCCESS"));
// === Confirm阶段 ===
confirm(txId);
} catch (Exception e) {
// === Cancel阶段 ===
cancel(txId);
throw e;
}
}
/**
* Confirm提交(需幂等)
*/
private void confirm(Long txId) {
TransactionLog log = transactionLogDao.findById(txId).orElseThrow();
if ("CONFIRMED".equals(log.getStatus())) return; // 幂等检查
try {
boolean confirmResult =
orderService.confirmOrder(txId) &&
inventoryService.confirmDeductStock(txId) &&
paymentService.confirmPayment(txId);
if (!confirmResult) {
throw new RuntimeException("Confirm阶段失败");
}
log.setStatus("CONFIRMED");
transactionLogDao.save(log);
} catch (Exception e) {
// 记录异常,触发告警人工干预
log.setStatus("CONFIRM_FAILED");
transactionLogDao.save(log);
throw e;
}
}
/**
* Cancel回滚(需幂等)
*/
private void cancel(Long txId) {
TransactionLog log = transactionLogDao.findById(txId).orElse(null);
if (log == null || "CANCELED".equals(log.getStatus())) return;
try {
// 逆序回滚(建议按依赖顺序调整)
boolean cancelResult =
paymentService.cancelPayment(txId) &&
inventoryService.cancelDeductStock(txId) &&
orderService.cancelOrder(txId);
log.setStatus(cancelResult ? "CANCELED" : "CANCEL_FAILED");
transactionLogDao.save(log);
} catch (Exception e) {
log.setStatus("CANCEL_FAILED");
transactionLogDao.save(log);
throw e;
}
}
// 定时任务:处理悬挂事务(例如Try成功但未收到Confirm/Cancel)
@Scheduled(fixedRate = 60000)
public void recoverHangingTransactions() {
List<TransactionLog> hangingTx = transactionLogDao.findByStatusAndCreateTimeBefore(
"TRY_SUCCESS", LocalDateTime.now().minusMinutes(30));
hangingTx.forEach(tx -> {
// 根据业务规则决定提交或回滚(示例自动回滚)
cancel(tx.getTxId());
});
}
}