RabbitMQ集群部署

一般来说,RabbitMQ部署分布式集群架构有如下三种:
1.Cluster
2.Federation
3.Shovel

其中最常用的就是cluster模式(集群),它可以动态增加节点或减少,但只支持同一网段的局域网内的节点。而federation允许单台服务器上或多台服务器组成的集群之间进行消息转发和路由。federation队列类似于单向点对点连接,消息会在整个联合队列之间转发任意次,直到被消费者接收。但是此方式也有弊端,就是无法实现高可用,当其中的某一个节点宕机时就导致服务不可用,这时候就需要引入中间件Hadoop。通常使用federation来连接internet上的中间服务器,用作订阅分发消息或工作队列。shovel与federation类似,不过实现更偏向于底层,它们两者均支持部署广域网节点。下面我们介绍cluster集群模式和部署过程。

一、普通集群
1,概念
RabbitMQ集群部署_第1张图片
上图中的三个节点组成了一个RabbitMQ的集群。其中exchange是交换器,它的元数据信息(交换器名称、交换器属性、绑定键等)在所有节点上都是一致的,而队列中的实际消息数据则只会存在于所创建的那个节点上,其它节点只知道这个队列的元数据信息和一个指向拥有这个消息的队列的节点指针。RabbitMQ集群会同步四种类型的内部元数据:队列元数据(队列名和属性)、交换器元数据(交换器名和属性)、绑定键和虚拟机。在用户访问其中任何一个rabbitmq节点时查询到的queue、user、exchange和vhost等信息都是一致的。那为什么普通集群只保持元数据同步,消息内容却没同步呢?这里涉及到存储空间和性能的问题,如果保持每个节点都有一份消息,那会导致每个节点的空间都非常大,消息的积压量会增加且无法通过扩容节点解决积压问题。另外如果要使每个节点存储一份消息,对于持久化的消息而言,内存和磁盘同步复制机制会导致性能受到很大影响。

2,工作原理
RabbitMQ集群部署_第2张图片
上图中的三个节点,其中节点1是数据节点(即实际存储消息内容的节点),如果此时有客户端(生产者或消费者)与节点1建立了连接,那么关于消息的收发就只在节点1上进行(可以理解为简单的单机模式),而如果此时客户端是与节点2或者节点3建立的连接,此时由于数据在节点1上,那么节点2或节点3只会起到一个消息转发的作用,例如此客户端是消费者,那么消息将由节点2或节点3从节点1中拉取,再经自身节点路由给消费者端;如果此客户端是生产者,那么消息先发给节点2或3,再路由到节点1的队列中存储。
一个节点可以是磁盘节点和内存节点,磁盘节点将元数据存储在磁盘,内存节点将元数据存储在内存,但对于队列和消息的持久化、非持久化而言,磁盘节点和内存节点存储方式无区别:持久化存磁盘,非持久化存内存。这里需要注意的是,内存节点只是将元数据(比如队列名和属性、交换器名和属性和虚拟机等)存储在内存,因此在对资源管理(创建和删除队列、交换器和虚拟机等)时的性能有所提升,但是对发布和订阅的消息速率并没有提升。RabbitMQ要求集群中至少有一个磁盘节点,当节点加入和离开集群时,必须通知磁盘节点(如果集群中唯一的磁盘节点崩溃了,则不能进行创建队列、创建交换器、创建绑定、添加用户、更改权限、添加和删除集群节点)。如果唯一磁盘的磁盘节点崩溃,集群是可以保持运行的,但不能更改任何东西。因此建议在集群中设置两个磁盘节点,只要一个即可正常操作。总之在无法得知它们如何使用才能保证最佳时建议最好都用磁盘节点。

总结:普通集群模式并不能保证服务的高可用,因为其它节点只复制了队列和交换器等元数据信息,并没有将真实的消息内容复制到自身节点。该部署模式只解决了单节点的压力问题,但是当数据节点宕机之后便无法提供服务了,消息的路由线路受到了阻隔,客户端则无法继续与服务交互。为了解决这个问题,就需要此消息数据也能被复制到集群的其它节点中,因此rabbitmq引入了镜像部署模式。

二、镜像集群
1,概念
RabbitMQ集群部署_第3张图片
Rabbitmq的镜像集群实际上是在普通集群的基础上增加了策略,它需要先按照普通集群的方式进行部署,部署完成之后再通过创建镜像队列的策略实现主备节点消息同步。也就是说,每个备用节点都有和主节点一样的队列,这个队列是由主节点通过创建镜像队列所产生的,且这些备用节点能及时的同步主节点中队列的入队消息。当消息设置了持久化时,每个节点都有属于自己的本地消息持久化存储机制。当消息入队和出队时,所有关于对主节点的操作都会同步给备用节点用来更新。此集群模式在主节点宕机之后备用节点所保留的消息与主节点完全一致,即可实现高可用。

2,工作原理
RabbitMQ集群部署_第4张图片
上图就是镜像集群模式的实现流程,其中有三个节点(主节点、备节点1、备节点2)和三个镜像队列queue(其中备节点上的queue是由主节点镜像生成的)。要注意的是,这里的主节点和备节点是针对某个队列而言的,并不能认为一个节点作为了所有队列的主节点,因为在整个镜像集群模式下,会存在多个节点和多个队列,这时候任何一个节点都能作为某一个队列的镜像主节点,其它节点则成了镜像备节点(例如:有A、B、C三个节点和Q1、Q2、Q3三个队列,如果A作为Q1的镜像主节点,那么B和C就作为了Q1的镜像备节点,在此基础上,如果B作为了Q2的镜像主节点,那么A和C就是Q2的镜像备节点)。每一个队列都是由两部分组成的,一个是queue,用来接收消息和发布消息,另外还有一个BackingQueue,它是用来做本地消息持久化处理。客户端发送给主节点队列的消息和ack应答都将会同步到其它备节点上。所有关于镜像主队列(mirror_queue_master)的操作,都会通过组播GM的方式同步到其它备用节点上,这里的GM负责消息的广播,mirror_queue_slave则负责回调处理(更新本次同步内容),因此当消息发送给备用节点时,则由mirror_queue_slave来做实际处理,将消息存储在queue中,如果是持久化消息则同时存储在BackingQueue中。master上的回调则由coordinator来处理(发布本次同步内容)。在主节点中,BackingQueue的存储则是由Queue进行调用。对于生产者而言,消息发送给queue之后,接着调用mirror_queue_master进行持久化处理,之后再通过GM广播发送本次同步消息给备用节点,备用节点通过回调mirror_queue_slave同步本次消息到queue和BackingQueue;对于消费者而言,从queue中获取消息之后,消息队列会等待消费者的ack应答,ack应答收到之后删除queue和BackingQueue中的该条消息,并将本次ack内容通过GM广播发送给备用节点同步本次操作。如果slave宕机了,那对于客户端的服务提供将不会有任何影响。如果master宕机了,则其它备用节点就提升为master继续服务消息不会丢失。那这其中多个备用节点是如何选择其中一个来作为master的呢?这里通过选取出“最年长的”节点作为master,因为这个备用节点相对于其它节点而言是同步时间最长、同步状态最好的一个节点,但如果存在没有任何一个slave与master完全同步的情况,那么master中未同步的消息将会丢失。
GM(Guarenteed Multicast):
GM模块实现的一种可靠的组播通讯协议,该协议能够保证组播消息的原子性,即保证组中活着的节点要么都收到消息要么都收不到。
它的实现大致为:将所有的节点形成一个循环链表,每个节点都会监控位于自己左右两边的节点,当有节点新增时,相邻的节点保证当前广播的消息会复制到新的节点上;当有节点失效时,相邻的节点会接管保证本次广播的消息会复制到下一个节点。在master节点和slave节点上的这些gm形成一个group,group(gm_group)的信息会记录在mnesia中。不同的镜像队列形成不同的group。消息从master节点对应的gm发出后,顺着链表依次传送到所有的节点,由于所有节点组成一个循环链表,master节点对应的gm最终会收到自己发送的消息,这个时候master节点就知道消息已经复制到所有的slave节点了。另外需要注意的是,每一个新节点的加入都会先清空这个节点原有数据,下图是新节点加入集群的一个简单模型:
RabbitMQ集群部署_第5张图片

消息的同步:
将新节点加入已存在的镜像队列,在默认情况下ha-sync-mode=manual,镜像队列中的消息不会主动同步到新节点,除非显式调用同步命令。当调用同步命令后,队列开始阻塞,无法对其进行操作,直到同步完毕。
当ha-sync-mode=automatic时,新加入节点时会默认同步已知的镜像队列。由于同步过程的限制,所以不建议在生产的消费队列中操作。

总结:镜像集群模式通过从主节点拷贝消息的方式使所有节点都能保留一份数据,一旦主节点崩溃,备节点就能完成替换从而继续对外提供服务。这解决了节点宕机带来的困扰,提高了服务稳定性,但是它并不能实现负载均衡,因为每个操作都要在所有节点做一遍,这无疑降低了系统性能。再者当消息大量入队时,集群内部的网络带宽会因此时的同步通讯被大大消耗掉,因此对于可靠性要求高、性能要求不高且消息量并不多的场景比较适用。如果对高可用和负载均衡都有要求的场景则需要结合HAProxy(实现节点间负载均衡)和keepalived(实现HAproxy的主备模式)中间件搭配使用,下面我们将对这种场景的部署进行全流程概述。

三、集群部署
这里准备了三台机器,分别是10.9.100.12、10.9.100.14、10.9.100.33,它们各自都已经部署了rabbitmq,现在我们开始集群部署。

1,集群环境准备
修改host配置:修改配置之前请先停掉三台机器上的rabbitmq服务。在三台机器上分别键入hostname查询出三台主机的主机名分别是:NodeName1、NodeName2、NodeName3,接着修改/etc/hosts文件,需要将三台机器的主机名配置在此文件中,例如在12机器,添加14机器和33机器的hostname,同样地需要在14机器添加12和33机器的hostname,在33机器添加12和14机器的hostname(保证三者均配置),如下:
vim /etc/hosts
RabbitMQ集群部署_第6张图片
拷贝cookie文件
完成上述配置主机流程后,我们接着将12机器的.erlang.cookie文件复制到14和33机器(此文件在rabbitmq服务启动时自动生成),因为节点之间需要通过此文件来判断是否允许交流(判断是否属于集群内部节点),如果三台机器的此文件内容不一致则集群无法启动成功。此文件内容一般是由不超过255个数字或字母组成的字符串构成(通常保存在/目录下)。在12机器键入如下命令将12机器的cookie文件拷贝到14和33机器上(这里机器的/目录和根目录/一致):
scp ~/.erlang.cookie [email protected]:/
scp ~/.erlang.cookie [email protected]:/

至此,我们的配置工作完成!接下来就可以启动集群了

2,集群常用命令
集群启动之前先要将三台机器的rabbitmq单独启动,在rabbitmq的sbin目录使用如下命令:
./rabbitmq-server -detached
启动集群:
上述三个节点各自启动之后选择其中一个节点作为基准,将其它节点逐步加入到此基准节点下就形成了集群,这里我们选择12机器的节点作为基准,将14机器的节点和33机器的节点加入集群,在14和33机器上分别依次执行如下命令:
./rabbitmqctl stop_app
#先停止服务(注:这里的参数stop_app和stop是不一样的,stop是停掉服务,stop_app是停掉这个节点,但是并没有停止rabbitmq依赖的erlang进程
./rabbitmqctl reset
# 重置状态(注:这里重置节点将会把此节点下的所有数据全部清除,包括队列、交换器、虚拟主机和用户等)
./rabbitmqctl join_cluster rabbit@NodeName1
# 节点加入集群, NodeName2加入到NodeName1, NodeName3加入NodeName2(也可以加入NodeName1,只要是集群中正在运行的一个节点就可以),初次加入集群必须通过这种加入基准节点的方式。
./rabbitmqctl start_app
# 启动服务

查询集群状态:
在另外两台机器完成上述四个命令后集群的启动就算成功了,我们可以查询一下集群的状态,在任何一台机器上的rabbitmq的sbin目录键入如下命令:
./rabbitmqctl cluster_status

通过如下命令可以修改集群的名称(这里修改成rabbitmq_cluster):
./rabbitmqctl set_cluster_name rabbitmq_cluster

开启镜像队列
这一步才是我们搭建镜像集群的关键,需要说明的是,镜像的含义是对队列设置镜像,而不是针对节点设置镜像。例如在12机器,我们执行如下命令表示对12机器NodeName1节点的所有队列,设置它的镜像到14和33机器上,这样的话另外两台机器上也存在和12机器节点上一模一样的队列了,它是由12机器节点的队列镜像生成而来的,在rabbitmq的sbin目录键入如下命令:
./rabbitmqctl set_policy ha-all “^” '{“ha-mode”:“all”}'
在上面我们指定了 ha-mode 的值为 all,代表消息会被同步到所有节点的相同队列中,ha-all表示策略名称(可自定义)。这里我们之所以这样配置,因为我们本身只有三个节点,因此复制操作的性能开销比较小。如果你的集群有很多节点,那么此时复制的性能开销就比较大,此时需要选择合适的复制系数。通常可以遵循过半写原则,即对于一个节点数为 n 的集群,只需要同步到 n/2+1 个节点上即可。此时需要同时修改镜像策略为 exactly,并指定复制系数 ha-params,示例命令如下:
./rabbitmqctl set_policy ha-two “^” ‘{“ha-mode”:”exactly”,“ha-params”:”2”,“ha-sync-mode”:”automatic”}’
除此之外,RabbitMQ 还支持使用正则表达式来过滤需要进行镜像操作的队列,下面的示例是将所有队列名以ha开头的设置为镜像,如下:
./rabbitmqctl set_policy ha-all “^ha.” ‘{“ha-mode”:“all”}’
官方使用参考说明如下:
https://www.rabbitmq.com/ha.html

停止集群
上述说明了集群启动的方式,接着我们介绍集群的停止,集群是不能通过一个命令完全停止的,它必须对各个节点依次停止才能最终停止集群,而且基准节点要作为最后一个停止,先停止其它加入到基准节点的节点,例如,我们上述将14机器和33机器的节点加入到12机器的基准节点,那么停止时需要先停止14机器和33机器的节点,最后再停止12机器节点(注:集群启动时就与此顺序相反,先启动12机器的节点,再启动14和33的节点),按照顺序执行如下:
./rabbitmqctl stop
按照33、14、12的顺序执行此条命令即可停止整个集群。

重启/重置节点
重启某一个节点执行步骤如下:
./rabbitmqctl stop
./rabbitmq-server -detached

注:重启节点实际上就是先执行停止,再启动即可,不需要再次加入到集群节点中,启动之后节点自动会加入到集群中(初次加入集群就需要加入某一个节点)。重置某一个节点,就是先停止该节点,然后执行reset命令清除所有数据,接着加入到集群中并启动,如下:

./rabbitmqctl stop_app
./rabbitmqctl reset
./rabbitmqctl join_cluster rabbit@NodeName
./rabbitmqctl start_app

注:重启过程中,节点停止后主节点同步过来的数据将无法保存在此节点,同理重置过程中,节点停止后也将无法获取同步的数据,reset重置还会将此节点原来所有的数据(队列、交换器、虚拟机、用户和同步过来的消息等)全部清除,直到重新加入集群、重启节点后可再次同步数据。

拆分集群节点(删除节点)
有时需要将集群中的某些节点拆分,或者说是删除集群中的某一个节点,可以在本机依次执行重置命令即可将本机节点从集群分离开:
./rabbitmqctl stop_app
./rabbitmqctl reset
./rabbitmqctl start_app

当我们在33机器执行完上述命令后,再次查询集群状态发现此时它是一个单独的集群,并不在之前的集群中。另外在本机也可以通过如下命令删除集群中的其它节点,例如我们在12机器剔除14机器的节点(注意:12机器作为主节点 ,不能随意删除),先在14机器上停止该节点:
./rabbitmqctl stop_app
接着在12机器删除该节点,如下:
./rabbitmqctl forget_cluster_node rabbit@NodeName2

最后我们再在14机器执行启动该节点,发现加入不了12机器的节点了,如图:
报错信息是12机器的节点已经不同意它加入了,因为它已经被删除了。只能再重置此节点和再次启动,如下:
./rabbitmqctl reset
./rabbitmqctl start_app

操作过后14机器的节点也变成了单独的一个集群,至此这个集群已经被拆分成了三个单独的集群,每个节点都是一个集群。如果需要再次聚合成一个集群,先在12机器主节点下执行重置的步骤,如下:
./rabbitmqctl stop_app
./rabbitmqctl reset
./rabbitmqctl start_app

然后再在14和33机器执行停止节点、加入主节点和启动节点即可再次聚合成一个集群,如下:
./rabbitmqctl stop_app
./rabbitmqctl join_cluster rabbit@NodeName1
./rabbitmqctl start_app

创建内存节点
前面说到内存节点和磁盘节点的区别,当我们把一个节点加入到集群中时,默认是以磁盘节点加入集群的,如果要以内存节点加入集群,只需要在加入集群时带上–ram参数就行了,如下:
./rabbitmqctl join_cluster --ram rabbit@NodeName1

改变节点类型
通过改变节点类型可以将磁盘节点改为内存节点,也可以将内存节点改为磁盘节点,如下:
./rabbitmqctl stop_app
./rabbitmqctl change_cluster_node_type disc

# disc是磁盘节点,ram是内存节点
./rabbitmqctl start_app

以上就是镜像集群的一些常用命令了,如需了解更多命令可登录官网进行学习:
https://www.rabbitmq.com/rabbitmqctl.8.html
至此镜像集群已经部署成功,后续可以部署中间件haproxy和keepalived实现集群的高可用+负载均衡。

你可能感兴趣的:(RabbitMQ,rabbitmq,MQ,分布式)