外卖霸王餐接口对接中的分布式事务一致性解决方案(Saga模式 vs 本地消息表)

外卖霸王餐接口对接中的分布式事务一致性解决方案(Saga模式 vs 本地消息表)

在外卖平台与第三方“霸王餐”营销系统对接过程中,涉及多个微服务之间的协同操作,如用户资格校验、订单创建、优惠券发放、库存扣减等。由于这些操作分布在不同的服务中,传统的本地事务无法保证全局一致性。因此,必须引入分布式事务解决方案。本文将对比两种主流方案:Saga模式本地消息表,并结合Java代码示例进行分析。

Saga模式实现

Saga是一种长事务管理模型,通过将一个全局事务拆分为多个本地事务,并为每个本地事务定义对应的补偿操作。若某个步骤失败,则依次执行已成功步骤的补偿逻辑,从而实现最终一致性。

在“霸王餐”场景中,典型流程如下:

  1. 校验用户是否具备参与资格;
  2. 创建订单;
  3. 扣减商家库存;
  4. 发放霸王餐优惠券。

若第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开发者团队,转载请注明出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值