1.RabbitMQ
RabbitMQ 是一个消息中间件:它接受并转发消息。你可以把它当做一个快递站点,当你要发送一个包 裹时,你把你的包裹放到快递站,快递员最终会把你的快递送到收件人那里,按照这种逻辑 RabbitMQ 是 一个快递站,一个快递员帮你传递快件。RabbitMQ 与快递站的主要区别在于,它不处理快件而是接收, 存储和转发消息数据。
四大核心
- 生产者 产生数据发送消息的程序是生产者
- 交换机 交换机是 RabbitMQ 非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息 推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推 送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定
- 队列 队列是 RabbitMQ 内部使用的一种数据结构,尽管消息流经 RabbitMQ 和应用程序,但它们只能存 储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。许多生产者可 以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。这就是我们使用队列的方式
- 消费者 消费与接收具有相似的含义。消费者大多时候是一个等待接收消息的程序。请注意生产者,消费 者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。
名词解释
- Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker Virtual
- host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似 于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出 多个 vhost,每个用户在自己的 vhost 创建 exchange/queue 等 Connection:publisher/consumer 和 broker 之间的 TCP 连接
- Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程 序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客 户端和 message
- broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销 Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发 消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
- Queue:消息最终被送到这里等待 consumer 取走 Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保 存到 exchange 中的查询表中,用于
- message 的分发依据
2.安装
链接:https://pan.baidu.com/s/1wONSbvyI2cysgFozXQ98cQ?pwd=i53a 提取码:i53a
- 文件上传 上传到/usr/local/software 目录下(如果没有 software 需要自己创建)
- 安装文件(分别按照以下顺序安装)
rpm -ivh erlang-21.3-1.el7.x86_64.rpm
yum install socat -y
rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm
其中erlang是rabbitmq需要的环境语言,el7表示可以在linux7上安装 使用
uname - a
可以查看版本
- 开始安装
分别输入上面给出的3条命令
- 常用命令(按照以下顺序执行)
添加开机启动 RabbitMQ 服务
chkconfig rabbitmq-server on
启动服务
/sbin/service rabbitmq-server start
查看服务状态
/sbin/service rabbitmq-server status
停止服务(选择执行)
/sbin/service rabbitmq-server stop
开启 web 管理插件
rabbitmq-plugins enable rabbitmq_management
重新启动服务
/sbin/service rabbitmq-server start
web管理界面会在15672端口开启,所以我们需要将15672端口开放( 需要重启防火墙服务)
firewall-cmd --zone=public --add-port=15672/tcp --permanent
重启防火墙服务
firewall-cmd --reload
访问地址 http://192.168.43.128:15672/
将ip地址改为你们自己的linux服务器的ip地址
初始账户密码都为:guest
但是一开始用默认账号密码(guest)访问会出现权限问题,我们需要添加一个新的用户来进行登录。
添加一个新的用户 创建账号(用户名:admin,密码:123)
rabbitmqctl add_user admin 123
设置用户角色(设置admin的角色伟administrator)
rabbitmqctl set_user_tags admin administrator
设置用户权限 set_permissions [-p <vhostpath>] <user> <conf> <write> <read>
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
用户 user_admin 具有/ 这个 virtual host 中所有资源的配置、写、读权限
查询当前用户和角色
rabbitmqctl list_users
再次利用 admin 用户登录,成功登录
3.使用Java发送消息
我们将用 Java 编写两个程序。发送单个消息的生产者和接收消息并打印出来的消费者。我们将介绍 Java API 中的一些细节。
在这之前我们要先将Linux服务器的5672端口开放,用于tcp连接。
firewall-cmd --zone=public --add-port=5672/tcp --permanent
重启防火墙服务
firewall-cmd --reload
引入依赖
<!--rabbitmq 依赖客户端-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
<!--操作文件流的一个依赖-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
消息生产者
java代码
public class Producer {
//队列名称
public static final String QUEUE_NAME = "queue1";
//发消息
public static void main(String[] args) throws IOException, TimeoutException {
//创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂IP 连接RabbitMQ队列
factory.setHost("192.168.43.128");
//用户名
factory.setUsername("admin");
//密码
factory.setPassword("123");
//创建连接
Connection connection = factory.newConnection();
//获取信道
Channel channel = connection.createChannel();
/**
* 生成队列
* 1.队列名称
* 2.队列里面的消息是否持久化
* 3.是否只供一个消费者进行消费 是否进行消息共享
* 4.是否进行自动删除
* 5.其他参数
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//消息
String message = "hello world";
/**
* 发消息
* 1.发送到哪个交换机
* 2.路由的key值是哪个 本次队列的名称
* 3.其他参数
* 4.发送的消息的消息体
*/
channel.basicPublish(QUEUE_NAME,"",null, message.getBytes());
System.out.println("消息发送完毕");
}
}
运行后,查看web管理界面,可以发现已经接受到了一个消息
消息消费者
package com.chenzhen.rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author ChenZhen
* @Description
* @create 2023/3/6 17:11
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
public class Consumer {
//队列名称
public static final String QUEUE_NAME = "queue1";
//接受消息
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂IP 连接RabbitMQ队列
factory.setHost("192.168.43.128");
//用户名
factory.setUsername("admin");
//密码
factory.setPassword("123");
//创建连接
Connection connection = factory.newConnection();
//获取信道
Channel channel = connection.createChannel();
//声明 接受消息
DeliverCallback deliverCallback = (consumerTag,message) ->{
System.out.println("message = " + new String(message.getBody()));
};
//取消消息时的回调
CancelCallback cancelCallback = consumerTag ->{
System.out.println("消息消费被中断");
};
/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功后是否要自动应答 true:自动 false:手动
* 3.消费成功之后的回调
* 4.消费者取消消费的回调
*/
channel.basicConsume(QUEUE_NAME, true,deliverCallback,cancelCallback);
}
}
运行后
封装工具类
package com.chenzhen.rabbitmq.utils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @author ChenZhen
* @Description
* @create 2023/3/6 17:43
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
public class RabbitMqUtils {
public static Channel getChannel() throws Exception{
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂IP 连接RabbitMQ队列
factory.setHost("192.168.43.128");
//用户名
factory.setUsername("admin");
//密码
factory.setPassword("123");
//创建连接
Connection connection = factory.newConnection();
//获取信道
Channel channel = connection.createChannel();
return channel;
}
}
Work Queues
工作队列(又称任务队列)的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时,这些工作线程将一起处理这些任务。
听不太懂,总之就是用多个消费者来并发的处理一个队列里的消息。
在这个案例中我们会启动两个工作线程,一个消息发送线程,我们来看看他们两个工作线程是如何工作的。
package com.chenzhen.rabbitmq.two;
import com.chenzhen.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
/**
* @author ChenZhen
* @Description 这是一个工作线程,相当于是消费者
* @create 2023/3/6 18:02
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
//这是一个工作线程,相当于是消费者
public class Worker01 {
//队列的名称
public static final String QUEUE_NAME = "queue1";
//接受消息
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag,message) -> {
System.out.println(new String (message.getBody()));
};
//取消消息时的回调
CancelCallback cancelCallback = consumerTag ->{
System.out.println("消息消费被中断");
};
System.out.println("c1等待接受消息......");
//消息的接受
channel.basicConsume(QUEUE_NAME, true,deliverCallback,cancelCallback);
}
}
运行后
我们在设置中开启允许多线程。
随后修改输出语句c1为c2,然后再次启动主函数。
可以看到此时运行了2个工作进程。
用来发送多个消息的生产者的代码,即消息发送线程
package com.chenzhen.rabbitmq.two;
import com.chenzhen.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.util.Scanner;
/**
* @author ChenZhen
* @Description
* @create 2023/3/7 11:14
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
public class Task01 {
public static final String QUEUE_NAME = "queue1";
//发送大量消息
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
/**
* 生成队列
* 1.队列名称
* 2.队列里面的消息是否持久化
* 3.是否只供一个消费者进行消费 是否进行消息共享
* 4.是否进行自动删除
* 5.其他参数
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//从控制台中接受信息
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String message = scanner.next();
/**
* 发消息
* 1.发送到哪个交换机
* 2.路由的key值是哪个 本次队列的名称
* 3.其他参数
* 4.发送的消息的消息体
*/
channel.basicPublish(QUEUE_NAME,"", null, message.getBytes());
System.out.println("发送消息:"+ message);
}
}
}
消息发送线程,发送4个消息abcd
第一个消费者接受a c 消息
第二个消费者接受b d消息
消息应答
消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉了,会发生什么情况。RabbitMQ 一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续发送给该消费这的消息,因为它无法接收到。 为了保证消息在发送过程中不丢失,rabbitmq 引入消息应答机制,消息应答就是:消费者在接收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了。
- 自动应答
消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者 channel 关闭,那么消息就丢失了,当然另一方面这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制,当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用
- 手动应答的方法
A.Channel.basicAck(用于肯定确认) RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了
B.Channel.basicNack(用于否定确认)
C.Channel.basicReject(用于否定确认) 与 Channel.basicNack 相比少一个参数 不处理该消息了直接拒绝,可以将其丢弃了
消息自动重新入队
如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或 TCP 连接丢失),导致消息未发送 ACK 确认,RabbitMQ 将了解到消息未完全处理,并将对其重新排队。如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息。
消息生产者代码
package com.chenzhen.rabbitmq.three;
import com.chenzhen.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.util.Scanner;
/**
* @author ChenZhen
* @Description
* @create 2023/3/7 16:51
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
public class Task2 {
//队列名称
public static final String TASK_QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明队列
channel.queueDeclare(TASK_QUEUE_NAME, false, false, false, null);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextInt()){
String message = scanner.next();
channel.basicPublish(TASK_QUEUE_NAME,"", null,message.getBytes("UTF-8"));
System.out.println("生产者发出消息:"+ message);
}
}
}
消息消费者代码,其一
package com.chenzhen.rabbitmq.three;
import com.chenzhen.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.util.concurrent.TimeUnit;
/**
* @author ChenZhen
* @Description
* @create 2023/3/7 17:17
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
public class Work3 {
//队列名称
public static final String TASK_QUEUE_NAME = "ack_queue";
//接收消息
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
System.out.println("c3等待接受消息处理");
DeliverCallback deliverCallback = (consumerTag, message) -> {
try {
//等待1s 模拟处理消息
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new String (message.getBody(),"UTF-8"));
//手动应答
/**
* 1.消息的标记 tag
* 2.是否批量应答 false:不批量应答
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
//取消消息时的回调
CancelCallback cancelCallback = consumerTag ->{
System.out.println("消息消费被中断");
};
boolean autoAck = false;
//采用手动应答
channel.basicConsume(TASK_QUEUE_NAME, autoAck,deliverCallback,cancelCallback);
}
}
消息消费者,其二
package com.chenzhen.rabbitmq.three;
import com.chenzhen.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.util.concurrent.TimeUnit;
/**
* @author ChenZhen
* @Description
* @create 2023/3/7 17:17
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
public class Work4 {
//队列名称
public static final String TASK_QUEUE_NAME = "ack_queue";
//接收消息
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
System.out.println("c4等待接受消息处理");
DeliverCallback deliverCallback = (consumerTag, message) -> {
try {
//等待30s 模拟处理消息
Thread.sleep(1000*30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new String (message.getBody(),"UTF-8"));
//手动应答
/**
* 1.消息的标记 tag
* 2.是否批量应答 false:不批量应答
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
//取消消息时的回调
CancelCallback cancelCallback = consumerTag ->{
System.out.println("消息消费被中断");
};
boolean autoAck = false;
//采用手动应答
channel.basicConsume(TASK_QUEUE_NAME, autoAck,deliverCallback,cancelCallback);
}
}
分别启动3个进程
生产者发送2个消息
如果第二个消费者在消费的时候挂机了,就不会进行应答,队列中的消息不会被销毁,则被转发给第一个消费者
队列持久化
//消息队列持久化
boolean durable = true;
//声明队列
channel.queueDeclare(TASK_QUEUE_NAME, durable, false, false, null);
在声明队列持久化前,如果该队列已存在,则需要先将该队列删除 否则会出现以下错误
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'ack_queue' in vhost '/': received 'true' but current is 'false', class-id=50, method-id=10)
运行代码后,ack_queue队列已开启持久化
消息持久化
要想让消息实现持久化需要在消息生产者修改代码,MessageProperties.PERSISTENT_TEXT_PLAIN 添 加这个属性。
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
添加持久化参数后
//设置生产者发送信息为持久化消息(要求保存到磁盘上)
channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes("UTF-8"));
将消息标记为持久化并不能完全保证不会丢失消息。尽管它告诉 RabbitMQ 将消息保存到磁盘,但是这里依然存在当消息刚准备存储在磁盘的时候 但是还没有存储完,消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强,但是对于我们的简单任务队列而言,这已经绰绰有余了.
PubSub订阅模式
在订阅模型中,多了一个 Exchange 角色,而且过程略有变化: ⚫ P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机) ⚫ C:消费者,消息的接收者,会一直等待消息到来 ⚫ Queue:消息队列,接收消息、缓存消息 ⚫ Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、 递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型: ➢ Fanout:广播,将消息交给所有绑定到交换机的队列 ➢ Direct:定向,把消息交给符合指定routing key 的队列 ➢ Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列 Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合 路由规则的队列,那么消息会丢失!
创建生产者代码
package com.chenzhen.rabbitmq.four;
import com.chenzhen.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import java.util.Scanner;
/**
* @author ChenZhen
* @Description
* @create 2023/3/7 16:51
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
public class PubSub_Producer {
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
// 创建交换机
/**
* 1.exchange:交换机名称
* 2.type : 交换机类型
* DIRECT("direct"):定向
* FANOUT("fanout"):扇形(广播)
* TOPIC("topic"):通配符
* HEADERS("headers"):参数匹配
* 3.durable : 是否持久化
* 4.autoDelete : 是否自动删除
* 5.internal : 内部使用。默认false
* 6.arguments : 参数列表
*/
String exchangeName = "test_fanout";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,false,false,false,null);
//创建队列
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
//消息队列持久化
boolean durable = false;
//声明队列
channel.queueDeclare(queue1Name, durable, false, false, null);
channel.queueDeclare(queue2Name, durable, false, false, null);
//绑定队列和交换机
/**
* 1. queue :队列名称
* 2.exchange: 交换机名称
* 3.routingKey : 路由键,绑定规则
* 如果交换机的类型为 fanout,routinKye设置为“”,
*/
channel.queueBind(queue1Name,exchangeName,"");
channel.queueBind(queue2Name,exchangeName,"");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.next();
//发送消息
channel.basicPublish(exchangeName,"", null ,message.getBytes("UTF-8"));
System.out.println("生产者发出消息:"+ message);
}
}
}
启动后发出消息
查看队列,2个队列都接受到了消息。
Routing 路由模式
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey(路由key)
- 消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey
- Exchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的 Routingkey 与消息的 Routing key 完全一致,才会接收到消息
代码跟前面的基本都差不多,这里就不重复打了 在生产者代码里,将模式修改为BuiltinExchangeType.DIRECT
String exchangeName = "test_direct";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,false,false,false,null);
并在队列和交换机绑定时指定路由key
channel.queueBind(queue1Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"info");
channel.queueBind(queue2Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"warning");
Topics 通配符模式
Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型
Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc 或者 item.insert,item.* 只能匹配 item.insert
这个和路由模式很像,只不过将路由key改成了通配符的形式,代码也是十分简单,修改交换机规则和路由键即可。就不将代码给出了。
Spring Boot整合RabbitMQ
生产者
- 创建生产者SpringBoot工程
- 引入start,依赖坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 编写yml配置,基本信息配置
# 配置rabbitmq的基本信息
spring:
rabbitmq:
host: 192.168.43.128
port: 5672
username: admin
password: 123
virtual-host: /
- 定义交换机,队列以及绑定关系的配置类
package com.chenzhen.springboot_rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author ChenZhen
* @Description
* @create 2023/3/8 17:45
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "boot_topic_exchange";
public static final String QUEUE_NAME = "boot_queue";
//1.交换机
@Bean("bootExchange")
public Exchange bootExchange(){
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//2.queue队列
@Bean("bootQueue")
public Queue bootQueue(){
return QueueBuilder.durable(QUEUE_NAME).build();
}
//3.队列和交换机的绑定关系
@Bean
public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue,@Qualifier("bootExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
}
}
- 注入RabbitTemplate,调用方法,完成消息发送
package com.chenzhen.springboot_rabbitmq;
import com.chenzhen.springboot_rabbitmq.config.RabbitMQConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringbootRabbitmqApplicationTests {
//1.注入rabbitmqTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.haha","boot mq hello ");
}
}
运行测试类后,队列成功接受到消息
消费者
消费者的代码比较简单,只要创建一个监听类并加上@RabbitListener注解即可。
package com.chenzhen.springboot_rabbitmq;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author ChenZhen
* @Description
* @create 2023/3/11 16:14
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
@Component
public class RabbitMQListener {
@RabbitListener(queues = "boot_queue")
public void listenerQueue(Message message){
System.out.println("message = " + new String(message.getBody()));
}
}
评论