之前有小伙伴说面试中被问到了这个问题,消息中间件如何确保消息顺序,因为松哥之前出过一个 RabbitMQ 的系列教程,所以他就跑来微信上问我,那么今天我们就来稍微捋一捋这个话题。
怎么说呢,感觉在面试中,如果涉及到消息中间件,两个频率比较高的问题,一个是如何确保消息的可靠性,另一个就是如何确保消息有序,如何确保消息可靠性,这个松哥之前已经写过了,公众号后台回复 2020 有文章索引。
1. 整体思路
首先,我们先来聊聊在确保消息有序这件事上的一个整体思路。
大家先来看下面一张图,这是官方提供的 RabbitMQ 消息发送流程图:
大家可以看到,消息从 Publisher 出来之后,先被发送到 Exchange,然后 Exchange 将消息转到 Queue 队列上,后,消费者消费 Queue 上的消息,大致上就是这样一个流程。
那么要确保消息的顺序,只需要确保两点即可:
发送有序。 消费有序。
其实只要这两点就够了。
因为当消息发送成功到达队列之后,队列本身就是有序的,先进先出,所以正常情况下,我们是不必考虑队列中消息顺序的问题的。
不过需要小伙伴们注意的是,如果你的消息发送到不同的队列中,那么消息出队的顺序就无法保证了。
所以,从这里我们就总结出个规则,就是如果要确保消息有序,相同类型的消息至少得发到一个队列中,不能发到多个队列中。
2. 发送有序
正常来说,我们发送消息的时候都是按照既定的业务顺序发送的,这点是无疑的。所以发送有序本来不是啥大事,问题在于,有的时候我们的项目是集群化部署,同一个项目有多个实例,当多个不同的实例分布于不同的服务器上运行的时候,都向 MQ 发消息,此时就无法确保消息的有序了。
那么对于这种情况,我们可以考虑使用 Redis 分布式锁来实现,发送消息之前,先去 Redis 上获取到锁,能拿到锁,然后再去发送消息,避免并发发送。至于 Redis 做分布式锁的具体用法我这里就不啰嗦了,之前的 vhr 视频里都讲过了,公号后台回复 vhr 可以查看视频介绍。
这是一个思路。
3. 消费有序
接下来就是消费有序了。
消费有序这里要注意的细节就比较多。
首先,同一个队列只能有一个消费者,如果存在多个消费者,则消费顺序就无法保证了。
其次,同一个队列不能开启并发消费,例如像下面这样的代码:
@RabbitListener(queues = RabbitConfig.JAVABOY_QUEUE_NAME,concurrency = "10")
public void handleMsg(String msg) {
logger.info("msg:{}", msg);
}
这个相当于建立了 10 个 channel 去同时消费消息,对于这种情况,也是没法保证消费的有序的,因为本地代码执行的快慢、是否抛异常等等,都有可能会影响到消息的顺序。
无法并发消费,就会导致消费性能下降,如果确实对性能又有比较高的要求,那么我们相同类型的队列可以创建多个,然后依然是每一个队列一个消费者即可。
举个简单的例子,比如电商下单的时候,需要给同一个队列发送多条消息,正常发送这些消息当然都是有序的。如果想提高并发能力,那么我们可以设置多个消息队列,跟一个用户相关的订单都发送到同一个消息队列中,然后一个队列对应一个消费者,这样就能保证消息的有序。
好啦,大概就这么多细节。
4. 小结
后,我们再稍微总结下:
消息发送,自己确保是有序的;集群化部署的话,可以通过分布式锁确保消息有序。 消息到达队列之后,默认就是有序的。 消息消费,一个队列对应一个消费者,并且一个消费者一个 channel,不能并发消费,就可以确保消息有序。
好啦,以上就是确保 RabbitMQ 消息有序的几个细节。