切换主题
07. 顺序消息:订单状态为什么会乱
线上事故
客服反馈:
有些订单页面先显示“已支付”,过一会又变回“已创建”。
排查后发现,订单状态事件是异步消费的:
text
OrderCreated
OrderPaid
OrderShipped不同订单之间不需要顺序,但同一个订单内部状态必须有序。如果 OrderPaid 比 OrderCreated 先被某个消费者处理,就会出现状态回退。
顺序消息解决什么
顺序消息解决的是:同一个业务分组内的消息按发送顺序消费。
在订单系统里,分组键就是 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,真实订单状态要用 orderId、userId 这类业务分组键。所有消息都用同一个 group,会把并发能力打成单队列。
常见坑
为了省事所有消息用同一个 messageGroup。
这样会严重影响吞吐。以为顺序消息能替代业务状态机。
状态合法性仍要业务自己校验。跨 topic 追求顺序。
先把需要顺序的状态事件收敛到清晰的消息流里。
练习题
- 订单状态事件应该用什么作为
messageGroup? - 用户积分流水是否适合顺序消息?
- 为什么全局顺序通常不是好目标?
参考答案
- 用
orderId。 - 适合。可以用
userId作为分组,保证同一用户积分流水有序。 - 全局顺序会牺牲大量并发能力,而且大多数业务只需要局部顺序。
来源
- RocketMQ 顺序消息:https://rocketmq.apache.org/docs/featureBehavior/03fifomessage/
- 官方 producer sample:https://github.com/apache/rocketmq-spring/blob/rocketmq-spring-all-2.3.4/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientProducerApplication.java