引言:为什么单体应用不需要考虑分布式事务?
当我们把一个单体应用拆分为多个微服务后,原本在同一个数据库事务中完成的操作,变成了跨服务、跨数据库的调用。比如一个电商下单操作,需要依次调用:订单服务(创建订单)、库存服务(扣减库存)、支付服务(扣款)、物流服务(创建运单)。任何一个步骤失败,之前已经成功的步骤都需要回滚——这不是普通数据库事务能解决的问题。
2PC为什么在微服务中行不通?
两阶段提交(2PC)是经典的分布式事务协议,但它有几个致命的缺陷使其不适合微服务架构:
- 同步阻塞:所有参与者在Prepare阶段都持有锁等待,任何一个服务不可用都会导致全局阻塞
- 单点故障:协调者(Coordinator)宕机后,参与者将永远处于不确定状态
- 数据不一致:Commit阶段网络分区可能导致部分成功部分失败
- 违背微服务自治原则:要求所有服务共享同一个事务协议,降低了服务的独立部署能力
Saga模式核心思想
Saga模式的核心思路是:将一个长事务拆分为一系列本地事务,每个本地事务都有对应的补偿事务(Compensating Transaction)。如果某个本地事务失败,就按逆序执行前面所有成功本地事务的补偿事务。
用电商下单的例子来说:
- 订单服务创建订单(本地事务T1)→ 补偿C1:取消订单
- 库存服务扣减库存(本地事务T2)→ 补偿C2:恢复库存
- 支付服务扣款(本地事务T3)→ 补偿C3:退款
- 物流服务创建运单(本地事务T4)→ 补偿C4:作废运单
如果T3失败,则执行C2→C1(注意顺序是逆序),系统恢复到操作前的状态。
Saga的两种实现方式
编排式Saga(Choreography)
每个服务完成本地事务后发布事件,其他服务监听事件并决定是否执行下一个步骤。
// 订单服务
class OrderService {
async createOrder(data) {
const order = await db.orders.create(data);
await eventBus.publish('ORDER_CREATED', { orderId: order.id, items: data.items });
return order;
}
// 监听补偿事件
eventBus.on('ORDER_FAILED', async ({ orderId }) => {
await db.orders.update(orderId, { status: 'CANCELLED' });
});
}
// 库存服务
class InventoryService {
eventBus.on('ORDER_CREATED', async ({ orderId, items }) => {
try {
for (const item of items) {
await db.inventory.decrement(item.productId, item.quantity, orderId);
}
await eventBus.publish('INVENTORY_RESERVED', { orderId });
} catch (error) {
await eventBus.publish('INVENTORY_RESERVATION_FAILED', { orderId });
throw error;
}
});
eventBus.on('INVENTORY_RESERVATION_FAILED', async ({ orderId }) => {
await eventBus.publish('ORDER_FAILED', { orderId });
});
}
优点:实现简单、松耦合、无需中心协调器
缺点:业务流程分散在多个服务中难以追踪;循环依赖风险;测试复杂
协调式Saga(Orchestration)
引入一个专门的Saga协调器(Orchestrator),负责编排整个事务流程,决定下一步执行什么、失败时如何补偿。
// Saga定义
const orderSagaDefinition = {
steps: [
{
name: 'createOrder', service: 'order-service', path: '/orders',
compensation: { method: 'patch', path: '/orders/{orderId}/cancel' }
},
{
name: 'reserveInventory', service: 'inventory-service', path: '/inventory/reserve',
compensation: { method: 'post', path: '/inventory/release' }
},
{
name: 'processPayment', service: 'payment-service', path: '/payments',
compensation: { method: 'post', path: '/payments/{paymentId}/refund' }
},
{
name: 'shipOrder', service: 'shipping-service', path: '/shipments',
compensation: { method: 'delete', path: '/shipments/{shipmentId}' }
}
]
};
// Saga协调器
class SagaOrchestrator {
async execute(sagaId, definition, input) {
const sagaState = await this.createSagaState(sagaId, definition, input);
const executedSteps = [];
for (const step of definition.steps) {
try {
const result = await this.callService(step, sagaState.state);
executedSteps.push({ step, result });
await this.recordStepSuccess(sagaId, step, result);
} catch (error) {
await this.compensate(sagaId, executedSteps.reverse());
await this.markSagaAsFailed(sagaId, step, error);
throw new SagaFailedException(sagaId, step, error);
}
}
await this.markSagaAsCompleted(sagaId);
return sagaState;
}
async compensate(sagaId, executedSteps) {
const errors = [];
for (const { step, result } of executedSteps) {
try {
await this.callCompensation(step, result);
await this.recordCompensationSuccess(sagaId, step);
} catch (compError) {
errors.compensationFailures.push({ step, compError });
}
}
if (errors.length > 0) {
await this.escalateCompensationFailures(sagaId, errors);
}
}
}
优点:业务流程集中管理、易于监控和调试、避免服务间循环依赖
缺点:引入单点、协调器本身需要高可用
实战:电商下单Saga完整实现
1. 持久化Saga状态
协调器需要持久化每一步的执行结果和状态,以便在崩溃恢复后能继续执行。使用事件溯源(Event Sourcing)+ CQRS模式:
// Saga状态表(PostgreSQL)
CREATE TABLE saga_instances (
saga_id UUID PRIMARY KEY,
status VARCHAR(20) NOT NULL, -- PENDING, RUNNING, COMPLETED, FAILED, COMPENSATING
started_at TIMESTAMP NOT NULL DEFAULT NOW(),
completed_at TIMESTAMP,
current_step INTEGER DEFAULT 0,
context JSONB NOT NULL DEFAULT '{}'
);
CREATE TABLE saga_steps (
id SERIAL PRIMARY KEY,
saga_id UUID REFERENCES saga_instances(saga_id),
step_name VARCHAR(100) NOT NULL,
action VARCHAR(20) NOT NULL, -- ACTION or COMPENSATION
status VARCHAR(20) NOT NULL,
request JSONB,
response JSONB,
error_message TEXT,
executed_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_saga_steps_saga_id ON saga_steps(saga_id);
CREATE INDEX idx_saga_status ON saga_instances(status) WHERE status IN ('RUNNING','COMPENSATING');
2. 幂等性保证
网络超时导致的重复请求可能让Saga步骤被重复执行。每个步骤都需要幂等性保证:
// 基于sagaId+stepId的幂等控制
async function executeWithIdempotency(sagaId, step, operation) {
const idempotencyKey = `${sagaId}-${step.name}`;
// 检查是否已执行
const existing = await db.saga_steps.findOne({
where: { saga_id: sagaId, step_name: step.name }
});
if (existing) {
if (existing.status === 'COMPLETED') return existing.response;
if (existing.status === 'PROCESSING') {
return await waitForCompletion(existing.id, 5000);
}
}
// 创建执行记录(利用唯一约束防并发)
try {
await db.saga_steps.create({
saga_id: sagaId,
step_name: step.name,
status: 'PROCESSING'
});
} catch (UniqueConstraintError) {
// 并发冲突,等重试
return await waitForCompletion(idempotencyKey, 5000);
}
const result = await operation();
await db.saga_steps.update(
{ status: 'COMPLETED', response: result },
{ where: { saga_id: sagaId, step_name: step.name } }
);
return result;
}
3. DAG式Saga进阶
当某些步骤可以并行执行时(如同时扣减库存和验证用户身份),Saga支持DAG(有向无环图)结构:
const parallelSagaDefinition = [
{ name: 'createOrder', next: ['reserveInventory', 'validateUser'] },
{ name: 'reserveInventory', requires: ['createOrder'], next: ['waitForReservations'] },
{ name: 'validateUser', requires: ['createOrder'], next: ['waitForReservations'] },
{ name: 'waitForReservations', requires: ['reserveInventory', 'validateUser'], next: ['processPayment'] },
{ name: 'processPayment', requires: ['waitForReservations'], next: ['shipOrder'] },
{ name: 'shipOrder', requires: ['processPayment'], next: [] }
];
Saga vs 其他分布式事务方案对比
| 方案 | 一致性 | 性能 | 适用场景 |
|---|---|---|---|
| 2PC | 强一致 | 低(阻塞) | 同构系统,微服务不适用 |
| Saga | 最终一致 | 高 | 长流程业务,跨服务事务 |
| TCC | 最终一致 | 中(需要Try预留) | 短事务,需要预留资源的场景 |
| 本地消息表 | 最终一致 | 高 | 异步通知,数据同步 |
| 最大努力通知 | 弱一致 | 最高 | 对一致性要求不高的场景 |
生产环境最佳实践
- Saga状态可观测性:每个步骤的开始/完成/失败都要上报到监控系统(Prometheus+Grafana),设置告警规则
- 补偿事务设计原则:补偿必须是幂等的、可安全重试的;对于不可逆操作(如发邮件),使用虚补偿(记录已发送状态)
- 超时处理:每个步骤设置合理超时时间(通常3-5秒),超时视为失败触发补偿
- 死锁预防:使用乐观锁而非悲观锁;补偿操作按逆序执行
- 人工介入机制:当自动补偿失败时,进入待人工处理队列,通知运维介入
- Saga边界设计:不是所有操作都需要Saga,只在真正的业务事务边界使用
- 混沌工程验证:在测试环境注入故障(网络分区、服务宕机),验证Saga正确性
总结
Saga是微服务架构中处理分布式事务的事实标准。它的核心哲学是:接受最终一致性,通过补偿而非锁来保证数据正确。无论是编排式还是协调式,关键是设计好每一个本地事务及其补偿操作。随着事件驱动架构的普及,Saga模式在现代微服务系统中将扮演越来越重要的角色。
建议从简单的编排式开始,待业务复杂度增加时再迁移到协调式。开源的Saga框架(如Axon Framework、Eventuate Tram Saga)可以帮你快速起步,但理解其底层原理才是构建可靠系统的关键。

发表评论 取消回复