MQ(Message Quene):翻译为消息队列,通过典型的生产者和消费者模型生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入轻松的实现系统间解耦。别名为消息中间件,通过利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
当今市面上有很多主流的消息中间件,如老牌的ActiveMQ、RabbitMQ,炙手可热的Kafka,阿里巴巴自主开发RocketMQ等。
ActiveMQ 方案成熟/性能缺陷(小公司)
ActiveMQ、是Apache出品,最流行的,能力强劲的开源消息总线。它是一个完全支持JNS规范的的消息中间件。丰富的APT,多种集群架构模式让认kctivelo在业界成为老牌的消息中间件,在中小型企业颇受欢迎!
Kafka 性能强劲/一致性堪忧(大数据)
Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache顶级项目。Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合产生大量数据的互联网服务的数据收集业务。
RocketMQ 没有缺点/未完全开源(大公司)
RocketMQ是阿里开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景。
RabbitMQ 最佳平替
RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由〈包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
基于
AMOP协议,erlang语言开发,是部署最广泛的开源消息中间件是最受欢迎的开源消息中间件之一。
AMQP(advanced message queuing protocol)在2003年时被提出,最早用于解决金融领不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的
provider天然性就是跨平台的。以下是AMQP协议模型:

官网:https://www.rabbitmq.com/
由于是Centos7所以只能下载带el7标识的
**注意:**RabbitMQ的版本和Erlang的版本是有关联的,需要在官网中去查阅
下载Erlang安装包
erlang-23.3.4.11-1.el7.x86_64.rpm
下载RabbitMQ安装包
rabbitmq-server-3.8.30-1.el7.noarch.rpm
进入云服务器中,将文件放到/home路径下

安装socat
yum -y install socat
安装Erlang
rpm -ivh erlang-23.3.4.11-1.el7.x86_64.rpm
输入erl查看是否安装成功

安装RabbitMQ
rpm -ivh rabbitmq-server-3.8.30-1.el7.noarch.rpm
运行cd /etc/rabbitmq,编写配置文件
如果想要使guest用户远程访问的话可以设置
那么我们guest用户的账号和密码都是
guest
vi rabbitmq.config新建文件,写入如下
[
{rabbit, [{loopback_users, []}]}
].
启动网页管理插件
rabbitmq-plugins enable rabbitmq_management

查看RabbitMQ运行状态
systemctl status rabbitmq-server

启动RabbitMQ
systemctl start rabbitmq-server
启动完成后再查看一下状态

停止RabbitMQ
如果要停止,可以使用以下命令
systemctl stop rabbitmq-server
防火墙设置
可以通过以下的命令进行查看防火墙状态
systemctl status firewalld
如果防火墙是开启的,你可以通过放行15672端口或者关闭防火墙
远程访问RabbitMq
通过IP地址:15672访问
用户名和密码如果配置了guest的话,那么就都是guest

如果不想使用guest用户访问的话,可以通过添加新用户的方式来访问
rabbitmqctl add_user admin password
rabbitmqctl set_user_tags admin administrator
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

引入依赖
com.rabbitmq amqp-client 5.9.0
package com.lzj;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;import java.io.IOException;
import java.util.Calendar;
import java.util.concurrent.TimeoutException;/*** * 生产者*
** @author:雷子杰* @date:2022/10/26*/
public class Provider {@Testpublic void testSendMessage() throws IOException, TimeoutException {//创建连接mq的连接工厂对象ConnectionFactory connectionFactory = new ConnectionFactory();//设置连接rabbitmq主机connectionFactory.setHost("120.79.188.192");//设置端口号connectionFactory.setPort(5672);//设置连接哪个虚拟主机connectionFactory.setVirtualHost("/ems");//设置访问虚拟主机的用户名和密码connectionFactory.setUsername("LeiZijie");connectionFactory.setPassword("66323192");//获取连接对象Connection connection = connectionFactory.newConnection();//获取连接中通道Channel channel = connection.createChannel();//通道绑定对应消息队列//参数1:队列名称,不存在则自动创建//参数2:用来定义队列是否要持久化//参数3:是否独占队列//参数4:是否在消费完成后自动删除队列//参数5:额外附加参数channel.queueDeclare("hello",false,false,false,null);//发布消息//参数1:交换机名称//参数2:队列名称//参数3:传递消息额外配置//参数4:消息的具体内容//参数3可以设置成 MessageProperties.PERSISTENT_TEXT_PLAIN,那么参数就可以完成持久化channel.basicPublish("","hello",null,"hello rabitmq".getBytes());channel.close();connection.close();}
}
web中的结果

注意,由于消费者获取是使用的多线程方式,所以不能在test方法中。
在消费者中,最后不要关闭通道和连接,否则可能会导致消息无法获取
package com.lzj;import com.rabbitmq.client.*;
import org.junit.Test;import java.io.IOException;
import java.util.concurrent.TimeoutException;/*** **
** @author:雷子杰* @date:2022/10/26*/
public class Customer {public static void main(String[] args) throws IOException, TimeoutException {//创建工厂连接ConnectionFactory connectionFactory = new ConnectionFactory();connectionFactory.setHost("120.79.188.192");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/ems");connectionFactory.setUsername("LeiZijie");connectionFactory.setPassword("66323192");//创建连接对象Connection connection = connectionFactory.newConnection();//创建通道Channel channel = connection.createChannel();//通道绑定对象channel.queueDeclare("hello",false,false,false,null);//消费信息//参数1:消费那个队列的消息,队列名称//参数2:开始消息的自动确认机制//参数3:消费时的回调接口channel.basicConsume("hello",true,new DefaultConsumer(channel){//最后一个参数:消息队列中取出的消息@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("new String(body)="+new String(body));}});//channel.close();//connection.close();}
}

工作队列(又名:任务队列)背后的主要思想是避免立即执行资源密集型任务而不得不等待它完成。相反,我们将任务安排在以后完成。我们将任务封装 为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当您运行许多工作人员时,任务将在他们之间共享。
这个概念在 Web 应用程序中特别有用,在这些应用程序中,无法在短暂的 HTTP 请求窗口中处理复杂的任务。
角色:
- P:生产者
- C1:消费者1
- C2:消费者2
package com.lzj.workqueues;import com.lzj.utils.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import java.io.IOException;/*** **
** @author:雷子杰* @date:2022/10/26*/
public class Provider {public static void main(String[] args) throws IOException {Connection connection = RabbitMQUtils.getConnection();Channel channel = connection.createChannel();channel.queueDeclare("work",true,false,false,null);for (int i = 0; i < 10; i++) {//生产消息channel.basicPublish("","work",null,(i+"hello work").getBytes());}RabbitMQUtils.closeConnection(channel,connection);}
}
package com.lzj.workqueues;import com.lzj.utils.RabbitMQUtils;
import com.rabbitmq.client.*;import java.io.IOException;/*** **
** @author:雷子杰* @date:2022/10/26*/
public class Customer1 {public static void main(String[] args) throws IOException {Connection connection = RabbitMQUtils.getConnection();Channel channel = connection.createChannel();channel.queueDeclare("work",true,false,false,null);channel.basicConsume("work",true,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者-1:"+new String(body));}});}
}
package com.lzj.workqueues;import com.lzj.utils.RabbitMQUtils;
import com.rabbitmq.client.*;import java.io.IOException;/*** **
** @author:雷子杰* @date:2022/10/26*/
public class Customer2 {public static void main(String[] args) throws IOException {Connection connection = RabbitMQUtils.getConnection();Channel channel = connection.createChannel();channel.queueDeclare("work",true,false,false,null);channel.basicConsume("work",true,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者-2:"+new String(body));}});}
}
将消费者中配置修改下即可
//每次只能消费一次
channel.basicQos(1);
channel.queueDeclare("work",true,false,false,null);//参数二由true改为false
channel.basicConsume("work",false,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者-1:"+new String(body));//手动确认消息channel.basicAck(envelope.getDeliveryTag(),false);//手动确认消息}
});
fanout:扇出,也称为广播

在广播模式下,消息发送流程是这样的:
队列的消费者都能拿到消息。实现一条消息被多个消费者消费package Fanout;import Utils.MQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class Provider {public static void main(String[] args) throws IOException, TimeoutException {// 获取连接对象Connection connection = MQConnection.getConnection();// 获取通道对象Channel channel = connection.createChannel();// 将通道声明指定的交换机// 参数:交换机名称、交换机类型(fanout为广播类型)channel.exchangeDeclare("logs","fanout");// 发送消息// fanout中routingkey没意义channel.basicPublish("logs", "", null, ("fanout type message").getBytes());// 关闭资源MQConnection.closeChannelAndConnection(channel, connection);}
}
由于消费者代码基本相同,只有输出的信息不同,所以这里就不写出来了
package Fanout;import Utils.MQConnection;
import com.rabbitmq.client.*;import java.io.IOException;public class Consumer1 {public static void main(String[] args) throws IOException {// 获取连接对象Connection connection = MQConnection.getConnection();// 获取通道对象Channel channel = connection.createChannel();// 将通道声明指定的交换机// 参数:交换机名称、交换机类型(fanout为广播类型)channel.exchangeDeclare("logs","fanout");// 临时队列String queueName=channel.queueDeclare().getQueue();// 绑定交换机和队列channel.queueBind(queueName,"logs","");// 消费消息channel.basicConsume(queueName,true,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者1:"+new String(body));}});}
}
在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
在Direct模型下:
package Direct;import Utils.MQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class Provider {public static void main(String[] args) throws IOException, TimeoutException {// 获取连接对象Connection connection = MQConnection.getConnection();// 获取通道对象Channel channel = connection.createChannel();// 将通道声明指定的交换机// 参数:交换机名称(自己起)、交换机类型(direct为路由模式)channel.exchangeDeclare("logs_direct", "direct");// 发送消息String routingKey = "error";// fanout中routingkey没意义channel.basicPublish("logs_direct", routingKey, null,("这是direct模型发布的基于routingkey:[" + routingKey + "] 发送的消息").getBytes());// 关闭资源MQConnection.closeChannelAndConnection(channel, connection);}
}
package Direct;import Utils.MQConnection;
import com.rabbitmq.client.*;import java.io.IOException;public class Consumer1 {public static void main(String[] args) throws IOException {// 获取连接对象Connection connection = MQConnection.getConnection();// 获取通道对象Channel channel = connection.createChannel();// 将通道声明指定的交换机// 参数:交换机名称、交换机类型channel.exchangeDeclare("logs_direct", "direct");// 临时队列String queueName = channel.queueDeclare().getQueue();// 基于routingKey绑定交换机和队列channel.queueBind(queueName, "logs_direct", "error");// 消费消息channel.basicConsume(queueName, true, new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者1:" + new String(body));}});}
}
package Direct;import Utils.MQConnection;
import com.rabbitmq.client.*;import java.io.IOException;public class Consumer2 {public static void main(String[] args) throws IOException {// 获取连接对象Connection connection = MQConnection.getConnection();// 获取通道对象Channel channel = connection.createChannel();// 将通道声明指定的交换机// 参数:交换机名称、交换机类型channel.exchangeDeclare("logs_direct", "direct");// 临时队列String queueName = channel.queueDeclare().getQueue();// 基于routingKey绑定交换机和队列channel.queueBind(queueName, "logs_direct", "error");channel.queueBind(queueName, "logs_direct", "info");channel.queueBind(queueName, "logs_direct", "warning");// 消费消息channel.basicConsume(queueName, true, new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者2:" + new String(body));}});}
}
当生产者发送Route key为error的消息时

当生产者发送Route key为info的消息时

Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key的时候使用通配符!这种模型Routingkey 一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

统配符:
*:匹配恰好1个单词#:匹配一个或多个单词如:
audit.#:匹配audit.irs.corporate或者audit.irs等audit.*:只能匹配audit.irs*.audit.#:中间必须是audit,audit前有一个或多个单词,后有一个单词package Topic;import Utils.MQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class Provider {public static void main(String[] args) throws IOException, TimeoutException {// 获取连接对象Connection connection = MQConnection.getConnection();// 获取通道对象Channel channel = connection.createChannel();// 将通道声明指定的交换机// 参数:交换机名称(自己起)、交换机类型(topic为动态路由)channel.exchangeDeclare("topics", "topic");String routingKey = "user.save";// 发送消息// fanout中routingkey没意义channel.basicPublish("topics", routingKey, null,("这是topic动态路由模型,routingkey:[" + routingKey + "]").getBytes());// 关闭资源MQConnection.closeChannelAndConnection(channel, connection);}
}
消费者1和2的不同之处在于类名、第20行、第25行的名字
package Topic;import Utils.MQConnection;
import com.rabbitmq.client.*;import java.io.IOException;public class Consumer1 {public static void main(String[] args) throws IOException {// 获取连接对象Connection connection = MQConnection.getConnection();// 获取通道对象Channel channel = connection.createChannel();// 将通道声明指定的交换机// 参数:交换机名称、交换机类型channel.exchangeDeclare("topics", "topic");// 临时队列String queueName = channel.queueDeclare().getQueue();// 基于通配符形式routingKey绑定交换机和队列// Consumer2设为user.#channel.queueBind(queueName, "topics", "user.*");// 消费消息channel.basicConsume(queueName, true, new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者1:" + new String(body));}});}
}

引入依赖
org.springframework.boot spring-boot-starter-amqp 2.7.5
配置文件设置
spring:rabbitmq:host: 120.79.188.192port: 5672username: LeiZijiepassword: 66323192virtual-host: /ems
package ecnu.cn;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {// 注入RabbitTemplate@Autowiredprivate RabbitTemplate rabbitTemplate;// hello world@Testpublic void test(){rabbitTemplate.convertAndSend("hello","hello world");}
}
package ecnu.cn.Hello;import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
// 指定监听hello队列
// 默认是持久化、非独占、不自动删除队列的
@RabbitListener(queuesToDeclare = @Queue(value = "hello", durable = "true", exclusive = "true", autoDelete = "true"))
public class Consumer {@RabbitHandlerpublic void receive(String message) {System.out.println("message: " + message);}
}
// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;// work
@Test
public void testWork() {for (int i = 0; i < 10; i++) {rabbitTemplate.convertAndSend("work", "work模型" + i);}
}
package ecnu.cn.Work;import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class WorkConsumer {// 第1个消费者@RabbitListener(queuesToDeclare = @Queue("work"))public void receive1(String message) {System.out.println("message1: " + message);}// 第2个消费者@RabbitListener(queuesToDeclare = @Queue("work"))public void receive2(String message) {System.out.println("message2: " + message);}
}
// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;// fanout 广播
@Test
public void testFanout(){// 注意参数多了个exchangerabbitTemplate.convertAndSend("logs", "", "Fanout模型发送的消息");
}
package ecnu.cn.Fanout;import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class FanoutConsumer {@RabbitListener(bindings = @QueueBinding(value = @Queue, // 创建临时队列exchange = @Exchange(name = "logs", type = "fanout") // 绑定的交换机))public void reveive1(String message) {System.out.println("message1: " + message);}@RabbitListener(bindings = @QueueBinding(value = @Queue, // 创建临时队列exchange = @Exchange(name = "logs", type = "fanout") // 绑定的交换机))public void reveive2(String message) {System.out.println("message2: " + message);}
}
// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;// route 路由模式
@Test
public void testRoute(){rabbitTemplate.convertAndSend("directs","info","发送info的key的路由信息");
}
package ecnu.cn.Route;import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class RouteConsumer {@RabbitListener(bindings = @QueueBinding(value = @Queue, // 创建临时队列// name和value的效果一样exchange = @Exchange(value = "directs", type = "direct"), // 自定义交换机信息key = {"info","error"}))public void receive1(String message) {System.out.println("message1: " + message);}@RabbitListener(bindings = @QueueBinding(value = @Queue, // 创建临时队列// name和value的效果一样exchange = @Exchange(value = "directs", type = "direct"), // 自定义交换机信息key = "error"))public void receive2(String message) {System.out.println("message2: " + message);}
}
// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;// topic 动态路由 订阅模式
@Test
public void testTopic(){rabbitTemplate.convertAndSend("topics","user.save","user.save 路由信息");
}
package ecnu.cn.Topic;import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class TopicConsumer {@RabbitListener(bindings = @QueueBinding(value = @Queue,exchange = @Exchange(value = "topics", type = "topic"),key = {"user.save", "user.*"}))public void receive1(String message) {System.out.println("message1: " + message);}@RabbitListener(bindings = @QueueBinding(value = @Queue,exchange = @Exchange(value = "topics", type = "topic"),key = {"order.#", "produce.#", "user.*"}))public void receive2(String message) {System.out.println("message2: " + message);}
}
用户注册后,需要发注册邮件和注册短信,传统的做法有两种 1.串行的方式 2.并行的方式
将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西

将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。

假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并行已经提高的处理时间,但是,前面说过,邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功,应该是写入数据库后就返回. 消息队列: 引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理

由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍
双11是购物狂节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口.

当库存系统出现故障时,订单就会失败。 订单系统和库存系统高耦合. 引入消息队列

用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
订阅下单的消息,获取下单消息,进行库操作。 就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失
秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。
All data/state required for the operation of a RabbitMQ broker is replicated across all nodes. An exception to this are message queues, which by default reside on one node, though they are visible and reachable from all nodes. To replicate queues across nodes in a cluster --摘自官网
默认情况下:RabbitMQ代理操作所需的所有数据/状态都将跨所有节点复制。这方面的一个例外是消息队列,默认情况下,消息队列位于一个节点上,尽管它们可以从所有节点看到和访问

核心解决问题: 当集群中某一时刻master节点宕机,可以对Queue中信息,进行备份
集群规划
node1: 10.15.0.3 mq1 master 主节点
node2: 10.15.0.4 mq2 repl1 副本节点
node3: 10.15.0.5 mq3 repl2 副本节点
克隆三台机器主机名和ip映射
vim /etc/hosts加入:
10.15.0.3 mq1
10.15.0.4 mq2
10.15.0.5 mq3
node1: vim /etc/hostname 加入: mq1
node2: vim /etc/hostname 加入: mq2
node3: vim /etc/hostname 加入: mq3
三个机器安装rabbitmq,并同步cookie文件,在node1上执行:
scp /var/lib/rabbitmq/.erlang.cookie root@mq2:/var/lib/rabbitmq/
scp /var/lib/rabbitmq/.erlang.cookie root@mq3:/var/lib/rabbitmq/
查看cookie是否一致:
node1: cat /var/lib/rabbitmq/.erlang.cookie
node2: cat /var/lib/rabbitmq/.erlang.cookie
node3: cat /var/lib/rabbitmq/.erlang.cookie
后台启动rabbitmq所有节点执行如下命令,启动成功访问管理界面:
rabbitmq-server -detached
在node2和node3执行加入集群命令:
1.关闭 rabbitmqctl stop_app
2.加入集群 rabbitmqctl join_cluster rabbit@mq1
3.启动服务 rabbitmqctl start_app
查看集群状态,任意节点执行:
rabbitmqctl cluster_status
如果出现如下显示,集群搭建成功:
Cluster status of node rabbit@mq3 …
[{nodes,[{disc,[rabbit@mq1,rabbit@mq2,rabbit@mq3]}]},
{running_nodes,[rabbit@mq1,rabbit@mq2,rabbit@mq3]},
{cluster_name,<<“rabbit@mq1”>>},
{partitions,[]},
{alarms,[{rabbit@mq1,[]},{rabbit@mq2,[]},{rabbit@mq3,[]}]}]
登录管理界面,展示如下状态:

测试集群在node1上,创建队列

查看node2和node3节点:

关闭node1节点,执行如下命令,查看node2和node3:
rabbitmqctl stop_app


This guide covers mirroring (queue contents replication) of classic queues --摘自官网
By default, contents of a queue within a RabbitMQ cluster are located on a single node (the node on which the queue was declared). This is in contrast to exchanges and bindings, which can always be considered to be on all nodes. Queues can optionally be made mirrored across multiple nodes. --摘自官网
镜像队列机制就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行自动同步,且如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升MQ集群的整体高可用性。

策略说明
rabbitmqctl set_policy [-p ] [--priority ] [--apply-to ]
-p Vhost: 可选参数,针对指定vhost下的queue进行设置
Name: policy的名称
Pattern: queue的匹配模式(正则表达式)
Definition:镜像定义,包括三个部分ha-mode, ha-params, ha-sync-modeha-mode:指明镜像队列的模式,有效值为 all/exactly/nodesall:表示在集群中所有的节点上进行镜像exactly:表示在指定个数的节点上进行镜像,节点的个数由ha-params指定nodes:表示在指定的节点上进行镜像,节点名称通过ha-params指定ha-params:ha-mode模式需要用到的参数ha-sync-mode:进行队列中消息的同步方式,有效值为automatic和manualpriority:可选参数,policy的优先级 查看当前策略
rabbitmqctl list_policies
添加策略
rabbitmqctl set_policy ha-all '^hello' '{"ha-mode":"all","ha-sync-mode":"automatic"}'
说明:策略正则表达式为 “^” 表示所有匹配所有队列名称 ^hello:匹配hello开头队列 删除策略
rabbitmqctl clear_policy ha-all
测试集群
官网给出的:

翻译一下就是,安装之后不会自动给你创建配置文件,你可以在一下几个地址上自己创建。
官网给出的配置例子:rabbitmq.conf.example