4. 搭建普通集群环境
现在,将以图7为例来搭建普通集群环境。
4.1 首先根据上文“RabbitMQ单节点环境的搭建”章节相关内容,准备好以下3个节点:
4.2 设置各节点的hostname:
vim /etc/hostname
……
reboot
注:其他Linux发行版可能需要通过“vim /etc/sysconfig/network”来修改hostname
4.3 修改各节点的hosts文件,并添加以下DNS信息:
vim /etc/hosts
192.168.1.10 rabbit01 rabbit01
192.168.1.20 rabbit02 rabbit02
192.168.1.30 rabbit03 rabbit03
4.4 将各节点的erlang.cookie设置为相同值,比如都使用rabbit01节点的值:
#先备份原cookie文件
rabbit02# cp /var/lib/rabbitmq/.erlang.cookie /var/lib/rabbitmq/.erlang.cookie.bak
rabbit03# cp /var/lib/rabbitmq/.erlang.cookie /var/lib/rabbitmq/.erlang.cookie.bak
#修改cookie的值
chmod 777 /var/lib/rabbitmq/.erlang.cookie
vim /var/lib/rabbitmq/.erlang.cookie
……
chmod 400 /var/lib/rabbitmq/.erlang.cookie
4.5 停止所有节点上的RabbitMQ服务,然后以detached方式独立运行:
rabbit01# rabbitmqctl stop
rabbit02# rabbitmqctl stop
rabbit03# rabbitmqctl stop
rabbit01# rabbitmq-server -detached
rabbit02# rabbitmq-server -detached
rabbit03# rabbitmq-server -detached
4.6 查看各节点的集群信息:
rabbit01# rabbitmqctl cluster_status
rabbit02# rabbitmqctl cluster_status
rabbit03# rabbitmqctl cluster_status
可以看到,各节点均以单磁盘节点的集群方式各自运行着。
4.7 将rabbit02、rabbit03 作为内存节点,加入到rabbit01的集群中
rabbit02# rabbitmqctl stop_app
rabbit02# rabbitmqctl join_cluster --ram rabbit@rabbit01
rabbit02# rabbitmqctl start_app
rabbit03# rabbitmqctl stop_app
rabbit03# rabbitmqctl join_cluster --ram rabbit@rabbit01
rabbit03# rabbitmqctl start_app
4.8 再次查看各节点的集群信息:
rabbit01# rabbitmqctl cluster_status
rabbit02# rabbitmqctl cluster_status
rabbit03# rabbitmqctl cluster_status
可以看到,各节点处于一个由rabbit01(disc node)、rabbit02(ram node)、rabbit03(ram node)组成的集群,名为“rabbit@rabbit01”
注:
如果需要将某个节点从集群中移除,使其变回独立节点,可以使用以下命令:
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
如果需要启停某个节点来进行维护,可以使用以下命令:
rabbitmqctl stop
#FormatImgID_24##FormatImgID_25#rabbitmq-server -detached
当新的节点加入到集群之后,其用户信息也被重置了(之前新增的admin账户不见了),需要重新配置管理员用户,以便访问RabbitMQ管理页面(在任意节点添加用户,会自动同步到各个集群节点):
#添加管理员用户并授权:
rabbit01# rabbitmqctl add_user admin admin
rabbit01# rabbitmqctl set_user_tags admin administrator
rabbit01# rabbitmqctl set_permissions -p / admin '.*' '.*' '.*'
5. 搭建高可用集群环境
通过Policy(策略)设置镜像队列,来实现RabbitMQ的高可用方案。策略主要用来控制和修改集群范围的某个Virtual Host中Exchange和Queue的行为。具体有以下三种策略类型:
这里,我们采用以下策略(在集群中任意节点启用策略,策略会自动同步到各个集群节点):
#同步以"ha."开头的队列到集群中各节点,应用于所有节点(包括新增节点)
#FormatImgID_27##FormatImgID_28#rabbitmqctl set_policy my-ha-all "^ha\." '{"ha-mode":"all",,"ha-sync-mode":"automatic"}'
rabbitmqctl set_policy my-ha-all "^ha\." '{"ha-mode":"all"}'
6. 验证集群的高可用性
接下来,基于图7的集群环境来验证集群中镜像队列的高可用性。
6.1 修改生产者程序(Send.java),连接到Node1(192.168.1.10)节点,发送消息到镜像队列ha.hello中(修改部分如下图):
6.2 修改消费者程序(Recv.java),连接到Node3(192.168.1.30)节点,接收ha.hello队列中的消息(修改部分如下图):
6.3 首先执行生产者程序(Send.java)发送100条消息,然后通过命令“rabbitmqctl stop”停止Node1的RabbitMQ服务,再执行消费者程序(Recv.java)接收消息。
7. 基于集群环境的客户端开发优化建议
上面介绍的Send.java与Recv.java代码,存在以下两点不足需要改进:
1、生产者、消费者都只连接到了集群中的某个节点。如果该节点故障之后,客户端程序将无法正常发送或接收消息;
2、没有设置自动重连机制,使得客户端程序与Broker之间建立的TCP连接很脆弱。一旦由于网络异常导致Connection关闭,客户端程序将程序将无法正常接收消息。
基于上述两个实际客户端开发的痛点,我们需要对程序进行集群全节点连接、自动重连的改进。改进后的Recv.java完整代码如下图19~图21所示:
首先让Recv类实现Runnable、Consumer接口,让Recv实例以Consomer线程的方式运行。
然后在重写run方法中进行自动重连、连接到所有节点的设置
然后重写handleDelivery方法,来设置接收到消息后的处理逻辑;并空实现Consumer接口中其他handleXXX的方法;最后通过main方法以线程的方式创建Consumer的实例来接收消息。
四、总结
本文通过介绍RabbitMQ的基本概念、主要作用和使用场景,回答了什么是RabbitMQ,以及用RabbitMQ能做什么的问题;然后通过搭建一个RabbitMQ单节点环境、用程序演示消息发送接收过程,回答了怎么使用RabbitMQ的问题;再通过搭建高可用集群环境,回答了如何实现RabbitMQ服务高可用性的问题。并分享了自己在集群环境的客户端开发实践中的一些经验。
对于上述的RabbitMQ高可用集群方案,还存在一定的缺点:虽然在客户端程序提供完整的集群节点信息能保证连接的可靠性,但是这种向客户端程序暴露集群(所有)节点的做法不太合适。
当集群环境发生变化(比如增加、删除节点)时,客户端程序还得做相应的配置变更,增加了集群环境和客户端程序耦合性。RabbitMQ官方建议通过在RabbitMQ集群环境之上增加一个抽象层,让客户端程序连接到该抽象层,实现集群环境和客户端程序的解耦。这个抽象层,可以是一个包含一段TTL配置的动态DNS服务,也可以是一个TCP LB(基于TCP协议的Load Balancer,如HAProxy)。