-
RabbitMQ集群包含四种架构模式
- 主备模式 Warren
- 镜像模式 Mirror
- 远程模式 Shovel
- 多活模式 Federation
-
主备模式(一主一备):实现RabbitMQ的高可用集群,一般在并发和数据量不高的情况下,这种模型非常的简单且好用,主备模式也称为Warren模式。一台干活,一台闲着,只有当主服务器挂掉的时候,备份服务器才会被启用,因此会有严重的负载不均衡的问题。
-
镜像模式:集群模式非常经典的就是Mirror镜像模式,保证100%数据不丢失,在实际工作中也是用的最多的。并且实现集群非常简单,一般会联网大厂都会构建这种镜像集群模式。在主备基础上进行了扩展,集群中所有节点的数据和配置信息都是一样的,在底层同时进行工作,集群的前面有一个负载均衡器(Nginx、Haproxy),来进行负载均衡,当其中的某个节点挂掉的时候,不会影响整个集群对外提供服务。
-
远程模式:远程模式可以实现双活的一种模式(容灾机制),简称Shovel模式,Shovel就是我们可以把消息进行不同数据中心的复制工作,可以跨地域的让两个MQ集群进行互联。(要求较高:所有节点的MQ的版本是一致的, 配置相对复杂,早起的版本不支持,已经被淘汰了)
-
多活模式: 是实现异地数据复制的主流模式,因为Shovel模式配置比较复杂,所以一般实现异地集群都是使用这种双活或者多活模型来实现的。这种模型需要依赖RabbitMQ和Federation插件,可以实现持续的可靠的AMQP数据通信,多活模式在实际配置与应用非常简单。
-
Mirror集群环境的搭建
-
首先需要准备两台服务器(192.168.18.177和192.168.18.178),并且都安装了RabbitMQ
-
修改hostname,在192.168.18.177 /etc/hostname 的内容做如下的修改
将localhost.localdomain 修改为 m1
-
修改hostname,在192.168.18.178 /etc/hostname 的内容做如下的修改
将localhost.localdomain 修改为 m2
-
修改hosts,上面两台主机的 /etc/hosts 文件的最下面分辨添加如下的配置
192.168.18.177 m1
192.168.18.178 m2
-
开放上面两台主机的的4个端口号,分别是4369、5672、15672、25672
firewall-cmd --zone=public --add-port=4369/tcp --permanent #Erlang的端口号 firewall-cmd --zone=public --add-port=4369/tcp --permanent #RabbitMQ的端口号 firewall-cmd --zone=public --add-port=4369/tcp --permanent #RabbitMQ的端口号 firewall-cmd --zone=public --add-port=4369/tcp --permanent #Erlang的端口号 firewall-cmd --relead #重载防火墙的配置
为了保险起见,最好重启主机,执行reboot命令
-
复制.erlang.cookie:.erlang.cookie是erlang分布式的token文件,集群内所有的节点要持有相同的.erlang.cookie文件,才允许彼此通信。
find / -name *.cookie #查找.erlang.cookie文件(在m1下执行) scp /var/lib/rabbitmq/.erlang.cookie 192.168.18.178:/var/lib/rabbitmq/ #将m1的.erlang.cookie文件复制到m2的相关路径下面(在m1下执行) chmod 400 /var/lib/rabbitmq/.erlang.cookie #修改文件的权限(两台服务器都需要执行)
-
在m2下执行下面的命令
rabbitmqctl stop_app #暂停m2服务 rabbitmqctl join_cluster rabbit@m1 #让m2加入集群,rabbit是m1默认的名称 rabbitmqctl start_app #启动m2服务 rabbitmqctl cluster_status #查看集群状态
-
查看集群状态是出现下面的信息表示集群配置成功
-
此时登录后端管理页面的时候会看到集群中所有节点的信息
-
此时若在m1中新增一个交换机,就会自动同步在m2的节点上
-
Haproxy配置MQ集群负载均衡
-
Haproxy是一款提供高可用性、负载均衡以及基于TCP(第四层)和HTTP(第七层)应用的代理软件,支持虚拟主机,它是免费的、快速并且可靠的一种解决方案。TCP代理服务器。
-
RabbitMQ集群镜像模式中,Haproxy用于做TCP代理,提供节点负载均衡,(LB-LoadBalance)与故障发现。
-
Haproxy工作示意图
-
安装Haproxy
yum install haproxy #安装Haproxy rpm -ql haproxy #查看Haproxy安装文件 haproxy #启动haproxy find / -name haproxy.cfg #查找haproxy的核心配置文件
-
修改haproxy.cfg
-
删除下面的内容
#--------------------------------------------------------------------- # main frontend which proxys to the backends #--------------------------------------------------------------------- frontend main *:5000 acl url_static path_beg -i /static /images /javascript /stylesheets acl url_static path_end -i .jpg .gif .png .css .js use_backend static if url_static default_backend app #--------------------------------------------------------------------- # static backend for serving up images, stylesheets and such #--------------------------------------------------------------------- backend static balance roundrobin server static 127.0.0.1:4331 check #--------------------------------------------------------------------- # round robin balancing between the various backends #--------------------------------------------------------------------- backend app balance roundrobin server app1 127.0.0.1:5001 check server app2 127.0.0.1:5002 check server app3 127.0.0.1:5003 check server app4 127.0.0.1:5004 check
-
添加如下的配置项
#对MQ集群进行监听 listen rabbitmq_cluster bind 0.0.0.0:5673 #通过5673对m1和m2进行映射 option tcplog #记录TCP连接状态和时间 mode tcp #四层协议代理,即对TCP进行转发 option clitcpka #开启TCP的Keep Alive(长连接模式) timeout connect 1s #haproxy与mq建立连接的超时时间 timeout client 10s #客户端与haproxy最大空闲时间 timeout server 10s #服务器与haproxy最大空闲时间 balance roundrobin #采用轮询转发消息 #每5秒发送一次心跳包,如果连续两次有响应则代表状态良好 #如果连续3次没有响应,则视为服务故障,该节点将被剔除 server node1 192.168.18.177:5672 check inter 5s rise 2 fall 3 server node2 192.168.18.178:5672 check inter 5s rise 2 fall 3 #开启监控服务 listen http_front bind 0.0.0.0:1080 #监听端口 stats refresh 30s #每30秒刷新一次 stats uri /haproxy?stats #统计页面uri stats auth admin:admin #统计页面用户名和密码设置
-
启动haproxy
haproxy -f /etc/haproxy/haproxy.cfg
-
此时访问 http://192.168.18.177:1080/haproxy?stats 输入用户名和密码(admin:admin),就会出现下面的界面
-
此时查看haproxy开放的端口号
netstat -tulpn | grep haproxy
会出现下面的端口,其中5673是代理服务器对外提供的端口,1080是监控服务的端口
-
-
客户端访问MQ集群
-
由于guest用户只能在本机访问,通过代理服务器不能使用guest用户进行消息发送,所以需要在某一个节点上面新建一个用户,并且为该用户分配权限及虚拟机(集群下的其他节点会自动同步新增的用户数据);另外,在代理服务器的防火墙中需要开放代理端口号和监控服务的端口号,否则程序无法连接到代理服务器上面。
-
RabbitMQUtil的代码
package com.kangswx.rabbitmq.mirror; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; public class RabbitMQUtil { private static ConnectionFactory connectionFactory; static { //ConnectionFactory创建MQ的物理连接 connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.18.177"); //代理服务器地址 connectionFactory.setPort(5673); //代理服务器端口 connectionFactory.setUsername("swkang"); //guest只能在本机进行访问,通过代理服务器发送消息时需要重新建立用户 connectionFactory.setPassword("swkang"); //guest connectionFactory.setVirtualHost("/"); //虚拟主机 } public static Connection getConnection() { Connection connection = null; try { connection = connectionFactory.newConnection(); } catch (Exception e) { throw new RuntimeException(e); //不需要显式的声明抛出 } return connection; } }
-
生产者代码
package com.kangswx.rabbitmq.mirror; import com.kangswx.rabbitmq.utils.RabbitMQConsts; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import java.io.IOException; import java.util.concurrent.TimeoutException; public class Procuder { public static void main(String[] args) throws IOException, TimeoutException { //TCP物理连接 Connection connection = RabbitMQUtil.getConnection(); //创建通信通道,相当于TCP的虚拟连接 Channel channel = connection.createChannel(); //创建队列,声明并创建一个队列,如果队列已经存在,则使用这个队列 //第一个参数,对列名称 helloworld //第二个参数,是否持久话,false表示不持久化数据,MQ停掉后数据就会丢失 //第三个参数,是否队列私有化,false表示所有的消费者都可以访问,true表示只有第一次拥有它的消费者才可以一直使用,其他消费者不能访问 //第四个参数,是否自动删除,false连接停掉后不自动删除掉这个队列 //第五个参数,其他额外的参数 channel.queueDeclare(RabbitMQConsts.QUEUE_HELLO, false, false, false, null); //需要发送的消息 String content = "Hello World bb!"; //第一个参数,交换机 //第二个参数,队列名称 //第三个参数,额外的设置属性 //第四个参数,需要发送的消息的字节数组 channel.basicPublish("", RabbitMQConsts.QUEUE_HELLO, null, content.getBytes()); channel.close(); connection.close(); System.out.println("数据发送成功"); } }
-
消费者代码
package com.kangswx.rabbitmq.mirror; import com.kangswx.rabbitmq.utils.RabbitMQConsts; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class Consumer { public static void main(String[] args) throws IOException, TimeoutException { //TCP物理连接 Connection connection = RabbitMQUtil.getConnection(); //创建通道 Channel channel = connection.createChannel(); //绑定消息队列 //第一个参数,对列名称 helloworld //第二个参数,是否持久话,false表示不持久化数据,MQ停掉后数据就会丢失 //第三个参数,是否队列私有化,false表示所有的消费者都可以访问,true表示只有第一次拥有它的消费者才可以一直使用,其他消费者不能访问 //第四个参数,是否自动删除,false连接停掉后不自动删除掉这个队列 //第五个参数,其他额外的参数 channel.queueDeclare(RabbitMQConsts.QUEUE_HELLO, false, false, false, null); //创建一个消息消费者 //第一个参数,队列名称 helloworld //第二个参数,是否自动确认收到消息,false表示手动编写程序来确认消息,这是MQ推荐的做法 //第三个参数,DefaultConsumer的实现类 channel.basicConsume(RabbitMQConsts.QUEUE_HELLO, false, new Receiver(channel)); //在消费者中不能关闭channel和connection } } class Receiver extends DefaultConsumer{ private Channel channel; //重写构造函数,channel通道对象需要从外部传入,在handleDelivery中会用到 public Receiver(Channel channel) { super(channel); this.channel = channel; } @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { /*super.handleDelivery(consumerTag, envelope, properties, body);*/ String messageBody = new String(body); System.out.println("消费者接收到: " + messageBody); //签收消息,确认消息 //第一个参数,envelope.getDeliveryTag()获取这个消息的TagId,是一个整数 //第二个参数,false只确认签收当前的消息,true时,表示签收该消费者所有未签收的消息 channel.basicAck(envelope.getDeliveryTag(), false); } }
-
-
上面示例的代码见 Java客户端访问RabbitMQ代码示例