引言:
本文主要分享有关RabbitMQ的相关知识,包括:RabbitMQ的简介、利用main进行演示各种情况下的消息队列、非持久化的简单案例、工作队列、公平的分发消息、实现持久化队列的方法;
rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统,遵循Mozilla Public License开源协议,服务器端用Erlang语言编写,支持多种客户端的工业级的消息队列(MQ)服务器,RabbitMQ 是建立在Erlang OTP平台上;
AMQP(Advanced Message Queuing Protocol),高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
AMQP协议本身包含三层:
Model Layer:位于协议最高层,主要定义了一些供客户端调用的命令,客户端可以通过这些命令实现自己的业务逻辑,例如,客户端可以通过queue declare声明一个队列,利用consume命令获取队列的消息;
Session Layer:主要负责将客户端命令发送给服务器,在将服务器端的应答返回给客户端,主要为客户端与服务器之间通信提供可靠性、同步机制和错误处理;
Transport Layer:主要传输二进制数据流,提供帧的处理、信道复用、错误检测和数据表示;
RabbitMQ的安装环境配置:https://blog.csdn.net/weixin_42601136/article/details/108672763
创建SpringBoot项目,加入web和lombook;
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
dependency>
简单的入门案例
- P: producer 生产者
- C: consumer 消费者
- 中间红色的部分是一个队列,在RabbitMQ中代表消息缓冲区
创建一个生产者,默认非持久化,这里发送了两次
六步:
- 创建连接工厂
- 创建连接
- 创建信道
- 创建消息
- 发送消息
- 释放资源
/**
* 生产者,消息发送者
* Created by Kak on 2020/9/21.
*/
public class ProducerMessage {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//编程端口
connectionFactory.setUsername("kaka");//本机可以使用guest
connectionFactory.setPassword("kaka");
connectionFactory.setVirtualHost("/");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建信道
Channel channel = connection.createChannel();
//创建消息
String msg = "This is rabbitMQ message!!!";
//发送消息
channel.basicPublish("","hello",null,msg.getBytes());
//释放资源
channel.close();
connection.close();
}
}
从队列中获取消息
五步:
- 创建连接工厂
- 创建连接
- 创建信道
- 消费队列里面的消息
- 释放资源
/**
* 消息接受者
* Created by Kak on 2020/9/21.
*/
public class ConsumerMessage {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//编程端口
connectionFactory.setUsername("kaka");//本机可以使用guest
connectionFactory.setPassword("kaka");
connectionFactory.setVirtualHost("/");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建信道
Channel channel = connection.createChannel();
System.out.println("准备接收消息!!!");
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumer--------" + new String(body, "UTF-8"));
}
};
/**
* 1:侦听的队列
* 2:是否自动提醒
* 3:接收消息后回调执行的逻辑
*/
channel.basicConsume("hello", true, consumer);
//使用标准输入中断
System.in.read();
channel.close();
connection.close();
}
}
发送成功!!!
看似公平实则不公平,两个收消息的一人一个,忽略了每个接受端的工作效率;
/**
* 生产者,消息发送者
* Created by Kak on 2020/9/21.
*/
public class ProducerMessage {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//编程端口
connectionFactory.setUsername("kaka");//本机可以使用guest
connectionFactory.setPassword("kaka");
connectionFactory.setVirtualHost("/");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建信道
Channel channel = connection.createChannel();
for(int i = 0; i<10;i++){
//创建消息
String msg = "This is rabbitMQ message!!!"+i;
//发送消息
channel.basicPublish("","hello",null,msg.getBytes());
System.out.println("------producer------:"+msg);
}
//释放资源
channel.close();
connection.close();
}
}
/**
* 消息接受者
* Created by Kak on 2020/9/21.
*/
public class ConsumerMessage_01 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//编程端口
connectionFactory.setUsername("kaka");//本机可以使用guest
connectionFactory.setPassword("kaka");
connectionFactory.setVirtualHost("/");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建信道
Channel channel = connection.createChannel();
System.out.println("准备接收消息!!!");
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumer1--------" + new String(body, "UTF-8"));
}
};
/**
* 1:侦听的队列
* 2:是否自动提醒
* 3:接收消息后回调执行的逻辑
*/
channel.basicConsume("hello", true, consumer);
//使用标准输入中断
System.in.read();
channel.close();
connection.close();
}
}
/**
* 消息接受者
* Created by Kak on 2020/9/21.
*/
public class ConsumerMessage_02 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//编程端口
connectionFactory.setUsername("kaka");//本机可以使用guest
connectionFactory.setPassword("kaka");
connectionFactory.setVirtualHost("/");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建信道
Channel channel = connection.createChannel();
System.out.println("准备接收消息!!!");
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumer2--------" + new String(body, "UTF-8"));
}
};
/**
* 1:侦听的队列
* 2:是否自动提醒
* 3:接收消息后回调执行的逻辑
*/
channel.basicConsume("hello", true, consumer);
//使用标准输入中断
System.in.read();
channel.close();
connection.close();
}
}
轮询机制会造成资源浪费的问题,原因是因为RabbitMQ在分发任务的时候盲目的一次性平均分配任务,它分发给消费者的任务后,不看消费者是否完成,直接在分配就造成了累积;
通过限制RabbitMQ只发不超过1条的消息给同一个消费者,且当消息处理完毕后,有了反馈,才会进行第二次发送;
int prefetchCount = 1; channel.basicQos(prefetchCount);
当然在使用basicQos方法的时候还需要设置autoAck: 设置应答模式,默认是自动应答
- 自动应答:只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费
- 手动应答:消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态,打开手动确认还有一个好处就是当消费者不正常死亡,RabbitMQ会将这个消息进行重新加入队列进行排队
/**
* 生产者,消息发送者
* Created by Kak on 2020/9/21.
*/
public class ProducerMessage {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//编程端口
connectionFactory.setUsername("kaka");//本机可以使用guest
connectionFactory.setPassword("kaka");
connectionFactory.setVirtualHost("/");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建信道
Channel channel = connection.createChannel();
//确保同时只发送1个消息
channel.basicQos(1);
for(int i = 0; i<10;i++){
//创建消息
String msg = "This is rabbitMQ message!!!"+i;
//发送消息
channel.basicPublish("","hello",null,msg.getBytes());
System.out.println("------producer------:"+msg);
}
//释放资源
channel.close();
connection.close();
}
}
将1接收者设置睡眠时间,模拟低效率;
/**
* 消息接受者
* Created by Kak on 2020/9/21.
*/
public class ConsumerMessage_01 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//编程端口
connectionFactory.setUsername("kaka");//本机可以使用guest
connectionFactory.setPassword("kaka");
connectionFactory.setVirtualHost("/");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建信道
Channel channel = connection.createChannel();
System.out.println("准备接收消息!!!");
channel.basicQos(1);
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("consumcrl:-----" + new String(delivery.getBody(), "UTF-8"));
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 手动通知
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
};
/**
* 1:侦听的队列
* 2:是否自动提醒
* 3:接收消息后回调执行的逻辑
* 4:取消或者异常消费时,执行的回调逻辑
*/
channel.basicConsume("hello", false, deliverCallback, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
//使用标准输入中断
System.in.read();
channel.close();
connection.close();
}
}
/**
* 消息接受者
* Created by Kak on 2020/9/21.
*/
public class ConsumerMessage_02 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//编程端口
connectionFactory.setUsername("kaka");//本机可以使用guest
connectionFactory.setPassword("kaka");
connectionFactory.setVirtualHost("/");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建信道
Channel channel = connection.createChannel();
System.out.println("准备接收消息!!!");
channel.basicQos(1);
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("consumcr2:-----" + new String(delivery.getBody(), "UTF-8"));
// 手动通知
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
};
/**
* 1:侦听的队列
* 2:是否自动提醒
* 3:接收消息后回调执行的逻辑
* 4:取消或者异常消费时,执行的回调逻辑
*/
channel.basicConsume("hello", false, deliverCallback, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
//使用标准输入中断
System.in.read();
channel.close();
connection.close();
}
}
上面说的队列在RabbitMQ退出或者崩溃时就会消失的队列,如果当时队列里面还有消息未被消费,那么异常退出会造成任务丢失;为了防止RabbitMQ意外退出造成队列中的任务丢失就用到了消息队列的持久化;
设置持久化队列需要在提供者和消费者中设置:
channel.queueDeclare(QUEUE_NAME,true,false,false,null); //第二个参数设置为true
当我们消息发布者发布一条消息之后,然后我们重启RabbitMQ服务器,查看web管理页面会发现,消息还在;
/**
* 消息接受者
* Created by Kak on 2020/9/21.
*/
public class ConsumerMessage {
static String queueName = "hellox";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//编程端口
connectionFactory.setUsername("kaka");//本机可以使用guest
connectionFactory.setPassword("kaka");
connectionFactory.setVirtualHost("/");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建信道
Channel channel = connection.createChannel();
//配置信道绑定的队列为持久化队列
channel.queueDeclare(queueName, true, false, false, null);
System.out.println("准备接收消息!!!");
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumer--------" + new String(body, "UTF-8"));
}
};
/**
* 1:侦听的队列
* 2:是否自动提醒
* 3:接收消息后回调执行的逻辑
*/
channel.basicConsume(queueName, true, consumer);
//使用标准输入中断
System.in.read();
channel.close();
connection.close();
}
}
/**
* 生产者,消息发送者
* 持久化队列
* Created by Kak on 2020/9/21.
*/
public class ProducerMessage {
static String queueName = "hellox";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//编程端口
connectionFactory.setUsername("kaka");//本机可以使用guest
connectionFactory.setPassword("kaka");
connectionFactory.setVirtualHost("/");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建信道
Channel channel = connection.createChannel();
//配置信道绑定的队列为持久化队列
channel.queueDeclare(queueName, true, false, false, null);
//创建消息
String msg = "This is rabbitMQ message!!!";
//发送消息
channel.basicPublish("", queueName, null, msg.getBytes());
//释放资源
channel.close();
connection.close();
}
}