目录
4.6 发布确认模式(Publisher Confirms)
学习一门新技术,我们总会遇到许许多多的陌生名词,我们可以从我们熟悉的点切入,去学习和关联这门我们不熟悉的技术。RabbitMQ我们比较熟悉的是Q(Queue)队列的意思,那RabbitMQ是不是就是一个保存消息的队列,更准确的说法是,RabbitMQ是一个消息代理中间件。
1. RabbitMQ简介
1.1 RabbitMQ
基于AMQP协议,erlang语言开发,是部署最广泛的开源消息中间件,是最受欢迎的开源消息中间件之一
1.2 RabbitMQ的介绍
RabbitMQ是使用Erlang语言来编写的,实现了AMQP协议的一款MQ产品。AMQP,即Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。MQ(Message Queue)消息队列是以生产者和消费者的异步通信原理来实现系统解耦合的。
1.3 RabbitMQ的特点
- 可靠性。支持持久化,传输确认,发布确认,消费端消息手动确认等保证了MQ的可靠性 。
- 灵活的分发消息策略。分发消息策略有:简单模式、工作队列模式、发布订阅模式、路由模式、主题模式。
- 支持集群。多台RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
- 多种协议。RabbitMQ支持多种消息队列协议,比如 AMQP、STOMP、MQTT 等等。
- 支持多种语言客户端。RabbitMQ几乎支持所有常用编程语言,包括 Java、Python 、JavaScript、Ruby、Go等。
- 插件机制。RabbitMQ提供了许多插件,可以通过插件进行扩展,也可以编写自己的插件。
2. RabbitMQ的安装
2.1 RabbitMQ下载
官网下载地址:https://www.rabbitmq.com/download.html
我们可以根据自身需求和操作系统版本下载自己想要的版本,我现在选择的是Linux操作系统CentOS版本下的 rabbitmq-server-3.8.22-1.el7.noarch.rpm,同时也需要下载erlang语言的依赖 erlang-23.2.5-1.el7.x86_64.rpm
2.2 下载的安装包
2.3 安装步骤
1. 上传rabbitmq的软件包到Linux目录
2. 先安装erlang的依赖包
rmp -ivh erlang-23.2.5-1.el7.x86_64.rpm
3. 再安装socat的依赖包
rmp -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm
4. 最后安装rabbitmq的依赖包
rmp -ivh rabbitmq-server-3.8.22-1.el7.noarch.rpm
5. 安装成功后再新建主配置文件rabbitmq.conf
vim /etc/rabbitmq/rabbitmq.conf
6. rabbitmq.conf 配置文中添加如下配置
# 默认允许登录的用户名,无法修改
default_user = guest
# 默认用户的密码,无法修改
default_pass = guest
# 配置默认用户的身份为管理员
default_user_tags.administrator = true
# 创建默认用户时分配给它的权限
default_permissions.configure = .*
default_permissions.read = .*
default_permissions.write = .*
# 默认loopback_users.guest = true,只允许本机访问,配置“guest”用户能够远程连接
# 注意,生产环境下不建议开启guest用户远程登录
loopback_users = none
7. 用户管理
rabbitmqctl add_user admin 123 # 创建新用户和密码
rabbitmqctl change_password admin admin123 # 修改用户密码
rabbitmqctl set_user_tags admin administrator # 设置角色
rabbitmqctl set_permissions -p / admin '.*' '.*' '.*' # 设置用户权限
8. 开启rabbitmq的管理界面插件功能
rabbitmq-plugins enable rabbitmq_management
9. 启动rabbitmq的服务
systemctl start rabbitmq-server
2.4 登录访问
在浏览器中访问:192.168.8.129:15672 打开登录界面,输入用户名和密码:guest / guest 进行登录,登录成功后就可以进入到RabbitMQ的管理界面。
3. RabbitMQ的入门
3.1 RabbitMQ的架构原理
RabbitMQ中的相关概念:
- Broker:RabbitMQ的实体服务器,提供一种传输服务,维护一条从生产者到消费者的传输线路,保证消息数据能按照指定的方式传输。
- Virtual host:虚拟主机,与用户绑定,用于环境隔离。用户只能在自己的虚拟主机中创建Exchange和Queue,并且同一个虚拟主机里面不能有相同名称的Exchange或Queue。
- Connection:Producer或Consumer和Broker之间建立的TCP连接。
- Channel:Channel是在Connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的Channel进行通信,Channel作为轻量级的Connection极大减少了操作系统建立TCP Connection的开销。
- Exchange:交换机,接收消息,按照路由规则将消息路由到一个或者多个队列。如果路由不到,或者返回给生产者,或者直接丢弃。RabbitMQ常用的交换器常用类型有direct、topic、fanout、headers四种。
- Queue:接收交换机路由的消息的容器,消费者监听队列中有消息后进行消费。
- Binding:绑定,Exchange和Queue之间的虚拟连接,绑定中可以包含一个或者多个routingKey。Binding信息被保存到Exchange中的查询表中,用于消息分发的依据。
3.2 永远的hello world程序
- 添加Maven依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
- 在rabbitmq的管理界面中新建虚拟主机 /study
- 书写RabbitMQUtils工具类
package com.example.rabbitmq.common;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @author: gjm
* @date: 2021/9/2 13:50
* @description: TODO
*/
public class RabbitMQUtils {
private static final ConnectionFactory connectionFactory;
static {
//创建连接mq的连接工厂对象
connectionFactory = new ConnectionFactory();
//设置连接rabbitmq主机
connectionFactory.setHost("192.168.8.129");
//设置端口号
connectionFactory.setPort(5672);
//设置连接哪个虚拟主机
connectionFactory.setVirtualHost("/study");
//设置访问虚拟主机的用户名和密码
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
}
/**
* 获取连接的方法
* @return
*/
public static Connection getRabbitMQConnection(){
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();
}
}
}
- 书写生产者Provider
import com.example.rabbitmq.common.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/2 11:05
* @description: 生产者发送消息
*/
public class Provider {
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException{
//获取连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
//获取连接中通道
Channel channel = connection.createChannel();
//通道绑定对应消息队列
//参数1: 队列名称,如果队列不存在则自动创建
//参数2: 用来定义队列是否要持久化 true:持久化队列,false:不持久化
//参数3: 是否独占队列 true: 独占队列, false: 不独占
//参数4: autoDelete 是否在消费完成后自动删除队列, true:自动删除,false: 不自动删除
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//发布消息
//参数1:交换机名称 参数2:队列名称 参数3:传递消息额外设置 参数4:消息的具体内容
channel.basicPublish("",QUEUE_NAME, null, "hello world".getBytes());
//关闭连接
RabbitMQUtils.closeConnectionAndChanel(channel, connection);
}
}
- 书写消费者Consumer
import com.rabbitmq.client.*;
import com.example.rabbitmq.common.RabbitMQUtils;
import java.io.IOException;
/**
* @author: guojm
* @date: 2021/9/3 0:47
* @description: 消费者消费消息
*/
public class Consumer {
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException{
//获取连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
//获取连接中通道
Channel channel = connection.createChannel();
//通道绑定对应消息队列
//参数1: queue: 队列名称,如果队列不存在则自动创建
//参数2: durable: 队列是否要持久化 true:持久化队列,false:不持久化
//参数3: exclusive: 是否独占队列 true: 独占队列, false: 不独占
//参数4: autoDelete 是否在消费完成后自动删除队列, true:自动删除,false: 不自动删除
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
System.out.println("等待消息中...");
// 消息消费时回调函数
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println("消费消息:" + new String(message.getBody()));
System.out.println("等待消息中...");
};
// 消息取消时回调函数
CancelCallback cancelCallback = consumerTag ->{};
//发布消息
//参数1: 消费哪个队列的消息 队列名称
//参数2: 开始消息的自动确认机制
//参数3: 消息的接收回调
//参数3: 消息的取消回调
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
}
}
- 先执行消费者没有消息时会等待,再执行生产者发送消息,消费者有消息时就会消费消息
4. RabbitMQ的工作模式
4.1 简单模式(Hello World)
在上图的简单模式中一个生产者(Producer)生产的消息只有一个消费者(Consumer)消费,Producer直接发送消息到hello队列,Consumer直接从hello队列中取出消息消费。
- 简单模式生产者代码为入门案例代码
//获取连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
//获取连接中通道
Channel channel = connection.createChannel();
//通道绑定对应消息队列
channel.queueDeclare("hello", true, false, false, null);
//生产者直接发送消息给队列
channel.basicPublish("","hello", null, "hello world".getBytes());
//关闭连接
RabbitMQUtils.closeConnectionAndChanel(channel, connection);
- 消费者代码也是入门案例代码
//获取连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
//获取连接中通道
Channel channel = connection.createChannel();
//通道绑定对应消息队列
channel.queueDeclare("hello", true, false, false, null);
System.out.println("等待消息中...");
// 消息消费时回调函数
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println("消费消息:" + new String(message.getBody()));
System.out.println("等待消息中...");
};
// 消息取消时回调函数
CancelCallback cancelCallback = consumerTag ->{};
// 消费者直接从队列中取出消息消费
channel.basicConsume("hello", true, deliverCallback, cancelCallback);
4.2 工作队列模式(Work Queues)
在工作队列模式中一个生产者(Producer)生产的消息由多个消费者(Consumer)消费,这种情况适合生产者生产消息速度远远大于消息消费速度的场景。这种模式下可以让多个消费者绑定同一个队列,共同消费队列中的消息,多个消费者之间是竞争关系,消息不会被重复消费。
- 工作队列的生产者代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.example.rabbitmq.common.RabbitMQUtils;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/04 22:03
* @description: Work Queues 模式的生产者
*/
public class Producer {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException {
// 获取rabbitmq 的连接
Connection connection = RabbitMQUtils.getRabbitMQConnection();
// 通过连接获取信道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 发布消息给队列
for (int i = 0; i < 10; i++) {
channel.basicPublish("",QUEUE_NAME, null, ("work queues 生产者的消息" + i ).getBytes());
}
// 关闭连接
RabbitMQUtils.closeConnectionAndChanel(channel,connection);
}
}
- 工作队列的消费者代码,创建消费者1 和消费者2共同消费。
import com.rabbitmq.client.*;
import com.example.rabbitmq.common.RabbitMQUtils;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/04 22:11
* @description: Work Queues 模式的消费者
*/
public class Consumer1 {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException {
// 获取rabbitmq的连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
// 通过连接对象获取信道
Channel channel = connection.createChannel();
// 声明队列,如果队列不存在则自动创建
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 消息消费时回调函数
DeliverCallback deliverCallback = (consumerTag, message)-> {
System.out.println("消费者1:" + new String(message.getBody()));
};
// 获取队列中的消息
channel.basicConsume(QUEUE_NAME, true, deliverCallback,consumerTag ->{});
}
}
- 先运行消费者1和消费者2代码,再运行生产者代码查看消费情况。从下面的结果我们可以看出工作队列模式中的消息默认是循环分发给每一个消费者的,就是不管消费者的消费速度如何都会按照轮询分发给每一个消费者。
- 工作队列模式中的代码优化,消费者端不开启消息的自动确认机制,autoAck设置为fasle,开启消息的自动确认机制可能会导致消息丢失。因为消费者端开启消息的自动确认机制后消息会轮询分发给消费者等待消费,不管消息是否消费成功都会立马在队列中删除,这时如果消费端宕机就会导致消息丢失。
import com.rabbitmq.client.*;
import com.example.rabbitmq.common.RabbitMQUtils;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/04 22:11
* @description: Work Queues 消费者手动确认消息
*/
public class Consumer1 {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException {
// 获取rabbitmq的连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
// 通过连接对象获取信道
Channel channel = connection.createChannel();
// 声明队列,如果队列不存在则自动创建
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 消息消费时回调函数
DeliverCallback deliverCallback = (consumerTag, message)-> {
System.out.println("消费者1:" + new String(message.getBody()));
// 手动确认消息
// 参数1:被确认消息的标签信息,参数2:是否批量确认消息
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
// 消费队列中的消息,关闭消息的自动确认机制,autoAck设置为false
channel.basicConsume(QUEUE_NAME, false, deliverCallback,consumerTag ->{});
}
}
- 工作队列模式实现消费端能者多劳,即消费者消费快的能多消费消息,实现消息的不公平分发。这时只需要在消费者端设置信道每次只能接受一个消息等待消费,消费者消费快的就能多接收消息消费。
// 消费者端设置信道每次只能接收一个消息
channel.basicQos(1);
// 消息消费时回调函数
DeliverCallback deliverCallback = (consumerTag, message)-> {
//模拟消息消费慢的场景
try{
Thread.sleep(500);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("消费者1:" + new String(message.getBody()));
// 手动确认消息
// 参数1:被确认消息的标签信息,参数2:是否批量确认消息
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
// 消费队列中的消息,关闭消息的自动确认机制,autoAck设置为false
channel.basicConsume(QUEUE_NAME, false, deliverCallback,consumerTag ->{});
- 工作队列模式中的能者多劳效果实现
4.3 发布订阅模式(Publish/Subscribe)
发布订阅模式也被称为广播模式,就是使用广播(fanout)类型的交换机实现的。生产者直接发送消息给交换机,由交换机负责将消息转发给绑定的多个队列实现被多个消费者消费的效果。这个模式实现一个消息被多个消费者消费的原理是用到了多个临时队列,fanout型交换机把一个消息转发到了多个队列中,每个队列的消费者各自消费自己绑定队列中的消息。
- 发布订阅模式的生产者代码
import com.example.rabbitmq.common.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/2 21:06
* @description: Publish/Subscribe模型的生产者
*/
public class Producer {
public static void main(String[] args)throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
Channel channel = connection.createChannel();
//通过通道声明指定交换机
// 参数1:交换机名称 参数2:交换机类型 fanout广播类型
channel.exchangeDeclare("logs", "fanout");
//发布消息给交换机
channel.basicPublish("logs", "",null, "Publish/Subscribe 生产者的消息".getBytes());
//关闭连接
RabbitMQUtils.closeConnectionAndChanel(channel, connection);
}
}
- 发布订阅模式的消费者代码,创建消费者1和消费者2演示消息的广播效果
import com.example.rabbitmq.common.RabbitMQUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/2 21:30
* @description: Publish/Subscribe模型的消费者1
*/
public class consumer1 {
public static void main(String[] args)throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
//获取通道
Channel channel = connection.createChannel();
//通道绑定交换机
channel.exchangeDeclare("logs", "fanout");
//临时队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和临时队列
channel.queueBind(queueName, "logs", "");
//消费消息
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));
}
});
}
}
- 先运行消费者在管理界面中查看创建的临时队列信息
- 消费者1和消费者2都消费到了生产者发送的消息
4.4 路由模式(Routing)
路由模式是由direct类型的交换机实现的,交换机和队列通过routingKey进行绑定,生产者发送消息给交换机时需要指定routingKey,交换机在转发消息给队列时会根据routingKey直接找到队列转发。路由模式下同一个routingKey可以和多个队列绑定,所以也能实现广播模式的效果,但是业务中一般是消息是需要由不同的标识做区别处理场景下使用。
- 路由模式的生产者代码
import com.example.rabbitmq.common.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/3 9:50
* @description: 路由模式的生产者
*/
public class Producer {
public static final String ROUTING_KEY = "error";
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
//获取连接中的信道
Channel channel = connection.createChannel();
//通过信道声明交换机名称和类型
channel.exchangeDeclare("logs direct", "direct");
//发送消息给交换机并指定routingKey
channel.basicPublish("logs direct", ROUTING_KEY,null, ("路由模型生产者发布的routingKey:"+ ROUTING_KEY +"的消息").getBytes());
//关闭连接
RabbitMQUtils.closeConnectionAndChanel(channel,connection);
}
}
- 路由模式的消费者代码
import com.example.rabbitmq.common.RabbitMQUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/3 10:04
* @description: 路由模式的消费者
*/
public class Consumer1 {
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
//获取连接的信道对象
Channel channel = connection.createChannel();
//通过信道声明交换机名称和类型
channel.exchangeDeclare("logs direct", "direct");
//创建一个临时队列
String queue = channel.queueDeclare().getQueue();
//基于routingkey绑定队列和交换机
channel.queueBind(queue, "logs direct", "error");
//获取消费的消息
channel.basicConsume(queue, 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));
}
});
}
}
- 消费者1程序运行,生产者发送routingKey为error的消息只能被routingKey也为error的消费者消费
4.5 主题模式(Topics)
主题模式本质就是路由模式的升级,也被称为动态路由模式。消费者端在声明队列和topic类型的交换机绑定时采用通配符形式的routingKey达到动态匹配的效果,这种模型routingKey一般是由一个或多个单词组成,多个单词之间以 “.” 分割,例如 order.create
- 通配符 * 只能代替一个词
比如:user.* 只能匹配 user.create 或 user.update这种.后面一个单词的
- 通配符 # 可以替换零个或多个单词
比如:order.# 能匹配 order.seckill.create 或 order.delete
- 主题模式的生产者代码
import com.example.rabbitmq.common.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/3 10:38
* @description: topics模式的生产者
*/
public class Producer {
public static final String ROUTING_KEY = "order.seckill.create";
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
//获取连接的信道对象
Channel channel = connection.createChannel();
//通过信道声明交换机名称和类型
channel.exchangeDeclare("topics", "topic");
//发布消息
channel.basicPublish("topics", ROUTING_KEY,null, ("主题模式生产者发布的routingKey:"+ ROUTING_KEY +"的消息").getBytes());
//关闭连接
RabbitMQUtils.closeConnectionAndChanel(channel,connection);
}
}
- 主题模式的消费者代码
import com.example.rabbitmq.common.RabbitMQUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/3 10:51
* @description: topics模式的消费者
*/
public class Consumer1 {
public static void main(String[] args) throws IOException{
//获取连接对象
Connection connection = RabbitMQUtils.getRabbitMQConnection();
//获取连接的信道对象
Channel channel = connection.createChannel();
//通过信道声明交换机名称和类型
channel.exchangeDeclare("topics", "topic");
//通过信道声明队列
channel.queueDeclare("Q1", true, false, false, null);
//基于通配符的routingkey绑定队列和交换机
channel.queueBind("Q1", "topics", "order.#");
//消费消息
channel.basicConsume("Q1", 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));
}
});
}
}
- 消费者1程序运行,生产者发送routingKey为order.seckill.create的消息被routingKey为order.#的消费者消费
4.6 发布确认模式(Publisher Confirms)
发布确认模式和前面介绍的模式有所不同,它更像是一种生产者端保证消息能够进行可靠投递的一种发送策略。如何保证消息不丢失这是生产中经常需要解决的问题,我们一般需要对队列和消息进行开启持久化机制,但是还需要考虑生产者端和消费者端的消息丢失问题,而发布确认模式是用于解决生产者发布消息到MQ服务器的过程中的消息丢失问题。从上图可以看出 RabbitMQ 提供的是“至少一次交付”(at-least-once delivery),异常情况下,消息会被重复投递或消费。
- 单个消息发布确认策略,即每次发布一个消息后都等待MQ服务端应答确认成功后再继续发下一个消息;这种方式消息投递的效率不高,但是比较可靠。
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import com.example.rabbitmq.common.RabbitMQUtils;
import java.io.IOException;
import java.util.UUID;
/**
* @author: gjm
* @date: 2021/9/05 8:50
* @description: 单个消息发布确认策略
*/
public class PublishMessageIndividually {
public static void main(String[] args) throws IOException, InterruptedException {
// 获取rabbitmq的连接
Connection connection = RabbitMQUtils.getRabbitMQConnection();
// 通过连接创建信道
Channel channel = connection.createChannel();
// 生成队列名称
String queueName = UUID.randomUUID().toString();
// 声明队列,开启队列持久化
channel.queueDeclare(queueName, true, false,false, null);
// 开启发布确认
channel.confirmSelect();
// 开始时间
long begin = System.currentTimeMillis();
int count = 0;
// 批量发消息
for (int i = 0; i < 1000; i++) {
String message ="单个确认模式的消息: "+i;
// 投递消息,设置消息的持久化
channel.basicPublish("",queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
// 单个消息发送后,调用同步等待确认方法获取结果
boolean flag = channel.waitForConfirms();
// 如果flag为true则表明消息被确认成功投递
if(flag) count++;
}
// 如果确认的消息数等于发布数则消息被全部投递成功
if (count == 1000) {
System.out.println("成功投递"+ count + "个消息并且确认");
}
//关闭连接
RabbitMQUtils.closeConnectionAndChanel(channel, connection);
long end = System.currentTimeMillis();
System.out.println("投递"+ 1000 + "个单个确认的消息,耗时:"+(end-begin)+"ms");
}
}
单个消息发布确认策略运行结果:
- 批量消息发布确认策略,即每次发布一批消息后才去请求MQ服务端的确认应答结果。这种方式要保证这一批消息全部都成功发送才能拿到确认成功的结果,中间只要有一条发送不成功拿到的是全局不成功的结果false。官网上已经说明这样会导致我们不知道哪条信息没有被成功发送,需要记录这批消息的内容做消息的发送重试,这会导致消息的重复发送,消费者端要处理消息的幂等性问题。
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import com.example.rabbitmq.common.RabbitMQUtils;
import java.io.IOException;
import java.util.UUID;
/**
* @author: gjm
* @date: 2021/9/05 9:57
* @description: 消息批量确认策略
*/
public class PublishMessageBatch {
public static void main(String[] args) throws IOException, InterruptedException {
// 获取rabbitmq的连接
Connection connection = RabbitMQUtils.getRabbitMQConnection();
// 通过连接创建信道
Channel channel = connection.createChannel();
// 生成队列名称
String queueName = UUID.randomUUID().toString();
// 声明队列,开启队列持久化
channel.queueDeclare(queueName, true, false,false, null);
// 开启发布确认
channel.confirmSelect();
// 开始时间
long begin = System.currentTimeMillis();
// 每次确认需要投递的消息总数
int batchSize = 100;
// 记录已投递的消息总数
int outstandingMessageCount = 0;
// 记录确认的次数
int confirmCount = 0;
// 批量发消息
for (int i = 0; i < 1000; i++) {
String message ="批量确认模式的消息: "+i;
// 投递消息,设置消息的持久化
channel.basicPublish("",queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
// 已投递的消息+1
outstandingMessageCount ++;
// 已投递的消息总数达到确认要求
if (outstandingMessageCount == batchSize) {
outstandingMessageCount = 0;
confirmCount ++;
boolean confirms = channel.waitForConfirms();
if (confirms) {
System.out.println("第" +confirmCount+ "次投递"+ batchSize +"条消息并确认");
}
}
}
RabbitMQUtils.closeConnectionAndChanel(channel, connection);
long end = System.currentTimeMillis();
System.out.println("投递"+ 1000 + "个批量确认消息,耗时:"+(end-begin)+"ms");
}
}
批量消息发布确认策略运行结果:
- 异步消息发布确认策略,即消息的发布和确认是异步的,可以显著提升消息发布的速度,也能通过异步消息确认的回调函数记录出未被确认投递成功的消息,三种消息确认发布策略中官网也指出这种效率为最高。
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmCallback;
import com.rabbitmq.client.Connection;
import com.example.rabbitmq.common.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
/**
* @author: gjm
* @date: 2021/9/05 11:12
* @description: 异步消息发布确认策略
*/
public class PublishMessageAsync {
public static void main(String[] args) throws IOException{
// 获取rabbitmq的连接
Connection connection = RabbitMQUtils.getRabbitMQConnection();
// 通过连接创建信道
Channel channel = connection.createChannel();
// 声明队列,开启队列持久化
String queueName = "confirm_queue";
channel.queueDeclare(queueName, true, false,false, null);
// 开启发布确认
channel.confirmSelect();
// 开始时间
long begin = System.currentTimeMillis();
// 记录未确认的消息,方便后续重发
ConcurrentSkipListMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();
//消息确认成功的回调函数
ConfirmCallback ackCallback = ((deliveryTag, multiple) -> {
/*
* 如果multiple等于true表明这次回调为多个消息确认,标识为deliveryTag的消息和他前面的消息都一起被确认
* 如果multiple等于false表明这次回调为单个消息确认,只有标识为deliveryTag的消息被确认
*/
if (multiple) {
// 使用headMap方法拿到标识为deliveryTag的消息和他前面的消息
ConcurrentNavigableMap<Long, String> confirmed = outstandingConfirms.headMap(deliveryTag, true);
// 批量删除已确认的消息
confirmed.clear();
} else {
// 删除单个确认的消息
outstandingConfirms.remove(deliveryTag);
}
System.out.println("多个消息确认:"+ multiple + " -- 确认的消息deliveryTag:"+ deliveryTag);
});
//消息未确认的回调函数
ConfirmCallback nackCallback = ((deliveryTag, multiple) -> {
// 取出未确认的消息
String message = outstandingConfirms.get(deliveryTag);
// 打印消息的详情
System.out.println("message:"+ message+ " -- deliveryTag:" +deliveryTag+ " -- multiple:"+multiple);
});
// 消息的监听器,监听哪些消息成功和失败
channel.addConfirmListener(ackCallback, nackCallback);
// 批量发消息
for (int i = 0; i < 1000; i++) {
String message ="批量异步确认模式的消息: "+i;
channel.basicPublish("",queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
// 保存未处理的确认信息
outstandingConfirms.put(channel.getNextPublishSeqNo(), message);
}
long end = System.currentTimeMillis();
System.out.println("投递"+ 1000 + "个异步批量确认消息,耗时:"+(end-begin)+"ms");
}
}
异步消息发布确认策略运行结果:
5. RabbitMQ的注解开发
5.1 简单模式
- 添加springboot整合rabbitmq的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 在application.yml文件中配置如下内容
spring:
rabbitmq:
host: 192.168.8.129
port: 5672
username: guest
password: guest
virtual-host: /study
- 书写生产者的代码,使用接口开发,在SpringBoot程序的主入口类上书写注解@EnableRabbit ,表示开启rabbitmq的自动配置功能,然后就可以直接注入和使用RabbitTemplate对象非常简单地发送消息给指定队列。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: gjm
* @date: 2021/9/04 10:14
* @description: 生产者端
*/
@RestController
@RequestMapping("/producer")
public class ProducerController {
private Logger logger = LoggerFactory.getLogger(ProducerController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/simple")
public String sendSimpleMessage(){
String message = "Hello World";
rabbitTemplate.convertAndSend("hello", message);
logger.info("简单模式的生产者发送了消息:{}", message);
return "success";
}
}
- 书写消费者的代码,消费者端使用@RabbitListener注解来声明一个队列,表示这是消费者端。通过@Queue(value = "hello")指定的队列名称,并且可以指定队列持久化,独占,自动删除等参数,默认不指定的话队列是持久化,非独占,非自动删除的。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
/**
* @author: gjm
* @date: 2021/9/04 9:40
* @description: 消费者端
*/
@Component
@RabbitListener(queuesToDeclare = @Queue(value = "hello"))
public class Consumer {
private Logger logger = LoggerFactory.getLogger(Consumer.class);
@RabbitHandler
public void receive(String message){
logger.info("简单模式的消费者接收了消息:{}", message);
}
}
- 运行程序,调用接口/producer/simple发送消息,消费者端会监听到消息并处理。
5.2 工作队列
- 加入配置
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 消费预取值,设置为1可实现消费者能者多劳,不设置则为轮询分发
acknowledge-mode: manual # 消息需消费者手动确认
- 生产者代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: gjm
* @date: 2021/9/04 10:14
* @description: 生产者端
*/
@RestController
@RequestMapping("/producer")
public class ProducerController {
private Logger logger = LoggerFactory.getLogger(ProducerController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/work")
public String sendWorkMessage(){
for (int i = 0; i < 10; i++) {
String message = "work mode message " + i;
rabbitTemplate.convertAndSend("work", message);
}
logger.info("工作模式的生产者发送了10条消息");
return "success";
}
}
- 消费者代码
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* @author: gjm
* @date: 2021/9/04 9:15
* @description: 工作队列消费者
*/
@Component
public class WorkConsumer {
private Logger logger = LoggerFactory.getLogger(WorkConsumer.class);
@RabbitListener(queuesToDeclare = @Queue(value = "work"))
public void consumer1(String body, Message message, Channel channel) throws InterruptedException{
logger.info("工作模式的消费者1接收了消息:{}", body);
TimeUnit.MILLISECONDS.sleep(500);
try {
// 消费者手动确认消息已消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
}
}
@RabbitListener(queuesToDeclare = @Queue(value = "work"))
public void consumer2(String body, Message message, Channel channel) {
logger.info("工作模式的消费者2接收了消息:{}", body);
try {
// 消费者手动确认消息已消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.3 发布订阅
- 使用配置类的方式声明队列和交换机并进行绑定(Binding)
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: gjm
* @date: 2021/9/05 8:16
* @description: 发布订阅模式的配置类
*/
@Configuration
public class FanoutModeConfig {
// 交换机名称
public static final String FANOUT_EXCHANGE_NAME = "fanout_exchange";
// 队列名称
public static final String FANOUT_QUEUE1_NAME = "fanout_queue1";
// 队列名称
public static final String FANOUT_QUEUE2_NAME = "fanout_queue2";
// 创建fanout类型的交换机
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange(FANOUT_EXCHANGE_NAME);
}
// 声明队列
@Bean
public Queue fanoutQueue1() {
return QueueBuilder.durable(FANOUT_QUEUE1_NAME).build();
}
// 声明队列
@Bean
public Queue fanoutQueue2() {
return QueueBuilder.durable(FANOUT_QUEUE2_NAME).build();
}
// 队列fanoutQueue1和交换机绑定
@Bean
public Binding queue1BindingExchange(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
// 队列fanoutQueue2和交换机绑定
@Bean
public Binding queue2BindingExchange(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
- 生产者代码,生产者直接发送消息给交换机。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: gjm
* @date: 2021/9/05 10:14
* @description: 生产者端
*/
@RestController
@RequestMapping("/producer")
public class ProducerController {
private Logger logger = LoggerFactory.getLogger(ProducerController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/fanout")
public String sendFanoutMessage(){
String message = "fanout mode message";
rabbitTemplate.convertAndSend("fanout_exchange", "", message);
logger.info("广播模式的生产者发送了消息:{}", message);
return "success";
}
}
- 消费者代码,消费者端使用@RabbitListener(queues = "queueName")直接监听队列就行,这里无需再声明和创建队列,配置类中已经声明。
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/05 9:15
* @description: 发布订阅模式的消费者
*/
@Component
public class FanoutConsumer {
private Logger logger = LoggerFactory.getLogger(FanoutConsumer.class);
@RabbitListener(queues = "fanout_queue1")
public void consumer1(String body, Message message, Channel channel) {
logger.info("广播模式的消费者1接收了消息:{}", body);
try {
// 消费者手动确认消息已消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
}
}
@RabbitListener(queues = "fanout_queue2")
public void consumer2(String body, Message message, Channel channel){
logger.info("广播模式的消费者2接收了消息:{}", body);
try {
// 消费者手动确认消息已消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.4 路由模式
- 使用direct类型的交换机通过routingKey和队列进行绑定(Binding)
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: gjm
* @date: 2021/9/05 8:16
* @description: 路由模式的配置类
*/
@Configuration
public class DirectModeConfig {
// 交换机名称
public static final String DIRECT_EXCHANGE_NAME = "direct_exchange";
// 队列名称
public static final String DIRECT_QUEUE_NAME = "direct_queue";
// 创建direct类型的交换机
@Bean
public DirectExchange directExchange(){
return new DirectExchange(DIRECT_EXCHANGE_NAME);
}
// 声明队列
@Bean
public Queue directQueue(){
return QueueBuilder.durable(DIRECT_QUEUE_NAME).build();
}
// 队列directQueue和交换机使用routingKey绑定
@Bean
public Binding queueBindingExchange(Queue directQueue, DirectExchange directExchange){
return BindingBuilder.bind(directQueue).to(directExchange).with("route.test");
}
}
- 生产者代码,生产者发送消息给交换机时指定routingKey。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: gjm
* @date: 2021/9/05 19:14
* @description: TODO
*/
@RestController
@RequestMapping("/producer")
public class ProducerController {
private Logger logger = LoggerFactory.getLogger(ProducerController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/routing")
public String sendRoutingMessage(){
String message = "routing mode message";
rabbitTemplate.convertAndSend("direct_exchange","route.test", message);
logger.info("路由模式的生产者发送了消息:{}", message);
return "success";
}
}
- 消费者代码
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/05 21:15
* @description: 路由模式的消费者
*/
@Component
public class DirectConsumer {
private Logger logger = LoggerFactory.getLogger(DirectConsumer.class);
@RabbitListener(queues = "direct_queue")
public void consumer(String body, Message message, Channel channel) {
logger.info("路由模式的消费者接收了消息:{}", body);
try {
// 消费者手动确认消息已消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.5 主题模式
- 使用topic类型的交换机通过routingKey和队列进行绑定(Binding)
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: gjm
* @date: 2021/9/05 8:16
* @description: 主题模式的配置类
*/
@Configuration
public class TopicModeConfig {
// 交换机名称
public static final String TOPIC_EXCHANGE_NAME = "topic_exchange";
// 队列名称
public static final String TOPIC_QUEUE_NAME = "topic_queue";
// 创建topic类型的交换机
@Bean
public TopicExchange topicExchange(){
return new TopicExchange(TOPIC_EXCHANGE_NAME);
}
// 声明队列
@Bean
public Queue topicQueue(){
return QueueBuilder.durable(TOPIC_QUEUE_NAME).build();
}
// 队列topicQueue和交换机使用routingKey绑定
@Bean
public Binding topicQueueBindingExchange(Queue topicQueue, TopicExchange topicExchange){
return BindingBuilder.bind(topicQueue).to(topicExchange).with("topic.*");
}
}
- 生产者代码,生产者发送消息给交换机时指定routingKey 。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: gjm
* @date: 2021/9/05 19:14
* @description: 主题模式的生产者
*/
@RestController
@RequestMapping("/producer")
public class ProducerController {
private Logger logger = LoggerFactory.getLogger(ProducerController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/topic")
public String sendTopicMessage(){
String message = "topic mode message";
rabbitTemplate.convertAndSend("topic_exchange","topic.test", message);
logger.info("主题模式的生产者发送了消息:{}", message);
return "success";
}
}
- 消费者代码,消费者端直接监听队列
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/05 19:15
* @description: 主题模式的消费者
*/
@Component
public class TopicConsumer {
private Logger logger = LoggerFactory.getLogger(TopicConsumer.class);
@RabbitListener(queues = "topic_queue")
public void consumer(String body, Message message, Channel channel) {
logger.info("主题模式的消费者接收了消息:{}", body);
try {
// 消费者手动确认消息已消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.6 发布确认
- 配置文件中配置:spring.rabbitmq.publisher-confirm-type: correlated;默认值为none,禁用发布确认模式;还可以设置为simple。
spring:
rabbitmq:
publisher-confirm-type: correlated # 消息发送给交换机时会触发回调
publisher-returns: true # 交换机无法路由到队列的消息会触发回调变成回退消息
-
实现交换机确认的回调接口RabbitTemplate.ConfirmCallback
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; /** * @author: gjm * @date: 2021/9/05 20:09 * @description: 消息发送到交换机的确认回调实现 */ @Component public class MyConfirmCallback implements RabbitTemplate.ConfirmCallback { private Logger logger = LoggerFactory.getLogger(MyConfirmCallback.class); @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void init(){ rabbitTemplate.setConfirmCallback(this); } @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { String id = correlationData.getId(); if(ack) { logger.info("交换机确认收到id:{}的消息", id); }else { // 这里需要对发送失败id的消息做消息处理,后续做消息补偿,应保证消息可通过id找回 logger.info("交换机未收到id:{}的消息, 原因是:{}", id, cause); } } }
- 发布确认模式的配置类
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: gjm
* @date: 2021/9/05 20:17
* @description: 发布确认模式配置类
*/
@Configuration
public class PublisherConfirmConfig {
// 交换机名称
public static final String PUBLISHER_CONFIRM_EXCHANGE = "publisher_confirm_exchange";
// 队列的名称
public static final String PUBLISHER_CONFIRM_QUEUE = "publisher_confirm_queue";
// 路由键的值
public static final String CONFIRM_ROUTING_KEY = "confirm.routing.key";
// 创建direct类型的交换机
@Bean
public DirectExchange confirmExchange(){
return new DirectExchange(PUBLISHER_CONFIRM_EXCHANGE);
}
// 创建队列
@Bean
public Queue confirmQueue(){
return QueueBuilder.durable(PUBLISHER_CONFIRM_QUEUE).build();
}
// 队列confirmQueue和交换机通过routingKey绑定
@Bean
public Binding confirmQueueBindingExchange(Queue confirmQueue, DirectExchange confirmExchange){
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTING_KEY);
}
}
- 发布确认的生产者代码
import com.mj.rabbitmq.config.PublisherConfirmConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
/**
* @author: gjm
* @date: 202/9/05 20:35
* @description: 发布确认模式发送消息的生产者
*/
@RestController
@RequestMapping("/confirm")
public class PublisherConfirmController {
private Logger logger = LoggerFactory.getLogger(PublisherConfirmController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendMsg/{confirmMsg}")
public void sendConfirmMessage(@PathVariable("confirmMsg")String confirmMsg) {
// 生成消息id,为了消息不丢失,应该保存消息到数据库
String msgId = UUID.randomUUID().toString();
// 生成消息的唯一标识信息
CorrelationData correlationData = new CorrelationData(msgId);
rabbitTemplate.convertAndSend(PublisherConfirmConfig.PUBLISHER_CONFIRM_EXCHANGE,
PublisherConfirmConfig.CONFIRM_ROUTING_KEY, confirmMsg, correlationData);
logger.info("发布确认的生产者发送消息:{}", confirmMsg);
}
}
- 发布确认的消费者代码
import com.mj.rabbitmq.config.PublisherConfirmConfig;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author: gjm
* @date: 2021/9/05 12:41
* @description: 发布确认模式发送消息的消费者
*/
@Component
@RabbitListener(queues = PublisherConfirmConfig.PUBLISHER_CONFIRM_QUEUE)
public class PublisherConfirmConsumer {
private Logger logger = LoggerFactory.getLogger(PublisherConfirmConsumer.class);
@RabbitHandler
public void receive(String msg, Message message, Channel channel){
logger.info("发布确认的消费者收到了消息:{}", msg);
try {
// 消费者手动确认消息已消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 查看消息发送给交换机后的确认回调结果
- 实现交换机无法路由到队列的消息进行回调的接口RabbitTemplate.ReturnCallback
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author: gjm
* @date: 2021/09/05 21:02
* @description: 消息发送到交换机的确认回调实现
*/
@Component
public class MyReturnCallback implements RabbitTemplate.ReturnCallback {
private Logger logger = LoggerFactory.getLogger(MyReturnCallback.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
rabbitTemplate.setReturnCallback(this);
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText,
String exchange, String routingKey) {
logger.info("replyCode:{} -- replyText:{} -- exchange:{} -- routingKey:{}",
replyCode, replyText, exchange, routingKey);
logger.info("交换机无法路由到队列的消息:{}", new String(message.getBody()));
}
}
- 产生回退消息的生产者代码
import com.mj.rabbitmq.config.PublisherConfirmConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
/**
* @author: gjm
* @date: 2021/9/05 21:05
* @description: 发布确认发送消息的生产者
*/
@RestController
@RequestMapping("/confirm")
public class PublisherConfirmController {
private Logger logger = LoggerFactory.getLogger(PublisherConfirmController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendReturnMsg/{returnMsg}")
public void sendReturnMessage(@PathVariable("returnMsg")String returnMsg) {
// 生成消息id,为了消息不丢失,应该保存消息到数据库
String msgId = UUID.randomUUID().toString();
// 生成消息的唯一标识信息
CorrelationData correlationData = new CorrelationData(msgId);
rabbitTemplate.convertAndSend(PublisherConfirmConfig.PUBLISHER_CONFIRM_EXCHANGE,
"return_routing_key", returnMsg, correlationData);
logger.info("发布确认的生产者发送消息:{}", returnMsg);
}
}
- 生产者发送消息后查看结果,交换机可以接收到消息,队列无法接收到消息 ,从回调函数中的打印结果中我们知道消息无法路由的原因是routingKey不存在,消息变成回退消息。