消息队列之RabbitMQ-分布式部署
RabbitMQ分布式部署有3种方式:
Federation与Shovel都是以插件的形式来实现,复杂性相对高,而集群是RabbitMQ的自带属性,相对简单。
这三种方式并不是互斥的,可以根据需求选择相互组合来达到目的。
RabbitMQ本身是基于Erlang编写,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的magic cookie来实现)。
因此,RabbitMQ天然支持Clustering。这使得RabbitMQ本身不需要像ActiveMQ、Kafka那样通过ZooKeeper分别来实现HA方案和保存集群的元数据。集群是保证可靠性的一种方式,同时可以通过水平扩展以达到增加消息吞吐量能力的目的。
我们把部署RabbitMQ的机器称为节点,也就是broker。broker有2种类型节点: 磁盘节点 和 内存节点 。顾名思义,磁盘节点的broker把元数据存储在磁盘中,内存节点把元数据存储在内存中,很明显,磁盘节点的broker在重启后元数据可以通过读取磁盘进行重建,保证了元数据不丢失,内存节点的broker可以获得更高的性能,但在重启后元数据就都丢了。
元数据包含以下内容:
单节点系统必须是磁盘节点 ,否则每次你重启RabbitMQ之后所有的系统配置信息都会丢失。
集群中至少有一个磁盘节点 ,当节点加入和离开集群时,必须通知磁盘 节点。
如果集群中的唯一一个磁盘节点,结果这个磁盘节点还崩溃了,那会发生什么情况?集群依然可以继续路由消息(因为其他节点元数据在还存在),但无法做以下操作:
也就是说,如果唯一磁盘的磁盘节点崩溃, 集群是可以保持运行的,但不能更改任何东西 。为了增加可靠性,一般会在集群中设置两个磁盘节点,只要任何一个处于工作状态,就可以保障集群的正常服务。
RabbitMQ的集群模式分为两种: 普通模式 与 镜像模式 。
普通模式,也是默认的集群模式。
对于Queue来说, 消息实体只存在于其中一个节点 ,A、B两个节点仅有相同的元数据,即队列结构。当消息进入A节点的Queue中后,consumer从B节点拉取时,RabbitMQ会临时在A、B间进行消息传输,把A中的消息实体取出并经过B发送给consumer。所以consumer应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理Queue。否则无论consumer连A或B,出口总在A,会产生瓶颈。
队列所在的节点称为 宿主节点 。
队列创建时,只会在宿主节点创建队列的进程,宿主节点包含完整的队列信息,包括元数据、状态、内容等等。因此, 只有队列的宿主节点才能知道队列的所有信息 。
队列创建后,集群只会同步队列和交换器的元数据到集群中的其他节点,并不会同步队列本身,因此 非宿主节点就只知道队列的元数据和指向该队列宿主节点的指针 。
假如现在一个客户端需要对Queue A进行发布或者订阅,发起与集群的连接,有两种可能的场景:
由于节点之间存在路由转发的情况,对延迟非常敏感,应当只在本地局域网内使用,在广域网中不应该使用集群,而应该用Federation或者Shovel代替。
这样的设计,保证了不论从哪个broker中均可以消费所有队列的数据,并分担了负载,因此,增加broker可以线性提高服务的性能和吞吐量。
但该方案也有显著的缺陷,那就是 不能保证消息不会丢失 。当集群中某一节点崩溃时,崩溃节点所在的队列进程和关联的绑定都会消失,附加在那些队列上的消费者也会丢失其订阅信息,匹配该队列的新消息也会丢失。比如A为宿主节点,当A节点故障后,B节点无法取到A节点中还未消费的消息实体。如果做了消息持久化,那么得等A节点恢复,然后才可被消费;如果没有持久化的话,然后就没有然后了……
肯定有不少同学会问,想要实现HA方案,那将RabbitMQ集群中的所有Queue的完整数据在所有节点上都保存一份不就可以了吗?比如类似MySQL的主主模式,任何一个节点出现故障或者宕机不可用时,那么使用者的客户端只要能连接至其他节点,不就能够照常完成消息的发布和订阅吗?
RabbitMQ这么设计是基于性能和存储空间上来考虑:
引入 镜像队列 (Mirror Queue)的机制,可以将队列镜像到集群中的其他Broker节点之上,如果集群中的一个节点失效了,队列能够自动切换到镜像中的另一个节点上以保证服务的可用性。
一个镜像队列中包含有1个主节点master和若干个从节点slave。其主从节点包含如下几个特点:
该模式和普通模式不同之处在于,消息实体会主动在镜像节点间同步,而不是在consumer取数据时临时拉取。该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉。所以在对可靠性要求较高的场合中适用。
一个队列想做成镜像队列,需要先设置policy,然后客户端创建队列的时候,rabbitmq集群根据队列名称自动设置为普通队列还是镜像队列。
镜像队列的配置通过添加policy完成,policy添加的命令为:
例如,对队列名称以hello开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
通常队列由两部分组成:一部分是AMQQueue,负责AMQP协议相关的消息处理,即接收生产者发布的消息、向消费者投递消息、处理消息confirm、acknowledge等等;另一部分是BackingQueue,它提供了相关的接口供AMQQueue调用,完成消息的存储以及可能的持久化工作等。
镜像队列基本上就是一个特殊的BackingQueue,它内部包裹了一个普通的BackingQueue做本地消息持久化处理,在此基础上增加了将消息和ack复制到所有镜像的功能。所有对mirror_queue_master的操作,会通过组播GM(下面会讲到)的方式同步到各slave节点。GM负责消息的广播,mirror_queue_slave负责回调处理,而master上的回调处理是由coordinator负责完成。mirror_queue_slave中包含了普通的BackingQueue进行消息的存储,master节点中BackingQueue包含在mirror_queue_master中由AMQQueue进行调用。
消息的发布(除了Basic.Publish之外)与消费都是通过master节点完成。master节点对消息进行处理的同时将消息的处理动作通过GM广播给所有的slave节点,slave节点的GM收到消息后,通过回调交由mirror_queue_slave进行实际的处理。
GM(Guarenteed Multicast) 是一种可靠的组播通讯协议,该协议能够保证组播消息的原子性,即保证组中活着的节点要么都收到消息要么都收不到。它的实现大致如下:
将所有的节点形成一个循环链表,每个节点都会监控位于自己左右两边的节点,当有节点新增时,相邻的节点保证当前广播的消息会复制到新的节点上;当有节点失效时,相邻的节点会接管保证本次广播的消息会复制到所有的节点。在master节点和slave节点上的这些gm形成一个group,group(gm_group)的信息会记录在mnesia中。不同的镜像队列形成不同的group。消息从master节点对于的gm发出后,顺着链表依次传送到所有的节点,由于所有节点组成一个循环链表,master节点对应的gm最终会收到自己发送的消息,这个时候master节点就知道消息已经复制到所有的slave节点了。
slave节点先从gm_group中获取对应group的所有成员信息,然后随机选择一个节点并向这个节点发送请求,这个节点收到请求后,更新gm_group对应的信息,同时通知左右节点更新邻居信息(调整对左右节点的监控)及当前正在广播的消息,然后回复通知请求节点成功加入group。请求加入group的节点收到回复后再更新rabbit_queue中的相关信息,并根据需要进行消息的同步。
当slave节点失效时,仅仅是相邻节点感知,然后重新调整邻居节点信息、更新rabbit_queue、gm_group的记录等。如果是master节点失效,"资格最老"的slave节点被提升为master节点,slave节点会创建出新的coordinator,并告知gm修改回调处理为coordinator,原来的mirror_queue_slave充当amqqueue_process处理生产者发布的消息,向消费者投递消息等。
上面提到如果是slave节点失效,只有相邻的节点能感知到,那么master节点失效是不是也是只有相邻的节点能感知到?假如是这样的话,如果相邻的节点不是"资格最老"的节点,怎么通知"资格最老"的节点提升为新的master节点呢?
实际上,所有的slave节点在加入group时,mirror_queue_slave进程会对master节点的amqqueue_process进程(也可能是mirror_queue_slave进程)进行监控,如果master节点失效的话,mirror_queue_slave会感知,然后再通过gm进行广播,这样所有的节点最终都会知道master节点失效。当然,只有"资格最老"的节点会提升自己为新的master。
消息从master节点发出,顺着节点链表发送。在这期间,所有的slave节点都会对消息进行缓存,当master节点收到自己发送的消息后,会再次广播ack消息,同样ack消息会顺着节点链表经过所有的slave节点,其作用是通知slave节点可以清除缓存的消息,当ack消息回到master节点时对应广播消息的生命周期结束。
下图为一个简单的示意图,A节点为master节点,广播一条内容为"test"的消息。"1"表示消息为广播的第一条消息;"id=A"表示消息的发送者为节点A。右边是slave节点记录的状态信息。
为什么所有的节点都需要缓存一份发布的消息呢?
master发布的消息是依次经过所有slave节点,在这期间的任何时刻,有可能有节点失效,那么相邻的节点可能需要重新发送给新的节点。例如,A->B->C->D->A形成的循环链表,A为master节点,广播消息发送给节点B,B再发送给C,如果节点C收到B发送的消息还未发送给D时异常结束了,那么节点B感知后节点C失效后需要重新将消息发送给D。同样,如果B节点将消息发送给C后,B,C节点中新增了E节点,那么B节点需要再将消息发送给新增的E节点。
配置镜像队列的时候有个 ha-sync-mode 属性,这个有什么用呢?
新节点加入到group后,最多能从左边节点获取到当前正在广播的消息内容,加入group之前已经广播的消息则无法获取到。如果此时master节点不幸失效,而新节点有恰好成为了新的master,那么加入group之前已经广播的消息则会全部丢失。
注意:这里的消息具体是指新节点加入前已经发布并复制到所有slave节点的消息,并且这些消息还未被消费者消费或者未被消费者确认。如果新节点加入前,所有广播的消息被消费者消费并确认了,master节点删除消息的同时会通知slave节点完成相应动作。这种情况等同于新节点加入前没有发布任何消息。
避免这种问题的解决办法就是对新的slave节点进行消息同步。当 ha-sync-mode 配置为自动同步(automatic)时,新节点加入group时会自动进行消息的同步;如果配置为manually则需要手动操作完成同步。
Federation直译过来是联邦,它的设计目标是使 RabbitMQ 在不同的 Broker 节点之间进行消息传递而无须建
立集群。具有以下特点:
那么它到底有什么用呢?我们可以从一个实际场景入手:
有两个服务分别部署在国内和海外,它们之间需要通过消息队列来通讯。
很明显无论RabbitMQ部署在海外还是国内,另一方一定得忍受连接上的延迟。因此我们可以在海外和国内各部署一个MQ,这样一来海外连接海外的MQ,国内连接国内,就不会有连接上的延迟了。
但这样还会有问题,假设某生产者将消息存入海外MQ中的某个队列 queueB , 在国内的服务想要消费 queueB 消息,消息的流转及确认必然要忍受较大的网络延迟 ,内部编码逻辑也会因这一因素变得更加复杂。
此外,服务可能得维护两个MQ的配置,比如国内服务在生产消息时得使用国内MQ,消费消息时得监听海外MQ的队列,降低了系统的维护性。
可能有人想到可以用集群,但是RabbitMQ的集群对延迟非常敏感,一般部署在局域网内,如果部署在广域网可能会产生网络分区等等问题。
这时候,Federation就派上用场了。它被设计成能够容忍不稳定的网络连接情况,完全能够满足这样的场景。
那使用Federation之后是怎样的业务流程呢?
首先我们在海外MQ上定义exchangeA,它通过路由键“rkA”绑定着queueA。然后用Federation在exchangeA上建立一条 单向 连接到国内RabbitMQ,Federation则自动会在国内RabbitMQ建立一个exchangeA交换器(默认同名)。
这时候,如果部署在国内的client C在国内MQ上publish了一条消息,这条消息会通过 Federation link 转发到海外MQ的交换器exchangeA中,最终消息会存入与 exchangeA 绑定的队列 queueA 中,而client C也能立即得到返回。
实际上,Federation插件还会在国内MQ建立一个内部的交换器:exchangeA→ broker3 B(broker3是集群名),并通过路由键 "rkA"将它和国内MQ的exchangeA绑定起来。接下来还会在国内MQ上建立一个内部队列federation: exchangeA->broker3 B,并与内部exchange绑定。这些操作都是内部的,对客户端来说是透明的。
值得一提的是,Federation的连接是单向的,如果是在海外MQ的exchangeA上发送消息是不会转到国内的。
这种在exchange上建立连接进行联邦的,就叫做 联邦交换器 。一个联邦交换器接收上游(upstream)的信息,这里的上游指的是其他的MQ节点。
对比前面举的例子,国内MQ就是上游,联邦交换器能够将原本发送给上游交换器的消息路由到本地的某个队列中。
有联邦交换器自然也有联播队列,联邦队列则允许一个本地消费者接收到来自上游队列的消息 。
如图,海外MQ有队列A,给其设置一条链接,Federation则自动会在国内RabbitMQ建立一个队列A(默认同名)。
当有消费者 ClinetA连接海外MQ并消费 queueA 中的消息时,如果队列 queueA中本身有若干消息堆积,那么 ClientA直接消费这些消息,此时海外MQ中的queueA并不会拉取国内中的 queueA 的消息;如果队列 queueA中没有消息堆积或者消息被消费完了,那么它会通过 Federation link 拉取上游队列 queueA 中的消息(如果有消息),然后存储到本地,之后再被消费者 ClientA进行消费 。
首先开启Federation 功能:
值得注意的是,当需要在集群中使用 Federation 功能的时候,集群中所有的节点都应该开启 Federation 插件。
接下来我们要配置两个东西:upstreams和Policies。
每个 upstream 用于定义与其他 Broker 建立连接的信息。
通用参数如下:
然后定义一个 Policy, 用于匹配交换器:
^exchange 意思是将匹配所有以exchange名字开头的交换器,为它们在上游创建连接。这样就创建了一个 Federation link。
Shovel是RabbitMQ的一个插件, 能够可靠、持续地从一个Broker 中的队列(作为源端,即source )拉取数据并转发至另一个Broker 中的交换器(作为目的端,即destination )。作为源端的队列和作为目的端的交换器可以同时位于同一个 Broker 上,也可以位于不同的 Broker 上。
使用Shovel有以下优势:
使用Shovel时,通常源为队列,目的为交换器:
但是,也可以源为队列,目的为队列。实际也是由交换器转发,只不过这个交换器是默认交换器。配置交换器做为源也是可行的。实际上会在源端自动新建一个队列,消息先存在这个队列,再被Shovel移走。
使用Shovel插件命令:
Shovel 既可以部署在源端,也可以部署在目的端。有两种方式可以部署 Shovel:
其主要差异如下:
来看一个使用Shovel治理消息堆积的案例。
当某个队列中的消息堆积严重时,比如超过某个设定的阈值,就可以通过 Shovel 将队列中的消息移交给另一个集群。
一文带你了解消息队列RabbitMQ安装和使用
了解RabbitMQ的安装和使用,它是基于AMQP协议的消息队列服务器,以高吞吐量和低延时闻名。RabbitMQ主要由以下核心模块构成:Broker(消息代理)、Producer(消息生产者)、Consumer(消息消费者)、Connection(连接)、Channel(通道)、Exchange(交换机)、Queue(队列)和Binding(绑定)等,它们共同构建了消...
消息队列之RabbitMQ-分布式部署
如图,海外MQ有队列A,给其设置一条链接,Federation则自动会在国内RabbitMQ建立一个队列A(默认同名)。 当有消费者 ClinetA连接海外MQ并消费 queueA 中的消息时,如果队列 queueA中本身有若干消息堆积,那么 ClientA直接消费这些消息,此时海外MQ中的queueA并不会拉取国内中的 queueA 的消息;如果队列 queueA中没有消...
消息队列之zeroMQ、rabbitMQ、kafka
用了消息队列系统出现问题排查的范围就变大、需要考虑消息队列导致的问题。 本文说明主流的消息队列,针对使用过的zeroMQ和rabbitMQ、Kakfa: zeroMQ :C语言开发,号称最快的消息队列,本着命名zero的含义,中油中间架构使用简单,表面上是基于socket的封装套接字API,在多个节点应用场景下非常灵活、架构的可扩展性很强, 实...
第1篇:部署RabbitMQ消息队列服务
部署RabbitMQ消息队列服务 消息队列(MQ)是中大型分布式系统内不同模块间通讯的重要方式,主要功能在于缓存并转发消息,提高系统容错性和维护性。在业务流程中引入MQ,如商城系统,库存、支付、物流子系统会通过MQ与MQ服务器连接。将非即时操作异步处理,大幅降低服务器响应时间,提高吞吐量。MQ提供削峰填谷功...
rabbit入门教程
在RabbitMQ中,消息是通过队列来传递的。你可以使用RabbitMQ的命令行界面或者AMQP客户端来创建队列。例如,使用命令行界面,你可以执行以下命令来创建一个名为“myqueue”的队列:rabbitmqadmin-V-fjson create myqueue。4、发布消息 一旦你创建了一个队列,你就可以开始发布消息了。你可以使用RabbitMQ的...
RabbitMQ 消息队列
若没有特别设定,消息一旦被队列分发给消费者,就被 Rabbitmq 从内存中删除。 在这种情况下,如果将一个正在处理消息的消费者强行关闭,那么,消息将未被完全处理,且 RabbitMQ 完全不知情。 为了解决上述问题,可以配置使得消息处理完后,向 RabbitMQ 返回一个 Acknowledgment 。 Rabbit...
rabbitmq消息队列介绍
rabbitmq是建立在AMQP上的企业消息系统。以生产者消费者为模型而存在的一个消息队列.1、解耦 这是一个天然的解耦,实现了应用程序不再通过接口,你只需要调用消息队列的接口把结果存放在消息队列即可。2、异步 一个同步的程序执行,通过消息队列,即可实现异步操作,而不必等待结果返回。在应对一些大并发中...
对于NodeJS如何操作消息队列RabbitMQ的分析
消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。二. 常用的消息队列有哪些?RabbitMQ、...
redis与rabbitmq做消息队列的区别
rabbitmq:队列,每条消息都可以选择性持久化,持久化粒度更小,更灵活;队列监控 rabbitmq实现了后台监控平台,可以在该平台上看到所有创建的队列的详细情况,良好的后台管理平台可以方面我们更好的使用;redis没有所谓的监控平台。总结 redis:轻量级,低延迟,高并发,低可靠性;rabbitmq:重量级,高可靠,...
RabbitMQ原理
消费者:接收并处理消息的客户端,通过消息队列获取信息。Server\/Broker:负责连接管理,实现AMQP实体服务,如虚拟主机vhost,它提供逻辑隔离并管理队列、交换器等。虚拟主机:逻辑隔离的容器,拥有独立的权限和资源,RabbitMQ默认vhost为“\/”。消息:由Properties和Body组成,用于服务器和应用程序间的通信。Exc...
佐秒复方: 消息队列的作用:数据分发,缓存数据,一份数据拷贝出N份供别人使用. 适用场景:常用于一个生产者多个消费者的场景. 也可以是多个消费者一个生产者.意义:说白了就是产生数据的和消费数据的解耦合.就像是个大鱼塘,放鱼的不用管抓鱼的.抓鱼的也不用管放鱼的.你的问题: 这种场景通常因业务而产生 这说明你看的知识课本,或者简单的例子. 如果上面的说明还是没有说清楚,那么只能说明你查阅的资料还不够,或者没有专门查他的应用场景.
定远县15118251269: 如何使用NODEJS+REDIS开发一个消息队列 - ?
佐秒复方: MQ全称为Message Queue, 消息队列(MQ)是一种应用程 序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们>.消 息传递指的是程序之间通过在消息中发送数据进行通...
定远县15118251269: RabbitMQ 可以用来做 RPC 吗 - ?
佐秒复方: 这里要说明事情有以下几点:1.RabbitMQ作为消息队列中间件,就设计成进行保证消息被可靠传递,所以才会有上述“RabbitMQ会将消息投递到下一个consumer客户端”的行为.这个默认行为要想更改,要么去改 RabbitMQ 服务端代码,要么...
定远县15118251269: RabbitMQ怎样能实现多个队列由一个消费者来接收消息 - ?
佐秒复方: 您好,很高兴为您解答. declare一个队列,置AMQP_PASSIVE标志位,就不会影响服务端状态,并返回消息计数. $conn = new AMQPConnection();//...$queue = new AMQPQueue($conn);$queue->setFlags(AMQP_PASSIVE);$messageCount = $queue->declare
定远县15118251269: springcloud 怎么利用rabbitmq - ?
佐秒复方:RabbitMQ 是实现 AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗.消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然.
定远县15118251269: 消息队列是一种用于存储和传递数据的中间件,用于解耦发送... - 上学吧?
佐秒复方: rabbitmq后台进程有哪些 MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过写和检索出入列队的针对应用程序的数据(消息)来通信,而无需专用连接来链接它们.消 息传递指的是程序之间通...
定远县15118251269: rabbitmq 控制界面怎么用 - ?
佐秒复方: rabbitmq 编辑 MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过写和检索出入列队的针对应用程序的数据(消息)来通信,而无需专用连接来链接它们.消 息传递指的是程序之间通过在消息中发送数据进行通...
定远县15118251269: 如何编程rabbitmq 如何删除队列中的消息 - ?
佐秒复方: 可以使用 Purge 方法清除“消息队列”系统中您有权访问的任何队列的内容.例如,假设在本地“消息队列”客户端上使用日记队列记录送出的所有消息的副本.当日记达到其大小上限时,您可以使用 Purge 方法清除不再需要的项
定远县15118251269: 到底什么是消息队列?Java中如何实现消息队列 - ?
佐秒复方: “消息队列”是在消息的传输过程中保存消息的容器.和我们学过的LinkedHashMap,TreeSet等一样,都是容器.既然是容器,就有有自己的特性,就像LinkedHashMap是以键值对存储.存取顺序不变.而消息队列,看到队列就可以知道.这个...