RabbitMQ消息模型概览(简明教程)

     小菜最近用到RabbitMQ,由于之前了解过其他消息中间件,算是有些基础,所以随手从网上搜了几篇文章,准备大概了解下RabbitMQ的消息模型,没想到网上文章千篇一律,写一大堆内容,就是说不明白到底怎么回事,真是逼小菜写博客…

     首先说明本文只适合有消息中间件基础的读者,本文不会讲解基础概念,而是一针见血的指明RabbitMQ该怎么用,告诉读者RabbitMQ能做什么,而不是像网络上其他文章那样花里胡哨抓不住重点。

     好了,直入正题。

 

simple简单队列

 

     这种队列,纯属RabbitMQ搞的一个花样,仅仅是个概念而已!并不是实际的队列类型!他就是在假设某个队列只有一个消费者,也就是说,读者在实际使用中,某个队列傻傻的只用一个消费者去消费,这就叫simple简单队列啦,应用场景极少,一般情况下消费端都会有多个消费者。

 

Fair dispatch公平分发

 

     这种队列让人一看,有点蒙逼,实际上这个概念非常非常简单,如果读者用过redis的话,这个队列模式很像redis的list用法,只不过redis是拉取模型,而mq是推送模型。

     这个公平,可不是说消息平均的发送给消费者,恰恰相反,消费者消费消息的多少,完全取决于消费者的处理能力,能者多劳,相当于消费者主动从mq中取消息,而不是被mq安排消息。

     实现上也不难理解,消费端消费数据时,会有一个确认消费完成的动作,mq收到消费完成的通知后,才会继续向该消费者发送消息,因此,如果消费者处理速度快,那么最终mq向它发送的消息就多,如果消费者处理的慢,mq向它发送的消息就少。

     在这小菜贴出java代码实现的关键点:

RabbitMQ Fair dispatch公平分发

     当然,这是消费端代码,仅仅在消费端做处理即可,对于生产端来说是透明的,不需要做任何处理。

 

Round-robin轮询分发

 

     所谓轮询分发,就是公平分发的退化版,打开自动通知,去掉手动通知,去掉消费端消费条数限制,就是轮询分发啦!!!

     其实轮询分发就是利用了自动通知参数,开启了自动通知,mq根据一个简单的规则(比如取模运算),先确定好哪些消息发送给哪些消费者,无论消费者处理能力如何,这些消息都得让你处理,因此每个消费者最终处理的消息数量,是相同的(忽略"消息数量/消费者"不能整除的情况)。

     这种模式很明显是有问题的,首先,这种模式不能很好的利用消费端的性能差异,做不到真正意义上的负载均衡,浪费资源;其次(只是猜测),这种模型还有可能造成大量消息堆积在消费者容器中,这是非常危险的,不仅会造成消息丢失,还有可能压垮消费者。

 

publish_subscribe发布订阅模式

 

     RabbitMQ 中有一个交换机Exchanges的概念,发布订阅就是通过交换机实现的。

     交换机的概念非常简单,就是一个转发器,有了交换机之后,生产端先把消息发送到交换机,然后交换机再把消息发送到与其绑定的消息队列,这样就解决了生成端如何把一条消息批量发送到多个消息队列的问题。

     交换机本身没有数据存储能力,仅仅是一个代理,可以理解成nginx。

     因此,实现发布订阅的关键在于:

 

          ·  生产端(发布端)直接发送消息到交换机,而不是具体的消息队列。

          ·  多个消费端(订阅端)将自己的消息队列绑定到同一个交换机上。

 

     这样就实现了发布订阅。

 

routing路由模式

 

     路由模式仅仅基于发布订阅搞了一点小事情,在发布订阅模式中,交换机无脑向所有与之绑定的消息队列发送消息,而路由模式对交换机做了一些限制,它指定了一个route key,生产端向交换机发送消息时,指定消息的route key,消费端将消息队列绑定到交换机时,也指定该队列消费的route key,这样一来,交换机就可以根据消息的route key,将该消息转发到绑定(消费)该route key的消息队列。

     生产端关键点:

RabbitMQ routing路由模式生产者 

     消费端关键点:

 RabbitMQ routing路由模式消费者

 

topic主题模式

 

     RabbitMQ又开始搞花样了,咋一看topic小菜还以为是kafka里的topic概念呢,弄的莫名其妙。

     主题模式其实就是路由模式的一个加强,而且是非常非常非常简单的一个加强:route key支持通配符。

     主题模式和路由模式完全一样,只不过是消费端route key不用写死,增加了一个模糊匹配的功能,这样在某些场景下,消费端就不用逐一绑定所有监听的route key,直接用抽象的通配符表示即可,当然,这是针对消费端的优化,与生产端无关。

 

关于RabbitMQ的可靠性

 

     消费端的消费可靠性,已经在"Fair dispatch公平分发"章节中做了介绍,即利用手动通知告诉mq消费成功,但通知也有不可达的可能,进而涉及到重发,具体的处理细节,读者自行查阅资料。

     生产端的提交可靠性,可以通过mq的回调机制实现,即生产端发送消息时自己维护一份已发送消息的集合,mq收到某条消息之后,会向生产端发送一个接收成功确认(体现在代码中就是回调),然后生产端根据确认消息的唯一id,从自己维护的已发送消息集合中移除该消息,从而确保每条消息都成功发送到了mq。

     假如某些消息未成功到达mq,那么就不会有对应消息的确认,最终集合中会有剩余元素(理想情况下是没有的),这些剩余元素,就是发送失败的消息,需要重发。

     简单展示下代码关键点( 生产端):

 RabbitMQ消息可靠性

 

     虽然确认机制可以保证消息的可靠性,但是必然带来性能损失,因此到底需不需要开启生产端或消费端的确认机制,需要根据业务场景具体分析。

 

一些注意事项

 

     RabbitMQ的Connection是昂贵的,但Channel是廉价的,在多线程环境下,尽量创建少数Connection,然后在每个Connection中创建多个Channel,利用Channel实现Connection复用,从而提高系统性能。很像Java NIO里的Selector到Channel的多路复用。

     生产端发送消息时,同一个Channel的basicPublish方法并不是线程安全的,因此更加体现出多Channel的重要性。如果生产端需要使用多线程发送消息,那么必须创建多个Channel,每一个线程单独使用一个Channel,但是这些Channel可以来自同一个Connection。假如线程数量过多,那么也不可以无限制的创建Channel,需要使用Channel Pool(连接池)的思路去控制并发。

     对于同一个Channel而言,发送消息和接受消息是互不影响的,可以进行并发操作。

 

吐槽

 

     最后吐槽一下RabbitMQ客户端API设计的真难用,同一个API竟然通过参数值重载,就比如向消息队列发送消息是这样:

 

 channel.basicPublish("","队列名称", null, message.getBytes()); 

 

     然后向交换机发消息是这样:

 

 channel.basicPublish("交换机名称","route key", null, message.getBytes()); 

 

     同一个方法,第二个参数的含义,竟然是通过第一个参数是否为空决定的,厉害了~

 

posted @ 2018-09-02 22:06  杨元  阅读(...)  评论(... 编辑 收藏