目的: 从源码角度解读RocketMQ 是如何保证消息顺序消费的
- RocketMQ 生产者 ---broker---消费者之间的关系如下图
大家看RocketMQ 官方文档应该有了解到,Rocketmq 能保证单个队列的局部有序性,但无法保证全局的有序性。
如何保证消息有序?我们从两个方面解读: 一首先要保证消息发送方发送消息有序,其次消费方要保证消费消息的顺序性
- 消息生产者保证消息有序: 上面我们提到,Rocketmq 可以保证单个队列的有序性,所以消息生产者需要保证相同类型的消息投递到同一个队列中,Rocketmq 提供了几种消息路由策略
首先看看Rocketmq的消息发送类:MQProducer.java, 里面有个很关键的参数是MessageQueueSelector,这个主要用来做消息的队列路由。
MessageQueueSelector主要有一下几个实现类 SelectMessageQueueByHash SelectMessageQueueByMachineRoom SelectMessageQueueByRandom
SelectMessageQueueByHash: 这个对于需要顺序消息来说非常重要,我们可以根据业务唯一性ID 来进行hash路由,比如对于订单类业务,
我们可以根据订单ID进行hash 路由,进而保证相同的订单可以发送到相同的队列。
SelectMessageQueueByMachineRoom: 这个对于多机房的场景比较实用,相同机房的业务把消息发送相同机房的MQ集群,减少跨机房的数据传输。
SelectMessageQueueByRandom: 随机发送从字面意思也很好理解了,就是随机找到一个队列进行发送。无法保障消息的有序性。
提示: 很多人会问一个问题,比如有message A和message B, 预期的结果是A先消费B后消费。 我们在消息发送的时候业务必须保证,消息A发送成功后再发送消息B,否则无法保证消息的有序性。
- 消息消费者保证消息有序
ConsumeMessageService是Rocketmq 客户端消费消息的接口类,他主要有两个实现类ConsumeMessageOrderlyService 和ConsumeMessageConcurrentlyService
ConsumeMessageConcurrentlyService :并发消费,客户端批量从broker拉取到消息后会提交到线程池去执行。
ConsumeMessageOrderlyService: 顺序消费,消息处理之前,首先对消息暂存的队列加锁,保证只有一个线程顺序消费
(反过来想一下,如果我们使用多线程,即便我们从broker拉取的消息是有序的,但是由于两个消息同时提交到两个线程去执行,因为CPU的时间片是无序的,无法保证执行的先后,也就很难做到业务上的消息顺序)
综上: 如果我们想保证消息的有序性,消息生产者和消息消费者必须两手抓,并且两手都要硬。