RabbitMQ之消息队列“治堵”秘籍

檀兵
原创 933       2019-07-09  

曾几何时,办公室惨叫连连,各类运维状况触目惊心。


”服务流量太大,超出了机器承受范围导致服务凉凉。”

”这代码耦合度怎么这么高,改一处影响一大堆。”

”java程序如何实现和C/C++、C#、python…等异构语言互调?”


知道如何简单高效的避免与解决这类问题吗?来,送你这本”绝世武功秘籍”,教你如何运用“消息队列”披荆斩棘!


那什么是消息队列呢?怎么用消息队列?该用什么消息队列……下面咱们开始进入正题,跟着我“魔鬼的步伐”一步步来!


首先,在介绍消息队列前,先举个栗子。比如双十一,各位“剁手党”从网上购物,然后商家需要将商品通过快递公司邮寄到你手中。那么快递公司是怎样运作的呢?按照笔者的想法,应该需要下面几步操作:


商家需要将商品送往快递网点投递,或者上门取件

(将商品送至网点)

快递网点收到商品后,需要将快递进行分拣

(按地址进行分拣)

按照分拣的快递往指定区域运输

(运往邮寄地址所在区域,如“江浙沪”包邮区)

安排快递员进行送货上门,或者通知你去网点自取

(收快递)。


消息队列跟前文的快递运输流程有点类似,更详细介绍消息队列的基本原理请往下看,你将知道消息队列是如何将“快递”运往指定的消费者手中的。


1、什么是消息队列?

消息队列的定义: 消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。


简单来说就是提供了一种消息发布和消费排队模型,用于跨平台和进程之间的通信。结合前面快递的例子,可以简单的说就是要有快递公司这样一个角色存在,实现人与人之间收发物品。


2、为什么要用消息队列?

为什么要用消息队列?

▲ 可以实现服务限流,提高服务稳定性(将请求缓冲起来,降低并发请求保障服务正常运行);

 可实现解耦,降低系统耦合度(生产者和消费者解耦设计);

▲ 可用于跨语言通信(消息通信不限制语言)。




3、RabbitMQ基本原理介绍

RabbitMQ是Pivotal公司于2007年发布的一款高性能、分布式的消息中间件,同时实现了AMQP(高级消息队列协议)。他是基于Erlang语言编写的,天生支持高并发,同时支持消息高可靠性,支持从内存持久化消息到磁盘,再从磁盘加载消息到内存。


在介绍RabbitMQ基本原理前,首先得说明下几个名词:

Connection(连接):即客户端与RabbitMQ建立的socket连接。


Channel(信道):为Connection的通信信道,其中一个connection可以对应多个信道。


Exchange(交换机)针对生产者,它指定消息按什么规则,路由到哪个队列。(可以理解为快递发送网点)


Queue(队列): 用于存储消息每个消息都会被投入到一个或多个队列。(可以理解为快递到货网点)


RoutingKey(路由键、主题):用于指定交换机和队列的绑定规则,消息只有主题匹配才会到达指定队列。(可以理解为邮寄地址)


Binding(绑定):用于关联交换机和队列,指定交换机按照路由键的规则进行绑定。(可以理解为快递分拣的规则,比如杭州的分到杭州的网点,上海的分到上海的网点,其中杭州网点和上海网点都是“队列”,寄件的网点就是“交换机”)


3.1发布原型

消息发送的原型

该原型基本概述为生产者将消息投递给exchange,exchange根据routingKey和事先建立好的binding进行匹配,决定将消息发往哪个queue。


3.2消费原型

消息订阅的原型


该原型基本概述为订阅者指定queue去订阅,当queue进入消息后,会立即将消息发送给订阅者(注:如果多个订阅者订阅同一个queue,则消息只会发给其中一个订阅者)。


这里有个小问题,需要思考一下。如果说希望生产者发送一条消息,希望多个订阅者可以消费(消息广播的场景),那么该如何设计呢?


这个问题很简单,既然一个queue只能将消息发给一个订阅者,那多个订阅者就每个人分别订阅一个队列,其中每个队列和exchange建立相同的binding,这样就可以实现消息的广播消费了。但是,要特别注意的是,消息广播是有瓶颈限制的。因为RabbitMQ进行队列广播时,是需要在内存中复制消息的,如果广播的量不大,10、20个这样的,对性能影响不大;但如果是大量的广播,则会急剧降低RabbitMQ的性能。因此MQ并不适合对端推送,相比较而言,更适用于服务间推送。

      

那么了解了消息发布和订阅的基本原理后,是不是对于上面“快递”的例子有点感觉了呢。其实,商家就好比生产者,发快递网点好比交换机,邮寄地址就是路由键,收快递网点就是队列,收件人就是订阅者。这么一对比,是不是觉得其实消息队列的原理跟生活中的快递的例子十分相似。


接下来,我们介绍下RabbitMQ的集群原理。


3.3 普通集群简介

既然RabbitMQ是高可用的消息中间件,那么一定是支持集群部署的。对于RabbitMQ来说,集群分为2种。一种是普通集群,另外一种是镜像队列。


对于普通集群而言,消息队列仅存在一份实体,即队列在哪个节点创建的,则该队列所有的消息就存在哪个节点。其他节点仅同步队列的元数据(如队列的名称,配置,绑定等信息),并不同步消息内容。虽然客户端通过其他节点也能订阅这个队列,但实际上RabbitMQ服务端内部还是通过队列实际所在节点上去临时获取消息,再从订阅的节点推送给订阅端的。因此从现象上看貌似集群每个节点信息是一样的,其实不然。下图为普通集群基本原理图:


需要注意:该种集群模式是不可靠的。如果队列所在节点宕机,则该队列状态为不可用,不能再发送消息进队列,同时其他节点上也无法订阅当前队列。因此不推荐使用普通集群模式。


那么,针对普通集群这一“神坑”的缺点,官方怎么提供解决方案的呢?答案就是“镜像队列”。但是普通集群是不是就没有意义了呢?或者说普通集群模式有什么好处呢?虽然该模式是不可靠的,但是确起到了消息负载均衡的作用。因为节点之间仅同步元数据,因此节点和节点之间分摊了压力,提高了处理性能。对于消息可靠性要求不高的场景,该模式比较适合,因为可以提高消息的性能和吞吐量。


3.4 镜像队列简介

相较于普通集群,镜像队列模式类似于多副本设计模式,即每个节点都存在队列实体,同时消息也在所有节点之间进行同步。在该模式下,即使某个节点宕机了,通过其他节点还是能正常订阅与发布的。因此推荐部署镜像队列模式,保证消息高可靠。下图为镜像队列模式基本原理图:


镜像模式其实就是主备模式,消息同步通过GM(一种可靠组播协议)进行。即生产者发送一条消息,消息经过master发送给slave,slave再发送给下一个slave,最终回到master,形成一个环状同步,因此也被称为“镜像环形队列”。


该模式大致实现原理为master和slave同时存储一份节点的路由表,这样根据路由表进行环状同步。中途如果有新节点加入或退出,则只需要在路由表插入或删除节点信息即可。同时master的选主模式为按照最早启动时间策略,因为RabbitMQ认为最早启动好的那个节点同步的信息是最完整的。


那么该模式下虽然消息可靠性得到了保障,但是缺点也很明显。那就是消息得在所有节点上进行同步,极大的加大了网络带宽的开销;同时也极大的降低了RabbitMQ的性能,因此想提高消息的可靠性,就得以牺牲性能为代价。

恒生技术之眼原创文章,未经授权禁止转载。详情见转载须知

联系我们

恒 生 技 术 之 眼