外卖霸王餐接口对接中的分布式事务一致性解决方案(Saga模式 vs 本地消息表)
在外卖平台与第三方“霸王餐”营销系统对接过程中,涉及多个微服务之间的协同操作,如用户资格校验、订单创建、优惠券发放、库存扣减等。由于这些操作分布在不同的服务中,传统的本地事务无法保证全局一致性。因此,必须引入分布式事务解决方案。本文将对比两种主流方案:Saga模式与本地消息表,并结合Java代码示例进行分析。
Saga模式实现
Saga是一种长事务管理模型,通过将一个全局事务拆分为多个本地事务,并为每个本地事务定义对应的补偿操作。若某个步骤失败,则依次执行已成功步骤的补偿逻辑,从而实现最终一致性。
在“霸王餐”场景中,典型流程如下:
- 校验用户是否具备参与资格;
- 创建订单;
- 扣减商家库存;
- 发放霸王餐优惠券。
若第3步失败,则需回滚第2步(取消订单),但第1步无需回滚(仅查询)。
实现如下:
package juwatech.cn.saga;
public class SagaTransaction {
private final List<SagaStep> steps = new ArrayList<>();
public void addStep(Action action, Compensation compensation) {
steps.add(new SagaStep(action, compensation));
}
public void execute() {
for (int i = 0; i < steps.size(); i++) {
try {
steps.get(i).action.execute();
} catch (Exception e) {
compensate(i - 1);
throw new RuntimeException("Saga failed at step " + i, e);
}
}
}
private void compensate(int lastIndex) {
for (int i = lastIndex; i >= 0; i--) {
try {
steps.get(i).compensation.execute();
} catch (Exception e) {
// 记录日志,人工介入
}
}
}
}
具体业务调用:
SagaTransaction saga = new SagaTransaction();
saga.addStep(
() -> orderService.createOrder(userId, itemId),
() -> orderService.cancelOrder(orderId)
);
saga.addStep(
() -> inventoryService.deduct(itemId, 1),
() -> inventoryService.revert(itemId, 1)
);
saga.addStep(
() -> couponService.issueFreeMealCoupon(userId),
() -> couponService.revokeCoupon(couponId)
);
saga.execute();
Saga模式优点在于逻辑清晰、易于理解,但缺点是补偿逻辑复杂,且无法保证隔离性(如中间状态可能被其他事务读取)。

本地消息表实现
本地消息表方案基于“可靠消息最终一致性”,核心思想是:在本地事务中同时写入业务数据和消息记录,再由后台任务异步投递消息到下游服务。
以“创建霸王餐订单”为例,在order_service中:
@Transactional
public void createFreeMealOrder(Long userId, Long itemId) {
Order order = new Order(userId, itemId, OrderType.FREE_MEAL);
orderMapper.insert(order);
Message message = new Message();
message.setBizId(order.getId());
message.setTopic("free_meal_order_created");
message.setStatus(MessageStatus.PENDING);
message.setPayload(JSON.toJSONString(order));
messageMapper.insert(message); // 与订单在同一事务
}
随后,独立的消息发送器轮询message表,将状态为PENDING的消息投递至MQ:
@Scheduled(fixedDelay = 1000)
public void sendPendingMessages() {
List<Message> messages = messageMapper.selectPending(100);
for (Message msg : messages) {
try {
rabbitTemplate.convertAndSend(msg.getTopic(), msg.getPayload());
messageMapper.markAsSent(msg.getId()); // 更新状态
} catch (Exception e) {
// 重试或告警
}
}
}
下游服务(如库存、优惠券)消费消息后执行本地操作,并需保证幂等性:
@RabbitListener(queues = "free_meal_inventory_queue")
public void handleInventoryDeduction(String payload) {
Order order = JSON.parseObject(payload, Order.class);
if (inventoryService.isDeducted(order.getId())) return; // 幂等检查
inventoryService.deduct(order.getItemId(), 1);
inventoryLogMapper.record(order.getId(), "DEDUCTED");
}
本地消息表方案强依赖数据库事务,能确保消息与业务数据原子性,且实现相对简单,但需额外维护消息表和轮询机制,存在一定的延迟。
方案对比与选型建议
| 维度 | Saga模式 | 本地消息表 |
|---|---|---|
| 一致性 | 最终一致 | 最终一致 |
| 隔离性 | 弱(无锁) | 弱(依赖业务幂等) |
| 实现复杂度 | 高(需补偿逻辑) | 中(需消息表+轮询) |
| 回滚能力 | 显式补偿 | 不支持回滚,靠重试+幂等 |
| 适用场景 | 步骤少、补偿明确 | 高吞吐、异步解耦 |
在“霸王餐”这类营销活动中,通常对实时性要求不高,但对系统稳定性要求高。若业务链路较短且补偿逻辑明确(如订单可取消、库存可回补),可采用Saga;若希望降低耦合、提升吞吐,且能接受异步处理,则本地消息表更合适。
本文著作权归吃喝不愁app开发者团队,转载请注明出处!
&spm=1001.2101.3001.5002&articleId=156150021&d=1&t=3&u=1d09af18fa8f47f492e4f6f589cfc3b6)
397

被折叠的 条评论
为什么被折叠?



