一、关键概念
1.1 元数据
元数据包含以下内容:
-
queue元数据:queue名称、属性
-
exchange元数据:exchange名称、类型、属性
-
binding元数据:exchange和queue之间、exchange和exchange之间的绑定关系
-
vhost元数据:vhost内部的命名空间、安全属性数据等
因此,当用户访问其中任何一个RabbitMQ节点时,通过rabbitmqctl查询到的queue/user/exchange/vhost等信息都是相同的。
1.2 节点类型:磁盘节点 和 内存节点
我们把部署RabbitMQ的机器称为节点,也就是broker。broker有2种类型节点:磁盘节点和内存节点。
- 磁盘节点:只是将元数据存在磁盘中
- 内存节点:只是将元数据存在内存中
队列(queue),消息(message)的持久化或非持久化,与节点是磁盘节点/内存节点没有任何关系。
集群添加或者删除节点时,会把变更通知到至少一个磁盘节点。在内存节点重启后,会先从磁盘节点中同步元数据,内存节点中唯一存储到磁盘的是磁盘节点的地址。
二、RabbitMQ集群的原理
背景:RabbitMQ这款消息队列中间件产品本身是基于Erlang编写,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的magic cookie来实现)。因此,RabbitMQ天然支持Clustering。这使得RabbitMQ本身不需要像ActiveMQ、Kafka那样通过ZooKeeper分别来实现HA方案和保存集群的元数据。集群是保证可靠性的一种方式,同时可以通过水平扩展以达到增加消息吞吐量能力的目的。
RabbitMQ分布式部署有3种方式:集群、Federation(联邦)和Shovel(铲)。这三种方式并不是互斥的,可以根据需求选择相互组合来达到目的,后两者都是以插件的形式进行设计,复杂性相对高,此篇只聊一下RabbitMQ自带的内建集群。
下面先来看下三个节点组成了一个RabbitMQ集群:
- Exchange(交换器)的元数据,Queue(队列)的元数据信息在所有节点上是一致的
- 而Queue的完整数据则只会存在于它所创建的那个节点上。其他节点只知道这个queue的metadata信息和一个指向queue的owner node的指针。
队列所在的节点称为宿主节点:
- 队列创建时,只会在宿主节点创建队列的进程,宿主节点包含完整的队列信息,包括元数据、状态、内容等等。因此,只有队列的宿主节点才能知道队列的所有信息。
- 队列创建后,集群只会同步队列和交换器的元数据到集群中的其他节点,并不会同步队列本身,因此非宿主节点就只知道队列的元数据和指向该队列宿主节点的指针。
场景1、客户端直接连接“队列所在节点broker”
如果有一个消息生产者或者消息消费者通过amqp-client的客户端连接至节点brokerA进行消息的发布或者订阅,那么此时的集群中的消息收发只与节点brokerA相关,这个没有任何问题;如果客户端相连的是节点B或者节点C(队列A数据不在该节点上),那么情况又会是怎么样呢?
场景2、客户端连接的是“非队列数据所在节点broker”
如果消息生产者所连接的是节点B或者节点C,此时队列A的完整数据不在该两个节点上,那么在发送消息过程中这两个节点主要起了一个路由转发作用,根据这两个节点上的元数据(也就是上文提到的:指向queue的owner node的指针)转发至节点A上,最终发送的消息还是会存储至节点A的队列A上。
优缺点
- 优点:这样的设计,保证了不论从哪个broker中均可以消费所有队列的数据,并分担了负载,因此,增加broker可以线性提高服务的性能和吞吐量。
- 缺点:但该方案也有显著的缺陷,那就是不能保证消息不会丢失。当集群中某一节点崩溃时,崩溃节点所在的队列进程和关联的绑定都会消失,附加在那些队列上的消费者也会丢失其订阅信息,匹配该队列的新消息也会丢失。崩溃节点重启后,需要从磁盘节点中同步元数据信息,并重建队列,所以,集群中要求必须至少有一个broker为磁盘节点,以保证集群的可用性。
解决消息可能丢失的问题
RabbitMQ采用镜像队列的方式保证队列的可靠性。镜像队列就是将队列镜像到集群中的其他节点,如果集群中一个节点失效了,队列就能自动的切换到镜像中的节点,一个镜像队列中包含有1个主节点master和若干个从节点slave。其主从节点包含如下几个特点:
-
消息的读写都是在master上进行,并不是读写分离
-
master接收命令后会向salve进行组播,salve会命令执行顺序执行
-
master失效,根据slave加入的时间,最老的slave会被提升为master
-
互为镜像的是队列,并非节点,集群中可以不同节点可以互为镜像队列,也就是说队列的master可以分布在不同的节点上
三、为何RabbitMQ集群仅采用元数据同步的方式
原因有以下两个:
- 存储空间的考虑:如果每个节点都拥有所有队列的完全拷贝,这样新增节点不能达到新增存储空间的目的,集群的消息积压能力会非常弱;
- 性能的考虑:如果每条消息都需要完整拷贝到每一个集群节点,那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟。对于持久化消息,网络和磁盘同步复制的开销都会明显增加。
四、集群中唯一一个磁盘节点崩溃了会发生什么情况?
如果磁盘节点崩溃了,集群依然可以继续路由消息(因为其他节点元数据在还存在),但无法做以下操作:
-
创建队列、交换器、绑定
-
添加用户
-
更改权限
-
添加、删除集群节点
也就是说,唯一的磁盘节点崩溃后,为了保证可用性,禁用了和元数据相关的添加、修改和删除操作。所以建立集群的时候,建议保证有2个以上的磁盘节点。
五、集群节点停止顺序有要求吗?
RabbitMQ 对集群的停止的顺序是有要求的,应该先关闭内存节点,最后再关闭磁盘节点。如果顺序恰好相反的话,可能会造成消息的丢失。
集群添加或者删除节点时,会把变更通知到至少一个磁盘节点。在内存节点重启后,会先从磁盘节点中同步元数据,内存节点中唯一存储到磁盘的是磁盘节点的地址。
六、RabbitMQ集群的搭建
(1)搭建RabbitMQ集群所需要安装的组件
在搭建RabbitMQ集群之前有必要在每台虚拟机上安装如下的组件包,分别如下:
a.Jdk 1.8
b.Erlang运行时环境,这里用的是otp_src_19.3.tar.gz (200MB+)
c.RabbitMq的Server组件,这里用的rabbitmq-server-generic-unix-3.6.10.tar.gz
关于如何安装上述三个组件的具体步骤,已经有不少博文对此进行了非常详细的描述,那么本文就不再赘述了。有需要的同学可以具体参考这些步骤来完成安装。
(2)搭建10节点组成的RabbitMQ集群
该节中主要展示的是集群搭建,需要确保每台机器上正确安装了上述三种组件,并且每台虚拟机上的RabbitMQ的实例能够正常启动起来。
a.编辑每台RabbitMQ的cookie文件,以确保各个节点的cookie文件使用的是同一个值,可以scp其中一台机器上的cookie至其他各个节点,cookie的默认路径为/var/lib/rabbitmq/.erlang.cookie或者$HOME/.erlang.cookie,节点之间通过cookie确定相互是否可通信。
b.配置各节点的hosts文件( vim /etc/hosts)
xxx.xxx.xxx.xxx rmq-broker-test-1
xxx.xxx.xxx.xxx rmq-broker-test-2
xxx.xxx.xxx.xxx rmq-broker-test-3
......
xxx.xxx.xxx.xxx rmq-broker-test-10
c.逐个节点启动RabbitMQ服务
rabbitmq-server -detached
d.查看各个节点和集群的工作运行状态
rabbitmqctl status, rabbitmqctl cluster_status
e.以rmq-broker-test-1为主节点,在rmq-broker-test-2上:
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@rmq-broker-test-2
rabbitmqctl start_app
在其余的节点上的操作步骤与rmq-broker-test-2虚拟机上的一样。
d.在RabbitMQ集群中的节点只有两种类型:内存节点/磁盘节点,单节点系统只运行磁盘类型的节点。而在集群中,可以选择配置部分节点为内存节点。
内存节点将所有的队列,交换器,绑定关系,用户,权限,和vhost的元数据信息保存在内存中。而磁盘节点将这些信息保存在磁盘中,但是内存节点的性能更高,为了保证集群的高可用性,必须保证集群中有两个以上的磁盘节点,来保证当有一个磁盘节点崩溃了,集群还能对外提供访问服务。在上面的操作中,可以通过如下的方式,设置新加入的节点为内存节点还是磁盘节点:
#加入时候设置节点为内存节点(默认加入的为磁盘节点)
[root@mq-testvm1 ~]# rabbitmqctl join_cluster rabbit@rmq-broker-test-1 --ram
#也通过下面方式修改的节点的类型
[root@mq-testvm1 ~]# rabbitmqctl changeclusternode_type disc | ram
e.最后可以通过“rabbitmqctl cluster_status”的方式来查看集群的状态,上面搭建的10个节点的RabbitMQ集群状态(3个节点为磁盘即诶但,7个节点为内存节点)如下:
Cluster status of node 'rabbit@rmq-broker-test-1'
[{nodes,[{disc,['rabbit@rmq-broker-test-1','rabbit@rmq-broker-test-2',
'rabbit@rmq-broker-test-3']},
{ram,['rabbit@rmq-broker-test-9','rabbit@rmq-broker-test-8',
'rabbit@rmq-broker-test-7','rabbit@rmq-broker-test-6',
'rabbit@rmq-broker-test-5','rabbit@rmq-broker-test-4',
'rabbit@rmq-broker-test-10']}]},
{running_nodes,['rabbit@rmq-broker-test-10','rabbit@rmq-broker-test-5',
'rabbit@rmq-broker-test-9','rabbit@rmq-broker-test-2',
'rabbit@rmq-broker-test-8','rabbit@rmq-broker-test-7',
'rabbit@rmq-broker-test-6','rabbit@rmq-broker-test-3',
'rabbit@rmq-broker-test-4','rabbit@rmq-broker-test-1']},
{cluster_name,<<"rabbit@mq-testvm1">>},
{partitions,[]},
{alarms,[{'rabbit@rmq-broker-test-10',[]},
{'rabbit@rmq-broker-test-5',[]},
{'rabbit@rmq-broker-test-9',[]},
{'rabbit@rmq-broker-test-2',[]},
{'rabbit@rmq-broker-test-8',[]},
{'rabbit@rmq-broker-test-7',[]},
{'rabbit@rmq-broker-test-6',[]},
{'rabbit@rmq-broker-test-3',[]},
{'rabbit@rmq-broker-test-4',[]},
{'rabbit@rmq-broker-test-1',[]}]}]
(3)配置HAProxy
HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案。根据官方数据,其最高极限支持10G的并发。HAProxy支持从4层至7层的网络交换,即覆盖所有的TCP协议。就是说,Haproxy 甚至还支持 Mysql 的均衡负载。为了实现RabbitMQ集群的软负载均衡,这里可以选择HAProxy。
关于HAProxy如何安装的文章之前也有很多同学写过,这里就不再赘述了,有需要的同学可以参考下网上的做法。这里主要说下安装完HAProxy组件后的具体配置。
HAProxy使用单一配置文件来定义所有属性,包括从前端IP到后端服务器。下面展示了用于7个RabbitMQ节点组成集群的负载均衡配置(另外3个磁盘节点用于保存集群的配置和元数据,不做负载)。同时,HAProxy运行在另外一台机器上。HAProxy的具体配置如下:
#全局配置
global
#日志输出配置,所有日志都记录在本机,通过local0输出
log 127.0.0.1 local0 info
#最大连接数
maxconn 4096
#改变当前的工作目录
chroot /apps/svr/haproxy
#以指定的UID运行haproxy进程
uid 99
#以指定的GID运行haproxy进程
gid 99
#以守护进程方式运行haproxy #debug #quiet
daemon
#debug
#当前进程pid文件
pidfile /apps/svr/haproxy/haproxy.pid
#默认配置
defaults
#应用全局的日志配置
log global
#默认的模式mode{tcp|http|health}
#tcp是4层,http是7层,health只返回OK
mode tcp
#日志类别tcplog
option tcplog
#不记录健康检查日志信息
option dontlognull
#3次失败则认为服务不可用
retries 3
#每个进程可用的最大连接数
maxconn 2000
#连接超时
timeout connect 5s
#客户端超时
timeout client 120s
#服务端超时
timeout server 120s
maxconn 2000
#连接超时
timeout connect 5s
#客户端超时
timeout client 120s
#服务端超时
timeout server 120s
#绑定配置
listen rabbitmq_cluster
bind 0.0.0.0:5672
#配置TCP模式
mode tcp
#加权轮询
balance roundrobin
#RabbitMQ集群节点配置,其中ip1~ip7为RabbitMQ集群节点ip地址
server rmq_node1 ip1:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node2 ip2:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node3 ip3:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node4 ip4:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node5 ip5:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node6 ip6:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node7 ip7:5672 check inter 5000 rise 2 fall 3 weight 1
#haproxy监控页面地址
listen monitor
bind 0.0.0.0:8100
mode http
option httplog
stats enable
stats uri /stats
stats refresh 5s
在上面的配置中“listen rabbitmq_cluster bind 0.0.0.0:5671”这里定义了客户端连接IP地址和端口号。这里配置的负载均衡算法是roundrobin—加权轮询。与配置RabbitMQ集群负载均衡最为相关的是“ server rmq_node1 ip1:5672 check inter 5000 rise 2 fall 3 weight 1”这种,它标识并且定义了后端RabbitMQ的服务。主要含义如下:
(a)“server <name>”部分:定义HAProxy内RabbitMQ服务的标识;
(b)“ip1:5672”部分:标识了后端RabbitMQ的服务地址;
(c)“check inter <value>”部分:表示每隔多少毫秒检查RabbitMQ服务是否可用;
(d)“rise <value>”部分:表示RabbitMQ服务在发生故障之后,需要多少次健康检查才能被再次确认可用;
(e)“fall <value>”部分:表示需要经历多少次失败的健康检查之后,HAProxy才会停止使用此RabbitMQ服务。
#启用HAProxy服务
[root@mq-testvm12 conf]# haproxy -f haproxy.cfg
启动后,即可看到如下的HAproxy的界面图:
(4)RabbitMQ的集群架构设计图
经过上面的RabbitMQ10个节点集群搭建和HAProxy软弹性负载均衡配置后即可组建一个中小规模的RabbitMQ集群了,然而为了能够在实际的生产环境使用还需要根据实际的业务需求对集群中的各个实例进行一些性能参数指标的监控,从性能、吞吐量和消息堆积能力等角度考虑,可以选择Kafka来作为RabbitMQ集群的监控队列使用。因此,这里先给出了一个中小规模RabbitMQ集群架构设计图:
对于消息的生产和消费者可以通过HAProxy的软负载将请求分发至RabbitMQ集群中的Node1~Node7节点,其中Node8~Node10的三个节点作为磁盘节点保存集群元数据和配置信息。鉴于篇幅原因这里就不在对监控部分进行详细的描述的,会在后续篇幅中对如何使用RabbitMQ的HTTP API接口进行监控数据统计进行详细阐述。
参考文献
作者:癫狂侠
链接:https://www.jianshu.com/p/6376936845ff
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。