一 集群简介
1 集群形式
- RabbitMQ是用Erlang开发的,集群很容易,因为Erlang天生就是一门分布式语言,但其本身并不支持负载均衡,需要自己配置(可以借助nginx)。
- RabbitMQ 集群中节点包括
内存节点(RAM)
,磁盘节点(Disk,消息持久化)
,集群中至少有一个Disk节点
2 普通模式(默认)
对于普通模式,集群中的各节点有相同的队列结构,但是消息只会存在于集群中那个的一个节点。对于消费者来 说,若消息进去A的Queue,但是消费者从B拉去的时候,B的Queue中是没有消息的,此时要将A中的消息发送给B,然后在发送给消费者
应用场景: 该模式适合于消息无需持久化的场合,如日志队列,当队列非持久化,且创建该队列的节点宕机,客户端才可以重连集群其他节点,并重新创建队列,若为持久化,只能等待故障节点的恢复。
3 镜像模式
与普通模式不同之处是消息实体主动在镜像节点同步,而不是取数据时临时拉取,高可用,该模式下,mirror queue 有一条选举算法,即 1个master,n个slave,生产者,消费者的请求都会转至master
应用场景: 可靠性要求较高的场合,如下单,库存队列 缺点: 若镜像队列过多,且消息体量大,集群内部网络带宽将会被此种同步通讯所消耗。
1 镜像集群也是基于普通集群,即先搭建普通集群,然后才能设置镜像队列 2 若消费过程中,master挂掉,则选举新master,若未来的及确认,则可能出现重复消费
补: 消息重复消费的问题
问题出现场景: 当消费者消费消息后,开始对消息进行确认收到已经消费的时候,向MQ发送消息,此时如果网络不稳定,或者MQ的master挂掉,此时就会出现本地消费者已经消费了消息,但是此时的MQ并没有收到消费者关于确认收到消息的ack,此时MQ就会把消息还保存到队列中;当网络稳定后或者master重新拉起,消费者就会再次收到相同的消息消费,就会出现消息重复消费。
解决办法:
可以在每次消费收到后,还未被处理的时候,可以拿到消息的id【messageId】,放入redis或者Mysql中,进行判断,如果存在messageId,就直接确认消息,不处理消息;如果没有,就处理消息,然后确认消息已经收到
@RabbitListener(queues = "test.queue")
public void upBlog(MessageDTO messageDTO Message message, Channel channel) {
long deliveryTag = message.getMessageProperties().getDeliveryTag(); // 获取消息的唯一ID
String messageId=redis.get("deliveryTag"); // 也可以使用redis的list
if(messageId==null){
// 1 执行业务方法
Boo xxxService.save(messageDTO);
if(flag){
// 2 确认消息
channel.basicAck(deliveryTag, false);
// 3 向redis中放入消息id
redis.set("deliveryTag",deliveryTag);
}else{
// 业务处理失败,消息重新入队
channel.basicReject(deliveryTag, true);
}
}else{
// 直接确认消息
channel.basicAck(deliveryTag, false);
}
}
二 搭建集群
1 创建容器,搭建普通集群
(1) 创建容器
docker run -d --hostname rabbitmq01 --name rabbitmq01 -v /data/es-rabbitmq/rabbitmq01:/var/lib/rabbitmq -p 15673:15672 -p 5673:5672 -e RABBITMQ_ERLANG_COOKIE='cloneman' rabbitmq:3-management
--hostname rabbitmq01 # 给rabbitmq 起一个主机名 RABBITMQ_ERLANG_COOKIE='cloneman' # 给定一个cookie,是每个rabbitmq节点加入集群的关键
docker run -d --hostname rabbitmq02 --name rabbitmq02 -v /data/es-rabbitmq/rabbitmq02:/var/lib/rabbitmq -p 15674:15672 -p 5674:5672 -e RABBITMQ_ERLANG_COOKIE='cloneman' --link rabbitmq01:rabbitmq01 rabbitmq:3-management
--link rabbitmq01:rabbitmq01 # 告诉rabbitmq02 可以访问rabbitmq01
docker run -d --hostname rabbitmq03 --name rabbitmq03 -v /data/es-rabbit/rabbitmq03:/var/lib/rabbitmq -p 15675:15672 -p 5675:5672 -e RABBITMQ_ERLANG_COOKIE='cloneman' --link rabbitmq01:rabbitmq01 --link rabbitmq02:rabbitmq02 rabbitmq:3-management
(2) 初始化集群内部属性
docker exec -it rabbitmq01 /bin/bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit
docker exec -it rabbitmq02 /bin/bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbitmq01
rabbitmqctl start_app
exit
docker exec -it rabbitmq03 /bin/bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbitmq01
rabbitmqctl start_app
exit
2 升级集群为镜像集群
docker exec -it rabbitmq01 /bin/bash
rabbitmqctl set_policy -p / clo_ha "^" '{"ha-mode":"all","ha-sync-mode":"automatic"}'
rabbitmqctl list_policies -p / # 查看策略
set_policy -p / clo_ha "^" '{"ha-mode":"all","ha-sync-mode":"automatic"}'
/ 为 vhost clo_ha 为 策略的名字 '^' 所有的队列 "ha-mode":"all" 所有队列都是高可用,all为复制所有的节点,包括新增节点 "ha-sync-mode":"automatic" 集群中队列信息自动同步
- 创建队列,发送消息,查看是否每个节点队列都有消息,如果都有,那么就搭建镜像集群成功