在微服务架构中,分布式事务是绕不开的难题。传统数据库的ACID事务无法跨服务,因此需要引入特殊的模式来保证数据最终一致性。本文通过实战代码,对比三种常见模式:Saga、TCC和最终一致性(基于消息队列)。
1. 场景设定
假设一个电商下单流程:
- 订单服务(Order Service):创建订单
- 库存服务(Inventory Service):扣减库存
- 支付服务(Payment Service):扣款
我们需要保证这三步要么全部成功,要么全部回滚(或补偿)。
2. Saga模式
Saga是一种长事务模式,将大事务拆分为一系列本地事务,每个本地事务都有对应的补偿操作。Saga分为两种编排方式:编排(Choreography)和协调(Orchestration)。
2.1 编排式Saga(基于事件)
服务之间通过事件驱动,每个服务在完成操作后发布事件,触发下一个服务。
代码示例(Node.js + RabbitMQ)
// order-service/index.js
const amqp = require('amqplib');
async function createOrder(order) {
// 1. 创建订单(本地事务)
await db.orders.insert(order);
// 2. 发布“订单创建”事件
const conn = await amqp.connect('amqp://localhost');
const channel = await conn.createChannel();
channel.publish('exchange', 'order.created', Buffer.from(JSON.stringify(order)));
// 3. 等待后续事件(如库存扣减成功/失败)
// 实际应用中会监听回复队列
}
// inventory-service/index.js
const amqp = require('amqplib');
async function handleOrderCreated(msg) {
const order = JSON.parse(msg.content.toString());
try {
// 扣减库存
await db.inventory.decrement(order.productId, order.quantity);
// 发布库存扣减成功事件
channel.publish('exchange', 'inventory.deducted', Buffer.from(JSON.stringify({ orderId: order.id })));
} catch (err) {
// 发布库存扣减失败事件,触发补偿
channel.publish('exchange', 'inventory.deduct.failed', Buffer.from(JSON.stringify({ orderId: order.id })));
}
}
补偿操作:如果后续步骤失败(如支付失败),则需发布“库存补偿”事件,库存服务监听后恢复库存。
优点:松耦合,服务自治。 缺点:事件流复杂,难以追踪;补偿逻辑分散。
2.2 协调式Saga(基于协调器)
引入一个协调器(Saga Orchestrator)负责发送指令给各个服务。
代码示例(Java + Spring Boot)
// SagaOrchestrator.java
@Component
public class SagaOrchestrator {
@Autowired
private OrderService orderService;
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Transactional
public void createOrderSaga(OrderRequest request) {
// 步骤1:创建订单
Order order = orderService.create(request);
try {
// 步骤2:扣减库存
inventoryService.deduct(order.getProductId(), order.getQuantity());
} catch (Exception e) {
// 补偿:取消订单
orderService.cancel(order.getId());
throw new SagaException("库存不足");
}
try {
// 步骤3:扣款
paymentService.debit(order.getUserId(), order.getAmount());
} catch (Exception e) {
// 补偿:恢复库存 + 取消订单
inventoryService.addBack(order.getProductId(), order.getQuantity());
orderService.cancel(order.getId());
throw new SagaException("扣款失败");
}
}
}
优点:逻辑集中,易于管理。 缺点:协调器可能成为单点。
3. TCC模式
TCC(Try-Confirm-Cancel)是一种补偿型事务模式,分为三个阶段:
- Try:预留资源(如锁定库存)
- Confirm:确认执行(如扣减库存)
- Cancel:取消(释放预留资源)
代码示例(Java + Spring Boot)
// InventoryServiceTCC.java
@Service
public class InventoryServiceTCC {
@Autowired
private InventoryRepository repository;
// Try:预留库存
@Transactional
public boolean tryDeduct(Long productId, Integer quantity, String txId) {
// 检查库存是否足够
Inventory inventory = repository.findByProductId(productId);
if (inventory.getAvailable() < quantity) {
return false;
}
// 锁定库存:减少可用库存,增加锁定库存
inventory.setAvailable(inventory.getAvailable() - quantity);
inventory.setLocked(inventory.getLocked() + quantity);
repository.save(inventory);
// 记录事务日志
saveTransactionLog(txId, productId, quantity, "TRY");
return true;
}
// Confirm:确认扣减(真正扣减锁定库存)
@Transactional
public boolean confirmDeduct(String txId) {
TransactionLog log = getTransactionLog(txId);
if (log == null || log.getStatus() != "TRY") {
return false;
}
Inventory inventory = repository.findByProductId(log.getProductId());
inventory.setLocked(inventory.getLocked() - log.getQuantity());
repository.save(inventory);
log.setStatus("CONFIRMED");
updateTransactionLog(log);
return true;
}
// Cancel:取消预留(释放锁定库存)
@Transactional
public boolean cancelDeduct(String txId) {
TransactionLog log = getTransactionLog(txId);
if (log == null || log.getStatus() != "TRY") {
return false;
}
Inventory inventory = repository.findByProductId(log.getProductId());
inventory.setAvailable(inventory.getAvailable() + log.getQuantity());
inventory.setLocked(inventory.getLocked() - log.getQuantity());
repository.save(inventory);
log.setStatus("CANCELLED");
updateTransactionLog(log);
return true;
}
}
注意:TCC需要业务侵入性强,每个服务都要实现Try/Confirm/Cancel接口。同时要保证幂等性。
适用场景:短事务,对一致性要求高(如金融)。
4. 最终一致性(基于消息队列)
利用消息队列(如RocketMQ、Kafka)实现异步确保。核心思想:本地事务 + 消息发送保证最终一致性。
代码示例(Spring Boot + RocketMQ)
// OrderService.java
@Service
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Transactional
public void createOrder(OrderRequest request) {
// 1. 本地创建订单
Order order = new Order();
order.setUserId(request.getUserId());
order.setAmount(request.getAmount());
order.setStatus("PENDING");
orderRepository.save(order);
// 2. 发送半消息(RocketMQ事务消息)
rocketMQTemplate.sendMessageInTransaction(
"order-tx-producer",
MessageBuilder.withPayload(order).build(),
order
);
}
// 事务消息监听器
@RocketMQTransactionListener(producerGroup = "order-tx-producer")
class OrderTransactionListener implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 执行本地事务(扣减库存等)
try {
// 这里可以调用库存服务(通过RPC或消息)
inventoryService.deduct(...);
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
return RocketMQLocalTransactionState.ROLLBACK;
}
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
// 回查订单状态
Order order = (Order) msg.getPayload();
Order dbOrder = orderRepository.findById(order.getId());
if ("SUCCESS".equals(dbOrder.getStatus())) {
return RocketMQLocalTransactionState.COMMIT;
} else if ("FAILED".equals(dbOrder.getStatus())) {
return RocketMQLocalTransactionState.ROLLBACK;
}
return RocketMQLocalTransactionState.UNKNOWN;
}
}
}
优点:性能高,业务解耦。 缺点:最终一致性有延迟;需要处理消息重复。
5. 模式对比与选型
| 模式 | 一致性 | 性能 | 业务侵入 | 适用场景 | |——|——–|——|———-|———-| | Saga(编排) | 最终 | 高 | 低 | 长事务,事件驱动 | | Saga(协调) | 最终 | 中 | 中 | 复杂流程控制 | | TCC | 强(准实时) | 低 | 高 | 金融、短事务 | | 最终一致性(消息) | 最终 | 高 | 低 | 异步场景,如订单 |
选型建议:
- 如果业务允许短暂不一致,优先选择最终一致性(消息队列)。
- 如果对一致性要求高且短事务,选TCC。
- 如果事务跨度长,选Saga。
6. 最佳实践
- 幂等性:所有补偿/确认操作必须幂等。
- 事务日志:记录每个步骤的状态,便于恢复。
- 超时处理:设置合理的超时,配合重试机制。
- 监控与告警:监控事务执行状态,失败时及时告警。
7. 总结
本文通过实战代码演示了Saga、TCC和最终一致性三种分布式事务模式。没有银弹,需要根据业务场景选择。建议从简单的最终一致性入手,随着业务复杂度增加再考虑Saga或TCC。