🍔RabbitMQ学习
课程地址:
【【编程不良人】MQ消息中间件之RabbitMQ以及整合SpringBoot2.x实战教程,已完结!】
测试代码:
https://github.com/zhangzhixi0305/RabbitMQ-Study#
一、MQ引言
1.1 什么是MQ
MQ
(Message Quene) : 翻译为 消息队列
,通过典型的 生产者
和消费者
模型,生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,轻松的实现系统间解耦。别名为 消息中间件
通过利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
当今市面上有很多主流的消息中间件,如老牌的ActiveMQ
、RabbitMQ
,炙手可热的Kafka
,阿里巴巴自主开发RocketMQ
等。
1.3 不同MQ特点
# 1.ActiveMQ ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。它是一个完全支持JMS规范的的消息中间件。丰富的API,多种集群架构模式让ActiveMQ在业界成为老牌的消息中间件,在中小型企业颇受欢迎! # 2.Kafka Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache顶级项目。Kafka主要特点是基于Pull的模式来处理消息消费, 追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求, 适合产生大量数据的互联网服务的数据收集业务。 # 3.RocketMQ RocketMQ是阿里开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起 源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消 息推送、日志流式处理、binglog分发等场景。 # 4.RabbitMQ RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和 发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在 其次。
RabbitMQ比Kafka可靠,Kafka更适合IO高吞吐的处理,一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用,比如ELK日志收集。
官方教程
: https://www.rabbitmq.com/#getstarted
2.1 RabbitMQ
AMQP协议
AMQP(advanced message queuing protocol)`在2003年时被提出,最早用于解决金融领不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的provider天然性就是跨平台的。以下是AMQP协议模型:
2.2、RabbitMQ的安装
官网下载地址:https://www.rabbitmq.com/download.html
1、下载安装包
链接: https://pan.baidu.com/s/19y9aIzkqzsYzwt7jwk29dw?pwd=rgjg
2、安装步骤
# 1.将rabbitmq安装包上传到linux系统中,这里我上传到了:/usr/local/rabbitmq下 erlang-22.0.7-1.el7.x86_64.rpm rabbitmq-server-3.7.18-1.el7.noarch.rpm # 2.安装Erlang依赖包 rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm rpm -ivh socat-1.7.3.2-2.el7.x86_64.rpm # 3.安装RabbitMQ安装包 rpm -ivh rabbitmq-server-3.7.18-1.el7.noarch.rpm # 4.复制配置文件 默认安装完成后配置文件模板在:/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example目录中 需要将配置文件复制到/etc/rabbitmq/目录中,并修改名称为rabbitmq.config cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config # 5.查看配置文件位置 ls /etc/rabbitmq/rabbitmq.config # 6.修改配置文件(参见下图:) vim /etc/rabbitmq/rabbitmq.config
# 7.执行如下命令,启动rabbitmq中的插件管理 rabbitmq-plugins enable rabbitmq_management 出现如下说明: Enabling plugins on node rabbit@localhost: rabbitmq_management The following plugins have been configured: rabbitmq_management rabbitmq_management_agent rabbitmq_web_dispatch Applying plugin configuration to rabbit@localhost... The following plugins have been enabled: rabbitmq_management rabbitmq_management_agent rabbitmq_web_dispatch set 3 plugins. Offline change; changes will take effect at broker restart. # 8.启动RabbitMQ的服务 systemctl start rabbitmq-server systemctl restart rabbitmq-server systemctl stop rabbitmq-server # 9.查看服务状态(见下图:) systemctl status rabbitmq-server ● rabbitmq-server.service - RabbitMQ broker Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled) Active: active (running) since 三 2019-09-25 22:26:35 CST; 7s ago Main PID: 2904 (beam.smp) Status: "Initialized" CGroup: /system.slice/rabbitmq-server.service ├─2904 /usr/lib64/erlang/erts-10.4.4/bin/beam.smp -W w -A 64 -MBas ageffcbf -MHas ageffcbf - MBlmbcs... ├─3220 erl_child_setup 32768 ├─3243 inet_gethost 4 └─3244 inet_gethost 4 .........
3、访问RabbitMQ-WEB管理界面:ip:15672
如果是阿里云腾讯云这些,要开放安全组端口:
放行端口:[25672] RabbitMQ-集群端口 放行端口:[5672] RabbitMQ-TCP通信端口 放行端口:[15672] RabbitMQ-web管理页面
如果是本地虚拟机,关闭防火墙就好了
systemctl disable firewalld
三、RabbitMQ配置
3.1、RabbitMQ管理命令行
# 1.服务启动相关 systemctl start|restart|stop|status rabbitmq-server # 2.管理命令行 用来在不使用web管理界面情况下命令操作RabbitMQ rabbitmqctl help 可以查看更多命令 # 3.插件管理命令行 rabbitmq-plugins enable|list|disable
3.2、WEB管理界面介绍
-
-
channels:通道,建立连接后,会形成通道,消息的投递获取依赖通道。
-
Exchanges:交换机,用来实现消息的路由
-
3.3、Admin用户和虚拟主机管理
1、添加用户
2、创建虚拟主机
虚拟主机:为了让各个用户可以互不干扰的工作,RabbitMQ添加了虚拟主机(Virtual Hosts)的概念。其实就是一个独立的访问路径,不同用户使用不同路径,各自有自己的队列、交换机,互相不会影响。
相当于MYSQL中数据库的概念吧,指定用户可以使用这个数据库(虚拟主机)
3、虚拟主机绑定用户
四、RabbitMQ程序案例
代码地址:https://github.com/zhangzhixi0305/RabbitMQ-Study
4.1、AMQP协议
4.2、RabbitMQ支持的消息模型
https://www.rabbitmq.com/getstarted.html
4.3、引入依赖
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.7.2</version> </dependency>
MQ工具类:
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; @SuppressWarnings("all") public class RabbitMQUtils { private static final ConnectionFactory connectionFactory; //重量级资源,类加载执行之执行一次 static { // 1、创建连接mq的连接工厂对象 connectionFactory = new ConnectionFactory(); // 2、设置连接rabbitmq主机 connectionFactory.setHost("192.168.31.73"); // 3、设置端口号 connectionFactory.setPort(5672); // 4、设置用户名和密码 connectionFactory.setUsername("zhixi"); connectionFactory.setPassword("zhixi158"); // 5、设置虚拟主机 connectionFactory.setVirtualHost("/ems"); } /** * 定义提供连接对象的方法 * * @return Connection */ public static Connection getConnection() { try { return connectionFactory.newConnection(); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 关闭通道和关闭连接工具方法 * * @param channel 通道 * @param conn 连接 */ public static void closeConnectionAndChanel(Channel channel, Connection conn) { try { if (channel != null) channel.close(); if (conn != null) conn.close(); } catch (Exception e) { e.printStackTrace(); } } }
4.4、第一种模型(直连)
生产者
/** * 生产消息-HelloWorld */ @Test public void testSenMessage() throws IOException, TimeoutException { // 1、创建连接工厂 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 assert connection != null; Channel channel = connection.createChannel(); // 3、通道绑定对应消息队列 //'参数1':用来声明通道对应的队列 //'参数2':用来指定是否持久化队列 //'参数3':用来指定是否独占队列,true:独占 false:不独占 //'参数4':用来指定是否在消费者消费完成后自动删除队列,true:自动删除 false:不自动删除 //'参数5':对队列的额外配置 channel.queueDeclare("hello", true, false, false, null); // 4、发布消息 // 参数1:交换机名称 // 参数2:路由键 // 参数3:消息的其他属性 - 路由标头等。MessageProperties.PERSISTENT_TEXT_PLAIN:设置消息的持久化 // 参数3:消息体 //channel.basicPublish("", "hello", null, "hello rabbitmq".getBytes()); channel.basicPublish("", "hello", MessageProperties.PERSISTENT_TEXT_PLAIN, "hello rabbitmq".getBytes()); RabbitMQUtils.closeConnectionAndChanel(channel, connection); }
消费者
public class ConsumerMessage { public static void main(String[] args) throws IOException, TimeoutException { Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 Channel channel = connection.createChannel(); // 3、通道绑定对应消息队列 channel.queueDeclare("hello", true, false, false, null); // 4、消费消息 Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body, StandardCharsets.UTF_8); System.out.println("消费消息:" + message); } }; // 5、消费消息(自动提交,消息拿到了,不管这边有没有消费完毕,MQ那边的消息已经没有了) channel.basicConsume("hello", true, consumer); // channel.close(); // connection.close(); } }
测试
生产者运行,会在MQ创建一个消息队列,并放入一条消息
运行消费者,就可以看到已经提交的消息
或者可以通过WEB界面,也是可以消费消息的:
4.5、第二种模型(work quene)
Work queues
,也被称为(Task queues
),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。
长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:
让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
角色:
-
-
C1:消费者-1,领取任务并且完成任务,假设完成速度较慢
-
C2:消费者-2:领取任务并完成任务,假设完成速度快
生产者
/** * 生产消息-WorkQueue */ @Test public void testWorkQueue() throws IOException { // 1、创建连接工厂 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 Channel channel = connection.createChannel(); channel.queueDeclare("work", true, false, false, null); for (int i = 1; i <= 10; i++) { channel.basicPublish("", "work", null, (i + "-hello rabbitmq").getBytes()); } RabbitMQUtils.closeConnectionAndChanel(channel, connection); }
消费者1
import com.rabbitmq.client.*; import com.zhixi.util.RabbitMQUtils; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; /** * @ClassName Consumber1 * @Author zhangzhixi * @Description * @Date 2023-03-27 18:08 * @Version 1.0 */ @SuppressWarnings("all") public class Consumber1 { public static void main(String[] args) throws IOException { // 1、创建连接工厂 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 final Channel channel = connection.createChannel(); channel.queueDeclare("work", true, false, false, null); channel.basicQos(1);//一次只接受一条未确认的消息 // 4、消费消息 Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } String message = new String(body, StandardCharsets.UTF_8); System.out.println("消费者1,消费消息:" + message); // 参数1:确认队列中哪个具体消息 // 参数2:是否开启多个消息同时确认(false:手动确认消息) channel.basicAck(envelope.getDeliveryTag(), false); } }; // 5、消费消息(关闭自动提交) channel.basicConsume("work", false, consumer); } }
消费者2
import com.rabbitmq.client.*; import com.zhixi.util.RabbitMQUtils; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * @ClassName Consumber1 * @Author zhangzhixi * @Description * @Date 2023-03-27 18:08 * @Version 1.0 */ @SuppressWarnings("all") public class Consumber2 { public static void main(String[] args) throws IOException { // 1、创建连接工厂 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 final Channel channel = connection.createChannel(); channel.queueDeclare("work", true, false, false, null); channel.basicQos(1);//一次只接受一条未确认的消息 // 4、消费消息 Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body, StandardCharsets.UTF_8); System.out.println("消费者2,消费消息:" + message); channel.basicAck(envelope.getDeliveryTag(), false);//手动确认消息 } }; // 5、消费消息(关闭自动提交) channel.basicConsume("work", false, consumer); } }
测试
总结:
默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。
4.6、第三种模型(fanout)
扇出,也称为广播
在广播模式下,消息发送流程是这样的:
-
可以有多个消费者
-
每个消费者有自己的queue(队列)
-
每个队列都要绑定到Exchange(交换机)
-
生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
-
交换机把消息发送给绑定过的所有队列
-
队列的消费者都能拿到消息。实现一条消息被多个消费者消费
生产者
/** * 生产消息-扇出/广播 */ @Test public void testFanout() throws IOException { // 1、获取连接对象 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 assert connection != null; Channel channel = connection.createChannel(); // 3、设置交换机的名称与交换机的类型。 // 交换机不存在会自动创建,fanout表示广播。 channel.exchangeDeclare("logs", "fanout"); // 4、发送消息 // 交换机名称、路由键、消息持久化、消息体 channel.basicPublish("logs", "", null, "fanout type message".getBytes()); // 5、释放资源 RabbitMQUtils.closeConnectionAndChanel(channel, connection); }
消费者1
import com.rabbitmq.client.*; import com.zhixi.util.RabbitMQUtils; import java.io.IOException; /** * @ClassName ConsumberFanout1 * @Author zhangzhixi * @Description * @Date 2023-03-28 10:33 * @Version 1.0 */ @SuppressWarnings("all") public class ConsumberFanout1 { public static void main(String[] args) throws IOException { // 1、创建连接工厂 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 assert connection != null; Channel channel = connection.createChannel(); channel.exchangeDeclare("logs", "fanout"); // 3、获取临时队列名称 String queueName = channel.queueDeclare().getQueue(); // 4、绑定交换机和队列 // 队列名称、交换机名称、路由键 channel.queueBind(queueName, "logs", ""); // 5、消费消息 channel.basicConsume(queueName, true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("消费者1:" + new String(body)); } }); } }
消费者2
import com.rabbitmq.client.*; import com.zhixi.util.RabbitMQUtils; import java.io.IOException; /** * @ClassName ConsumberFanout1 * @Author zhangzhixi * @Description * @Date 2023-03-28 10:33 * @Version 1.0 */ @SuppressWarnings("all") public class ConsumberFanout2 { public static void main(String[] args) throws IOException { // 1、创建连接工厂 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 assert connection != null; Channel channel = connection.createChannel(); // 3、获取临时队列名称 String queueName = channel.queueDeclare().getQueue(); channel.exchangeDeclare("logs", "fanout"); // 4、绑定交换机和队列 // 队列名称、交换机名称、路由键 channel.queueBind(queueName, "logs", ""); // 5、消费消息 channel.basicConsume(queueName, true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("消费者2:" + new String(body)); } }); } }
测试:先开启两个消费者,再开启生产者
4.7、第四种模型(Routing)
4.7.1、Routing 之订阅模型-Direct(直连)
在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
在Direct模型下:
-
队列与交换机的绑定,不能是任意绑定了,而是要指定一个
RoutingKey
(路由key) -
消息的发送方在 向 Exchange发送消息时,也必须指定消息的
RoutingKey
。 -
Exchange不再把消息交给每一个绑定的队列,而是根据消息的
Routing Key
进行判断,只有队列的Routingkey
与消息的Routing key
完全一致,才会接收到消息
图解:
-
P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
-
X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
-
C1:消费者,其所在队列指定了需要routing key 为 error 的消息
-
C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息
生产者
/** * 生产消息-路由-直连 */ @Test public void testRoutingDirect() throws IOException { // 1、获取连接对象 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 assert connection != null; Channel channel = connection.createChannel(); // 3、通过通道声明交换机,设置交换机的名称与交换机的类型。 // 交换机不存在会自动创建,fanout表示广播。 channel.exchangeDeclare("logs_direct", "direct"); // 4、路由key String routingKey = "error"; // 5、发送消息 // 交换机名称、路由键、持久化、消息体 channel.basicPublish("logs_direct", routingKey, null, ("基于路由方式的直连消息发送,路由键是【" + routingKey + "】发送的消息").getBytes()); // 5、释放资源 RabbitMQUtils.closeConnectionAndChanel(channel, connection); }
消费者1:指定路由键为info
import com.rabbitmq.client.*; import com.zhixi.util.RabbitMQUtils; import java.io.IOException; /** * @ClassName ConsumberDirect1 * @Author zhangzhixi * @Description * @Date 2023-03-28 11:16 * @Version 1.0 */ @SuppressWarnings("all") public class ConsumberDirect1 { /** * 交换机名称 */ private static final String exchange = "logs_direct"; public static void main(String[] args) throws IOException { // 1、创建连接工厂 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 assert connection != null; Channel channel = connection.createChannel(); channel.exchangeDeclare(exchange, "direct"); // 3、获取临时队列名称 String queueName = channel.queueDeclare().getQueue(); // 4、绑定交换机和队列 // 队列名称、交换机名称、路由键 channel.queueBind(queueName, exchange, "info"); // 5、消费消息 channel.basicConsume(queueName, true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("消费者1中的消息:" + new String(body)); } }); } }
消费者2:指定路由键为error
// 4、绑定交换机和队列 // 队列名称、交换机名称、路由键 channel.queueBind(queueName, exchange, "error");
消费者3:指定路由键为info、warning、error
// 4、绑定交换机和队列 // 队列名称、交换机名称、路由键 channel.queueBind(queueName, exchange, "info"); channel.queueBind(queueName, exchange, "error"); channel.queueBind(queueName, exchange, "warning");
测试:开启三个消费者,再开启生产者
发送error消息,只有特定的消费者能够接收到
4.7.2、Routing 之订阅模型-Topic(动态路由)
Topic
类型的Exchange
与Direct
相比,都是可以根据RoutingKey
把消息路由到不同的队列。
只不过Topic
类型Exchange
可以让队列在绑定Routing key
的时候使用通配符!
这种模型Routingkey
一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
# 通配符
* (star) 匹配不多不少恰好1个词
# (hash) 匹配一个或多个词
# 例如
audit.# 匹配audit.irs.corporate或者 audit.irs 等
audit.* 只能匹配 audit.irs
生产者
* 生产消息-路由-动态路由 */ @Test public void testRoutingTopic() throws IOException { // 1、获取连接对象 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 assert connection != null; Channel channel = connection.createChannel(); // 3、通过通道声明交换机,设置交换机的名称与交换机的类型。 // 交换机不存在会自动创建,fanout表示广播。 channel.exchangeDeclare("topics", "topic"); // 4、路由key String routingKey = "user.save.message"; // 5、发送消息 // 交换机名称、路由键、持久化、消息体 channel.basicPublish("topics", routingKey, null, ("基于路由方式的动态路由消息发送,路由键是【" + routingKey + "】发送的消息").getBytes()); // 5、释放资源 RabbitMQUtils.closeConnectionAndChanel(channel, connection); }
消费者1:使用*通配符匹配一个单词
import com.rabbitmq.client.*; import com.zhixi.util.RabbitMQUtils; import java.io.IOException; /** * @ClassName ConsumberTopic1 * @Author zhangzhixi * @Description * @Date 2023-03-28 12:44 * @Version 1.0 */ public class ConsumberTopic1 { /** * 交换机名称 */ private static final String exchange = "topics"; public static void main(String[] args) throws IOException { // 1、创建连接工厂 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 assert connection != null; Channel channel = connection.createChannel(); channel.exchangeDeclare(exchange, "topic"); // 3、获取临时队列名称 String queueName = channel.queueDeclare().getQueue(); // 4、绑定交换机和队列 // 队列名称、交换机名称、路由键 // user.后面只包含一个单词 channel.queueBind(queueName, exchange, "user.*"); // 5、消费消息 channel.basicConsume(queueName, true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("动态路由中,消费者1中的消息:" + new String(body)); } }); } }
消费者2:使用#通配符匹配0个或者多个单词
import com.rabbitmq.client.*; import com.zhixi.util.RabbitMQUtils; import java.io.IOException; /** * @ClassName ConsumberTopic1 * @Author zhangzhixi * @Description * @Date 2023-03-28 12:44 * @Version 1.0 */ public class ConsumberTopic2 { /** * 交换机名称 */ private static final String exchange = "topics"; public static void main(String[] args) throws IOException { // 1、创建连接工厂 Connection connection = RabbitMQUtils.getConnection(); // 2、创建通道 assert connection != null; Channel channel = connection.createChannel(); channel.exchangeDeclare(exchange, "topic"); // 3、获取临时队列名称 String queueName = channel.queueDeclare().getQueue(); // 4、绑定交换机和队列 // 队列名称、交换机名称、路由键 // user.后面包含0个或者多个单词 channel.queueBind(queueName, exchange, "user.#"); // 5、消费消息 channel.basicConsume(queueName, true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("动态路由中,消费者2中的消息:" + new String(body)); } }); } }
测试:开启两个生产者,再开启消费者
实际上就比上面的直连多了通配符的概念
五、SpringBoot中使用RabbitMQ
直接看代码吧:https://gitee.com/zhang-zhixi/RabbitMQ-Study/tree/master/rabbitmq-02-springboot
启动SpringBoot项目,就会让消费者进行监听,然后再进行启动生产者进行测试
六、RabbitMQ集群
6.1、集群架构
普通集群
默认情况下:RabbitMQ代理操作所需的所有数据/状态都将跨所有节点复制。这方面的一个例外是消息队列,默认情况下,消息队列位于一个节点上,尽管它们可以从所有节点看到和访问
核心解决问题: 当集群中某一时刻master节点宕机,可以对Quene中信息,进行备份
缺点:不会自动切换节点,主节点宕机,其他节点不会提供服务
镜像集群
镜像队列机制就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行自动同步,且如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升MQ集群的整体高可用性。
6.2、集群环境搭建(Docker方式)
参考链接:
https://blog.csdn.net/weixin_46073538/article/details/124766598
https://blog.csdn.net/yaomingyang/article/details/102922480
1、下载MQ镜像
docker pull rabbitmq:3.8-management
2、启动容器:启动后就可以访问到Web界面了[ip+1567x]
docker run -d --hostname myRabbit1 --name rabbit1 -p 15673:15672 -p 5673:5672 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:3.8-management docker run -d --hostname myRabbit2 --name rabbit2 -p 15674:15672 -p 5674:5672 --link rabbit1:myRabbit1 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:3.8-management docker run -d --hostname myRabbit3 --name rabbit3 -p 15675:15672 -p 5675:5672 --link rabbit1:myRabbit1 --link rabbit2:myRabbit2 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:3.8-management
3、加入集群
1、#进入rabbitmq01容器,重新初始化一下,如果是新安装则reset可以忽略重置。 docker exec -it rabbit1 bash rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl start_app exit 2、#进入rabbitmq02容器,重新初始化一下,将02节点加入到集群中 docker exec -it rabbit2 bash rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl join_cluster --ram rabbit@myRabbit1 #参数“--ram”表示设置为内存节点,忽略该参数默认为磁盘节点。 rabbitmqctl start_app exit 3、#进入rabbitmq03容器,重新初始化一下,将03节点加入到集群中 docker exec -it rabbit3 bash rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl join_cluster --ram rabbit@myRabbit1 rabbitmqctl start_app exit
4、配置镜像队列
1、# 随便进入一个容器 docker exec -it rabbit1 bash 2、#设置策略匹配所有名称是amp开头的队列都存储在2个节点上的命令如下 rabbitmqctl set_policy -p / ha "^amp*" '{"ha-mode":"exactly","ha-params":2}' #或者设置策略匹配所有名称的队列都进行高可用配置 rabbitmqctl set_policy -p / ha "^" '{"ha-mode":"all","ha-sync-mode":"automatic"}' 3、#查询策略 rabbitmqctl list_policies -p / #查看vhost下的所有的策略(policies )
参数一:策略名称,可以随便填,此外我们命名为ha(高可用);
参考二:-p / 设置哪个虚拟主机,可以使用rabbitmqctl list_policies -p / 查看vhost 下所有的策略(policies )。
参数三:队列名称的匹配规则,使用正则表达式表示;
参数四:为镜像队列的主体规则,是json字符串,分为三个属性:ha-mode | ha-params | ha-sync-mode,分别的解释如下:
- ha-mode:镜像模式,分类:all/exactly/nodes,all存储在所有节点;exactly存储x个节点,节点的个数由ha-params指定;nodes指定存储的节点上名称,通过ha-params指定;
- ha-params:作为参数,为ha-mode的补充;
- ha-sync-mode:镜像消息同步方式:automatic(自动),manually(手动)
5、其他命令
查看镜像队列: rabbitmqctl list_policies
删除镜像队列: rabbitmqctl clear_policy