跳到主要内容

分布式事务

问题

跨服务的事务如何保证一致性?有哪些分布式事务方案?

答案

为什么需要分布式事务

微服务架构下,一个业务操作可能涉及多个服务的数据库。例如下单:

  1. 订单服务:创建订单
  2. 库存服务:扣减库存
  3. 支付服务:扣款

单个数据库的 ACID 事务无法跨服务使用。

常见方案对比

方案一致性复杂度性能适用
2PC(两阶段提交)强一致传统数据库
Saga最终一致微服务
Event Sourcing最终一致事件驱动架构
Outbox Pattern最终一致消息可靠投递
TCC强一致极高金融场景

Saga 模式

saga.ts
// 编排式 Saga(协调者管理流程)
class OrderSaga {
private steps: SagaStep[] = [];

addStep(execute: () => Promise<void>, compensate: () => Promise<void>) {
this.steps.push({ execute, compensate });
}

async run() {
const executedSteps: SagaStep[] = [];

try {
for (const step of this.steps) {
await step.execute();
executedSteps.push(step);
}
} catch (error) {
// 逆序执行补偿操作
for (const step of executedSteps.reverse()) {
try {
await step.compensate();
} catch (compensateError) {
// 补偿失败,记录日志,人工介入
logger.error('Compensation failed', compensateError);
}
}
throw error;
}
}
}

// 使用
const saga = new OrderSaga();
saga.addStep(
() => orderService.create(orderId), // 执行
() => orderService.cancel(orderId), // 补偿
);
saga.addStep(
() => inventoryService.deduct(productId, qty),
() => inventoryService.restore(productId, qty),
);
saga.addStep(
() => paymentService.charge(userId, amount),
() => paymentService.refund(userId, amount),
);
await saga.run();

Outbox Pattern

outbox.ts
// 在同一个数据库事务中写业务数据和 Outbox
async function createOrder(order: CreateOrderDto) {
await prisma.$transaction(async (tx) => {
// 1. 写业务数据
const created = await tx.order.create({ data: order });

// 2. 写 Outbox 表(同一事务,保证原子性)
await tx.outbox.create({
data: {
aggregateType: 'Order',
aggregateId: created.id,
eventType: 'OrderCreated',
payload: JSON.stringify(created),
},
});
});
}

// 后台任务:轮询 Outbox 表,发布消息
async function publishOutboxEvents() {
const events = await prisma.outbox.findMany({
where: { published: false },
take: 100,
});

for (const event of events) {
await messageQueue.publish(event.eventType, event.payload);
await prisma.outbox.update({
where: { id: event.id },
data: { published: true },
});
}
}
为什么不直接发消息?

"写数据库 + 发消息" 不是原子操作。数据库写成功但消息发送失败,会导致数据不一致。Outbox Pattern 将消息写入数据库同一事务,保证了可靠性。


常见面试问题

Q1: Saga 的编排模式和协调模式有什么区别?

答案

维度编排(Orchestration)协调(Choreography)
协调方式中心协调者管理各服务通过事件通信
耦合度协调者知道所有步骤服务间松耦合
可观测性好(流程集中)差(流程分散)
适用流程复杂流程简单(2-3 步)

Q2: 补偿操作失败怎么办?

答案

  1. 重试:补偿操作设计为幂等,可安全重试
  2. 记录:持久化到数据库/日志
  3. 人工介入:告警通知,人工处理

Q3: 分布式事务和本地事务怎么选?

答案

尽量避免分布式事务:

  1. 优先通过服务设计避免跨服务事务
  2. 能用单库事务就不要分布式事务
  3. 接受最终一致性时用 Saga/Outbox
  4. 强一致性场景考虑 TCC 或 2PC

相关链接