目录
1. RabbitMQ简介
1.1 RabbitMQ
1.2 RabbitMQ的介绍
1.3 RabbitMQ的特点
2. RabbitMQ的安装
2.1 RabbitMQ下载
2.2 下载的安装包
2.3 安装步骤
2.4 登录访问
3. RabbitMQ的入门
3.1 RabbitMQ的架构原理
3.2 永远的hello world程序
4. RabbitMQ的工作模式
4.1 简单模式(Hello World)
4.2 工作队列模式(Work Queues)
4.3 发布订阅模式(Publish/Subscribe)
4.4 路由模式(Routing)
4.5 主题模式(Topics)
4.6 发布确认模式(Publisher Confirms)
5. RabbitMQ的注解开发
5.1 简单模式
5.2 工作队列
5.3 发布订阅
5.4 路由模式
5.5 主题模式
5.6 发布确认
学习一门新技术,我们总会遇到许许多多的陌生名词,我们可以从我们熟悉的点切入,去学习和关联这门我们不熟悉的技术。RabbitMQ我们比较熟悉的是Q(Queue)队列的意思,那RabbitMQ是不是就是一个保存消息的队列,更准确的说法是,RabbitMQ是一个消息代理中间件。
基于AMQP协议,erlang语言开发,是部署最广泛的开源消息中间件,是最受欢迎的开源消息中间件之一
RabbitMQ是使用Erlang语言来编写的,实现了AMQP协议的一款MQ产品。AMQP,即Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。MQ(Message Queue)消息队列是以生产者和消费者的异步通信原理来实现系统解耦合的。
官网下载地址: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
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
在浏览器中访问:192.168.8.129:15672 打开登录界面,输入用户名和密码:guest / guest 进行登录,登录成功后就可以进入到RabbitMQ的管理界面。
RabbitMQ中的相关概念:
com.rabbitmq
amqp-client
5.8.0
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();
}
}
}
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);
}
}
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);
}
}
在上图的简单模式中一个生产者(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);
在工作队列模式中一个生产者(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);
}
}
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 ->{});
}
}
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 ->{});
发布订阅模式也被称为广播模式,就是使用广播(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);
}
}
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));
}
});
}
}
路由模式是由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));
}
});
}
}
主题模式本质就是路由模式的升级,也被称为动态路由模式。消费者端在声明队列和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));
}
});
}
}
发布确认模式和前面介绍的模式有所不同,它更像是一种生产者端保证消息能够进行可靠投递的一种发送策略。如何保证消息不丢失这是生产中经常需要解决的问题,我们一般需要对队列和消息进行开启持久化机制,但是还需要考虑生产者端和消费者端的消息丢失问题,而发布确认模式是用于解决生产者发布消息到MQ服务器的过程中的消息丢失问题。从上图可以看出 RabbitMQ 提供的是“至少一次交付”(at-least-once delivery),异常情况下,消息会被重复投递或消费。
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");
}
}
单个消息发布确认策略运行结果:
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 outstandingConfirms = new ConcurrentSkipListMap<>();
//消息确认成功的回调函数
ConfirmCallback ackCallback = ((deliveryTag, multiple) -> {
/*
* 如果multiple等于true表明这次回调为多个消息确认,标识为deliveryTag的消息和他前面的消息都一起被确认
* 如果multiple等于false表明这次回调为单个消息确认,只有标识为deliveryTag的消息被确认
*/
if (multiple) {
// 使用headMap方法拿到标识为deliveryTag的消息和他前面的消息
ConcurrentNavigableMap 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");
}
}
异步消息发布确认策略运行结果:
org.springframework.boot
spring-boot-starter-amqp
spring:
rabbitmq:
host: 192.168.8.129
port: 5672
username: guest
password: guest
virtual-host: /study
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";
}
}
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);
}
}
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();
}
}
}
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";
}
}
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();
}
}
}
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");
}
}
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();
}
}
}
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.*");
}
}
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();
}
}
}
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();
}
}
}
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);
}
}