目的
- 允许消费者和生产者在RabbitMQ节点崩溃的情况下继续运行,即保证高可用。
- 通过添加更多的节点来线性扩展消息通信吞吐量,即保证性能线性可扩展。
前提
集群中的队列
在集群中创建队列,集群只会在单个节点而不是所有节点上创建完整的队列信息。所有其他非所有者节点只知道队列的元数据和指向该队列所在节点的指针。因此当集群节点崩溃时,该节点的队列相关的元数据、消息、新消息都将丢失。(注:镜像队列可同步所有信息)
为什么RabbitMQ默认不讲队列内容和状态复制到所有集群节点呢?
原因有二:
存储空间。 如果每个集群节点都拥有所有队列的完整拷贝,那么添加新节点不会给你带来更多存储空间。
性能。 消息的发布需要将消息复制到每一个集群节点。对于持久化消息来说,每一条消息都会触发磁盘活动。 每次新增节点,网络和磁盘负载都会增加,最终只能保持集群性能的平稳。
所以通过设置集群中的唯一节点来负责任何特定队列,只有该负责节点才会因队列消息而遭受磁盘活动的影响。所有其他节点需要将接受到的该队列的消息传递给该队列的所有者节点。因此,往RabbitMQ集群添加更多的节点意味着你将拥有更多的节点来传播队列,这些新增节点为你带来了性能的提升。
Erlang cookie
因为RabbitMQ采用Erlang开发,Erlang通过认证Erlang cookie的方式来允许节点间相互通信。所以RabbitMQ需要相互通信的节点必须使用相同的Erlang cookie。
磁盘节点 vs 内存节点
- 内存节点将所有元数据(队列、交换器、绑定、用户、权限、VHost)存储在内存中。 节点关闭,异常退出后,这些元数据会丢失。
- 磁盘节点将所有元数据存储于磁盘中。
使用内存节点可使得元数据的声明之类的操作更加快速。对元数据的存取内存节点比磁盘节点更快速。
RabbitMQ要求集群中至少有一个磁盘节点。
集群部署
集群部署首要问题是节点互联, 节点互联方式有两种:
1. 私有DNS
DNS不用多说.
2. Docker Networking
这是Docker v1.9提供的新功能,使用Docker network创建私有网络,连接到网络内的节点可以互联。
有人可能会想,为什么不使用--link,本人试验了,节点间无法互联,估计我创建的姿势不对。并且需要--link所有节点,麻烦。
直接修改hosts文件,不过这种方式也需要逐个修改,较为繁琐不可取。
集群创建脚步:
本人基于jrlangford的DNS版本修改了一版,并增加networking版本的Shell脚本,可一键部署集群。
步骤1. 创建节点互联基础
#创建DNS服务 docker run --name dns \ -v /var/run/docker.sock:/docker.sock \ --restart='always' \ -d \ phensley/docker-dns --domain localdomain.com
#创建Docker私有网络 docker network create rabbitmqnet
步骤2. 创建节点
#使用DNS docker run -d \ --name=rabbitmq1 \ -p 5672:5672 \ -p 15672:15672 \ -e RABBITMQ_NODENAME=rabbitmq1 \ -e RABBITMQ_ERLANG_COOKIE='YZSDHWMFSMKEMBDHSGGZ' \ -h rabbitmq1.localdomain.com \ --dns $(docker inspect -f '{{.NetworkSettings.IPAddress}}' dns) \ --dns-search localdomain.com \ rabbitmq:3.5-management
#使用Docker Networking docker run -d \ --name=rabbitmq1 \ -p 5672:5672 \ -p 15672:15672 \ -e RABBITMQ_NODENAME=rabbitmq1 \ -e RABBITMQ_ERLANG_COOKIE='YZSDHWMFSMKEMBDHSGGZ' \ -h rabbitmq1 \ --net=rabbitmqnet \ rabbitmq:3.5-management
步骤3. 加入集群
#磁盘节点 docker exec rabbitmq2 bash -c \ "rabbitmqctl stop_app && \ rabbitmqctl reset && \ rabbitmqctl join_cluster rabbitmq1@rabbitmq1 && \ rabbitmqctl start_app"
#内存节点 docker exec rabbitmq3 bash -c \ "rabbitmqctl stop_app && \ rabbitmqctl reset && \ rabbitmqctl join_cluster --ram rabbitmq1@rabbitmq1 && \ rabbitmqctl start_app"
步骤4. 设置镜像队列
设置所有队列为镜像队列,除了amq开头的交换器绑定队列。 生成环境不建议这种处理, 应该设置部分队列为镜像队列。
docker exec rabbitmq1 rabbitmqctl set_policy HA '^(?!amq\.).*' '{"ha-mode": "all"}'
步骤5. 退出集群
docker exec rabbitmq3 bash -c \ "rabbitmqctl stop_app && \ rabbitmqctl reset && \ rabbitmqctl start_app"
集群重启
集群节点的关闭顺序与节点的启动顺序相反,即最后关闭的最先启动,并且最后关闭的节点必须是磁盘节点。请务必遵守按此步骤处理,否则可能造成集群无法启动,造成元数据,消息丢失的情况。
Docker容器不同于虚拟机或主机,容器内部署的应用无法启动,基本上这个容器就废了。Docker现在不允许做一些已创建容器的启动参数调整。
集群部署、运行准则
1. 保证集群中至少有一个在线,那怕这个节点是内存节点.
2. 如果关闭整个集群节点,必须按照顺序逐个关闭,并且最后一个关闭节点务必为磁盘节点。在启动时倒着顺序启动。(如关闭顺序r1,r2,r3.那么启动顺序为r3,r2,r1).
3. 集群中如果使用了虚拟技术(Docker,VM),虚拟节点不能全在同一台物理机中。(你不能保证物理机不会宕机,如果宕机基本上集群是启动不了的.你不知道关闭顺序,你就无法确定启动顺序,并且宕机关闭时间可能RabbitMQ节点都没有反应过来,无法做一些集群状态处理。)
参考资料
- RabbitMQ Doc - Clustering Guide
- RabbitMQ Doc - Highly Available Queues
- RabbitMQ 实战 - 第五章 集群并处理失败