在知乎看到了这个问题,总结下(发现某乎社会热点问题讨论没法看,专业知识问题老哥们答得可是很ok)
首先,根据RocketMQ的存储机制,RocketMQ是支持顺序消费的。但这个顺序,不是全局顺序,只是分区(Message Queue)顺序。要全局顺序只能一个分区(Message Queue)。
接下来,我们从生产者和消费者两方面的逻辑来考虑这个问题。
Consumer
消费消息的顺序要同发送消息的顺序一致。由于 Consumer 消费消息的时候是针对 Message Queue 顺序拉取并开始消费,且一条 Message Queue 只会给一个消费者(集群模式下)(一个Topic的多个队列分配给一个group,然后group的多个消费者各自分到队列进行消费,默认平均分配),所以能够保证同一个消费者实例对于 Queue 上消息的消费是顺序地开始消费(不一定顺序消费完成,因为消费可能并行)。
Producer
发送消息的时候,消息发送默认是会采用轮询的方式发送到不同的queue,需要的话生产者发送的时候可以用 MessageQueueSelector 为某一批消息(通常是有相同的唯一标示id)选择同一个 Queue ,则这一批消息的消费将是顺序消息(并由同一个consumer完成消息)。或者 Message Queue 的数量只有 1 ,但这样消费的实例只能有一个,多出来的实例都会空跑。
但的确会有一些异常场景会导致乱序。如Broker 宕机或重启,导致写入队列的数量上出现变化。这样在MessageQueueSelector之中的逻辑无论是轮训还是取模就会出问题,由于队列总数发生发化,消费者也会触发负载均衡,而默认地负载均衡算法采取哈希取模平均,这样负载均衡分配到定位的队列会发化,使得队列可能分配到别的实例上,则会短暂地出现消息顺序不一致。除非选择牺牲failover特性(即 Broker 集群中只要有一台机器不可用,则整个集群都不可用),如master挂了无法发通接下来那批消息,但是服务可用性大大降低。
讨论完顺序消费的逻辑,我们来看一看实际场景中的问题,扩容是我们可能会遇到的场景,我们来思考下在扩容情况下,如何在不停写的情况下保证顺序消费?
顺序消息扩容的过程中,如何在不停写的情况下保证消息顺序?
1成倍扩容,实现扩容前后,同样的 key,hash 到原队列,或者 hash 到新扩容的队列。
2扩容前,记录旧队列中的最大位点。
3对于每个 Consumer Group ,保证旧队列中的数据消费完,再消费新队列,也即:先对新队列进行禁读即可。
参考资料