1. 分布式事务的困境与Saga的诞生
在单体应用时代,事务管理就像在自家后院种菜——所有操作都在一个数据库里完成,ACID特性(原子性、一致性、隔离性、持久性)由数据库引擎完美保障。我们熟悉的代码模板是这样的:
java复制@Transactional
public void placeOrder(Order order) {
accountRepository.deduct(order.getUserId(), order.getAmount());
inventoryRepository.reduce(order.getProductId(), order.getQuantity());
orderRepository.save(order);
}
但随着微服务架构的普及,这个美好的世界被彻底颠覆。当订单服务、支付服务和库存服务各自拥有独立的数据库时,传统的本地事务就像被拆散的拼图,再也无法保证全局一致性。这时开发者面临三个残酷现实:
- 网络不可靠:跨服务调用可能失败
- 性能瓶颈:全局锁会导致系统吞吐量骤降
- 架构约束:不同服务可能使用异构数据库
我在电商系统迁移微服务架构时,曾遇到一个典型场景:用户支付成功后库存扣减失败。按照传统思维,我们首先尝试了2PC(两阶段提交),结果在高并发时系统吞吐量下降了80%。这迫使我们寻找更务实的解决方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Saga模式的核心思想解析
2.1 基本工作原理
Saga模式的核心可以用"分段提交,逆向补偿"八个字概括。与2PC的"预提交-提交"机制不同,Saga将分布式事务拆解为一系列本地事务:
- 正向操作序列:T1 → T2 → T3 → ... → Tn
- 补偿操作序列:C1 ← C2 ← C3 ← ... ← Cn
当某个正向操作失败时,系统会按照反向顺序执行对应的补偿操作。以电商下单为例:
code复制正向流程:
1. 创建订单(T1)
2. 扣减支付(T2)
3. 扣减库存(T3)
补偿流程:
库存扣减失败 → 退款(C2) → 取消订单(C1)
2.2 与2PC的本质区别
通过对比可以清晰看出Saga的设计哲学:
| 特性 | 2PC | Saga |
|---|---|---|
| 一致性 | 强一致 | 最终一致 |
| 锁机制 | 全局锁 | 无锁 |
| 可用性 | 协调者单点故障 | 无单点故障 |
| 性能影响 | 高延迟 | 低延迟 |
| 适用场景 | 短事务、低并发 | 长事务、高并发 |
实际项目中,2PC的平均延迟通常在100ms以上,而Saga通常能控制在20ms内。但要注意,Saga的这种优势是以牺牲强一致性为代价的。
3. Saga的两种实现模式详解
3.1 编排式(Orchestration)实现
编排式Saga引入了一个专门的协调器(Saga Coordinator)来集中管理事务流程。这是目前Java生态中最主流的实现方式。
典型架构设计
mermaid复制graph TD
SC[Saga Coordinator] -->|调用| OS[Order Service]
SC -->|调用| PS[Payment Service]
SC -->|调用| IS[Inventory Service]
IS -->|回调| SC
PS -->|回调| SC
OS -->|回调| SC
Spring Boot实现示例
java复制@Service
public class OrderSagaCoordinator {
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@Autowired
private InventoryService inventoryService;
@Transactional
public void createOrder(OrderDTO orderDTO) {
// 记录Saga执行状态
SagaLog sagaLog = new SagaLog(orderDTO.getOrderId());
try {
// 步骤1:创建订单
Order order = orderService.createOrder(orderDTO);
sagaLog.logStep("ORDER_CREATED");
// 步骤2:扣款
paymentService.debit(order.getUserId(), order.getAmount());
sagaLog.logStep("PAYMENT_DEBITED");
// 步骤3:扣库存
inventoryService.deduct(order.getProductId(), order.getQuantity());
sagaLog.logStep("INVENTORY_DEDUCTED");
sagaLog.complete();
} catch (Exception ex) {
// 根据日志执行补偿
compensate(sagaLog);
throw new SagaException("Order creation failed", ex);
}
}
private void compensate(SagaLog sagaLog) {
if (sagaLog.containsStep("INVENTORY_DEDUCTED")) {
inventoryService.compensateDeduct(sagaLog.getOrderId());
}
if (sagaLog.containsStep("PAYMENT_DEBITED")) {
paymentService.compensateDebit(sagaLog.getOrderId());
}
if (sagaLog.containsStep("ORDER_CREATED")) {
orderService.cancelOrder(sagaLog.getOrderId());
}
}
}
关键设计要点
- 状态持久化:必须记录每个Saga实例的执行状态
- 超时处理:需要设置每个步骤的超时时间
- 幂等设计:补偿操作可能被重复调用
在实际项目中,我们通常会将Saga状态存储在单独的数据库表中,包含字段如:saga_id、current_step、status、compensated等。
3.2 编舞式(Choreography)实现
编舞式Saga通过事件驱动架构实现,各服务通过消息队列进行通信。
典型事件流程
code复制OrderCreated → PaymentDebited → InventoryD
