Skip to content

07. 顺序消息:订单状态为什么会乱

线上事故

客服反馈:

有些订单页面先显示“已支付”,过一会又变回“已创建”。

排查后发现,订单状态事件是异步消费的:

text
OrderCreated
OrderPaid
OrderShipped

不同订单之间不需要顺序,但同一个订单内部状态必须有序。如果 OrderPaidOrderCreated 先被某个消费者处理,就会出现状态回退。

顺序消息解决什么

顺序消息解决的是:同一个业务分组内的消息按发送顺序消费。

在订单系统里,分组键就是 orderId

text
orderId=O1001:
  OrderCreated -> OrderPaid -> OrderShipped

orderId=O1002:
  OrderCreated -> OrderCancelled

不同订单之间不需要互相等待。

发送 FIFO 消息

java
public SendReceipt publishOrderStatusChanged(OrderStatusChangedEvent event) {
    return rocketMQClientTemplate.syncSendFifoMessage(
            "order-status-topic",
            event,
            event.orderId()
    );
}

第三个参数就是 messageGroup。官方 samples 里也通过 demo.rocketmq.message-group 演示了 FIFO 消息发送。

什么时候用顺序消息

场景是否适合
同一订单状态流转适合
同一用户积分流水适合
所有订单全局有序通常不适合
发短信通知通常不需要

顺序越强,吞吐越容易受影响。只给真正需要顺序的业务分组加顺序。

消费端注意

消费者处理顺序消息时,业务逻辑也不能破坏顺序。例如同一个订单的状态更新要做版本判断:

java
public void applyStatus(OrderStatusChangedEvent event) {
    Order order = orderRepository.getById(event.orderId());
    if (event.version() <= order.getStatusVersion()) {
        return;
    }
    order.changeStatus(event.newStatus(), event.version());
    orderRepository.save(order);
}

顺序消息能降低乱序概率,但业务层仍要防止旧事件覆盖新状态。

小技巧

messageGroup 不要用常量。很多示例为了演示写 group1,真实订单状态要用 orderIduserId 这类业务分组键。所有消息都用同一个 group,会把并发能力打成单队列。

常见坑

  1. 为了省事所有消息用同一个 messageGroup。
    这样会严重影响吞吐。

  2. 以为顺序消息能替代业务状态机。
    状态合法性仍要业务自己校验。

  3. 跨 topic 追求顺序。
    先把需要顺序的状态事件收敛到清晰的消息流里。

练习题

  1. 订单状态事件应该用什么作为 messageGroup
  2. 用户积分流水是否适合顺序消息?
  3. 为什么全局顺序通常不是好目标?

参考答案

  1. orderId
  2. 适合。可以用 userId 作为分组,保证同一用户积分流水有序。
  3. 全局顺序会牺牲大量并发能力,而且大多数业务只需要局部顺序。

来源

Built with VitePress. Deployed on Cloudflare Pages.