Saga 模式分布式事务:编排与协调器实现
Saga 模式分布式事务:从概念到实战
什么是分布式事务?为什么要用 Saga?
在微服务架构中,一个业务操作常常需要跨多个服务更新数据。例如,一个电商下单流程可能涉及订单服务、库存服务和支付服务。传统单数据库的事务(ACID)无法直接应用于这种跨服务场景。于是我们引入了分布式事务来保证数据一致性。
Saga 是一种为长时间运行、跨多个服务的业务事务提供最终一致性的解决方案。它把一个全局事务拆分为一系列有序的本地事务,每个本地事务都有对应的补偿操作。如果某个步骤失败,Saga 会反向执行已成功步骤的补偿操作,达到“回滚”的效果。
Saga 并非强一致性,而是最终一致性,中间会存在短暂的不一致窗口,适合对一致性实时性要求不极端、但必须保证最终数据对齐的业务场景。
Saga 的实现模式:编排与协调器
实现 Saga 主要分为两类:编排(Choreography) 和 协调器(Orchestration)。选择哪种取决于团队规模、业务复杂度和对集中控制的需求。
编排(Choreography)
编排是一种去中心化的 Saga 实现。每个服务在完成自己的本地事务后,发布一个事件,其他服务订阅并响应。没有统一的指挥者,服务之间通过事件链自然衔接。
执行过程示例(下单流程):
- 订单服务创建订单,状态为
PENDING,发布订单已创建事件。 - 库存服务接收事件,扣减库存,发布
库存已扣减事件。 - 支付服务接收事件,执行扣款,发布
支付已完成事件。 - 订单服务接收事件,将订单状态更新为
已确认。
失败回滚:
若支付失败,支付服务发布支付已失败事件。库存服务监听到后执行补偿(恢复库存),并发布库存已恢复事件。订单服务监听到后将订单置为已取消。
优点:
- 简单易懂,适合小型系统。
- 服务之间松耦合,容易扩展新步骤。
缺点:
- 流程散布在多个服务中,难以监控和排障。
- 容易产生循环依赖或事件风暴。
- 添加新步骤需要理解整个事件流,维护成本随复杂度上升。
协调器(Orchestration)
协调器模式引入一个中心化的 Saga 协调器(Orchestrator),它负责告诉每个参与者该执行什么本地事务,并处理失败补偿。协调器就像一个流程指挥家,按预定流程发送命令并接收响应。
执行过程示例:
- 协调器发送
创建订单命令给订单服务,订单服务返回成功。 - 协调器发送
扣减库存命令给库存服务,库存服务返回成功。 - 协调器发送
执行支付命令给支付服务,支付服务返回失败。 - 协调器按倒序发送补偿命令:向库存服务发送
恢复库存,向订单服务发送取消订单。
优点:
- 业务流程清晰集中在协调器中,易于理解、监控和修改。
- 避免了服务间的依赖循环,减少耦合。
- 适合复杂、多分支的业务流程。
缺点:
- 协调器可能成为单点瓶颈或性能热点(可借助异步、持久化等技术缓解)。
- 服务之间仍需保证接口幂等性,且补偿逻辑需正确实现。
编排 vs. 协调器对比:
| 维度 | 编排(Choreography) | 协调器(Orchestration) |
|---|---|---|
| 控制中心 | 无,事件驱动 | 有,集中式协调器 |
| 服务耦合 | 松(通过消息) | 稍紧(需与协调器通信) |
| 流程维护 | 较难,逻辑分散 | 集中,易于修改 |
| 测试与监控 | 复杂 | 简单,可单点跟踪 |
| 适用规模 | 小型、简单流程 | 中大型、复杂流程 |
Saga 协调器设计要点
如果你选择了协调器模式,设计时需关注以下核心问题:
1. 事务状态的持久化
协调器必须记录当前 Saga 的执行状态(如执行到第几步、每一步的成败)。状态通常存储在数据库中,避免协调器自身崩溃导致流程中断。通常采用事件溯源或状态机模式保存。
2. 幂等性保证
参与者服务接收的命令或补偿可能因网络重试而重复到达。因此,每个服务接口都需要实现幂等性(比如通过唯一业务 ID 去重)。协调器也应具备重试机制,但需设定最大重试次数,超时后转为人工介入或特定补偿。
3. 补偿逻辑的正确性
补偿不是简单的“撤销”,它必须处理并发、中间状态和数据一致性。例如,扣减库存可补偿为加回库存,但如果加回时库存记录已被其他操作修改,则需要使用乐观锁或版本号防止覆盖。
4. 超时和异常处理
Saga 是长时间运行事务,参与方可能长时间无响应。协调器需对每个命令设置超时,超时后执行补偿或重试。同时需要处理协调器自身故障后的恢复:从持久化状态中恢复未完成的事务,继续执行或补偿。
5. 隔离性问题
Saga 缺乏传统事务的隔离性,可能在执行过程中其他事务读到中间状态。解决方案包括:
- 语义锁定:在业务字段上增加状态标记(如
PENDING),其他操作检查此标记。 - 可补偿性分析:设计时确保补偿能正确处理脏读。
- 假设失败率低:对于最终一致性能容忍短暂不一致的业务,不加锁。
亲手实现一个简单的 Saga 协调器
下面用伪代码展示一个基于状态机的协调器核心逻辑,帮助你理解实现模式。
class SagaOrchestrator:
def __init__(self):
# 假设有持久化存储 saga_state
self.state_store = StateStore()
def execute_saga(self, saga_id, saga_definition, input_data):
# 初始化状态并持久化
state = SagaState(saga_id, "STARTED", 0, input_data)
self.state_store.save(state)
try:
for step in saga_definition.steps:
# 发送命令并等待响应(可异步)
response = self.send_command(step.service, step.command, input_data)
if response.is_success():
state.current_step += 1
self.state_store.save(state)
else:
# 步骤失败,开始补偿
self.compensate(saga_id, state.current_step)
return
# 所有步骤成功
state.status = "COMPLETED"
self.state_store.save(state)
except TimeoutError:
self.compensate(saga_id, state.current_step)
def compensate(self, saga_id, failed_step_index):
state = self.state_store.get(saga_id)
# 从当前步骤向前执行补偿
for i in reversed(range(failed_step_index + 1)):
step = saga_definition.steps[i]
self.send_command(step.service, step.compensation_command, state.data)
state.status = "COMPENSATED"
self.state_store.save(state)
在实际项目中,你会使用消息队列、事件总线或 HTTP 调用来实现命令发送,并用数据库保存 sagas 状态。框架如 Axon Framework(Java)、Eventuate、MicroProfile LRA 或云服务(AWS Step Functions)都提供了现成的 Saga 支持。
Saga 模式落地的最佳实践
- 明确定义边界:不是一个业务操作越长越适合 Saga,过长的事务考虑拆分为更小的流程。
- 设计小步快跑的步骤:每个步骤应尽量短小、独立,避免跨服务长事务。
- 优先使用协调器模式:对于大多数项目,协调器的可维护性优势远大于引入的集中点风险。
- 实现全局唯一 ID:每个 Saga 实例有唯一 ID,并贯穿所有命令和事件,便于追踪。
- 监控和告警:为 Saga 状态迁移设置监控,堆积的长时间未完成的 Saga 需要及时告警。
- 从失败中恢复:提供管理接口手动重试或跳过某步,将人工干预作为兜底。
- 与事件驱动的最终一致性结合:即使是协调器模式,最终通知也可以由事件完成,降低同步等待。
总结
Saga 模式是微服务架构中处理分布式事务的利器。它用一组有序的本地事务和补偿操作来保证业务数据的最终一致。两种实现风格——编排和协调器,分别适用于不同复杂度。协调器模式因其流程清晰、易于维护而成为大多数场景的首选。设计时,需要重点关注状态持久化、幂等性、补偿正确性和异常恢复。掌握 Saga 模式,你就能在微服务世界中游刃有余地设计可靠的数据一致性方案。