引言:为什么单体应用不需要考虑分布式事务?

当我们把一个单体应用拆分为多个微服务后,原本在同一个数据库事务中完成的操作,变成了跨服务、跨数据库的调用。比如一个电商下单操作,需要依次调用:订单服务(创建订单)、库存服务(扣减库存)、支付服务(扣款)、物流服务(创建运单)。任何一个步骤失败,之前已经成功的步骤都需要回滚——这不是普通数据库事务能解决的问题。

2PC为什么在微服务中行不通?

两阶段提交(2PC)是经典的分布式事务协议,但它有几个致命的缺陷使其不适合微服务架构:

  • 同步阻塞:所有参与者在Prepare阶段都持有锁等待,任何一个服务不可用都会导致全局阻塞
  • 单点故障:协调者(Coordinator)宕机后,参与者将永远处于不确定状态
  • 数据不一致:Commit阶段网络分区可能导致部分成功部分失败
  • 违背微服务自治原则:要求所有服务共享同一个事务协议,降低了服务的独立部署能力

Saga模式核心思想

Saga模式的核心思路是:将一个长事务拆分为一系列本地事务,每个本地事务都有对应的补偿事务(Compensating Transaction)。如果某个本地事务失败,就按逆序执行前面所有成功本地事务的补偿事务。

用电商下单的例子来说:

  1. 订单服务创建订单(本地事务T1)→ 补偿C1:取消订单
  2. 库存服务扣减库存(本地事务T2)→ 补偿C2:恢复库存
  3. 支付服务扣款(本地事务T3)→ 补偿C3:退款
  4. 物流服务创建运单(本地事务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预留)短事务,需要预留资源的场景
本地消息表最终一致高异步通知,数据同步
最大努力通知弱一致最高对一致性要求不高的场景

生产环境最佳实践

  1. Saga状态可观测性:每个步骤的开始/完成/失败都要上报到监控系统(Prometheus+Grafana),设置告警规则
  2. 补偿事务设计原则:补偿必须是幂等的、可安全重试的;对于不可逆操作(如发邮件),使用虚补偿(记录已发送状态)
  3. 超时处理:每个步骤设置合理超时时间(通常3-5秒),超时视为失败触发补偿
  4. 死锁预防:使用乐观锁而非悲观锁;补偿操作按逆序执行
  5. 人工介入机制:当自动补偿失败时,进入待人工处理队列,通知运维介入
  6. Saga边界设计:不是所有操作都需要Saga,只在真正的业务事务边界使用
  7. 混沌工程验证:在测试环境注入故障(网络分区、服务宕机),验证Saga正确性

总结

Saga是微服务架构中处理分布式事务的事实标准。它的核心哲学是:接受最终一致性,通过补偿而非锁来保证数据正确。无论是编排式还是协调式,关键是设计好每一个本地事务及其补偿操作。随着事件驱动架构的普及,Saga模式在现代微服务系统中将扮演越来越重要的角色。

建议从简单的编排式开始,待业务复杂度增加时再迁移到协调式。开源的Saga框架(如Axon Framework、Eventuate Tram Saga)可以帮你快速起步,但理解其底层原理才是构建可靠系统的关键。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部