目录
1.4 关于 RabbitMQ 的持久化、不公平分发以及预取值
什么是 RabbitMQ ?
RabbitMQ 是流行的消息队列服务软件,是开源的AMQP(高级消息队列协议)实现;支持多种客户端,如:Java、Python、C、PHP、Ruby、JavaScript等,用于在分布式系统中存储转发消息,可以实现异步处理、流量削峰、系统解耦,在易用性、扩展性、高可用等方面表现优异
1、初识 RabbitMQ 消息队列
1.1 MQ 四大核心概念
生产者:
产生数据发送消息的程序
交换机:
交换机是 RabbitMQ 非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息推送到队列中;交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定
队列:
队列是 RabbitMQ 内部使用的一种数据结构,尽管消息流经 RabbitMQ 和应用程序,但它们只能存储在队列中;队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区;许多生产者可以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据;这就是我们使用队列的方式
消费者:
消费与接收具有相似的含义;消费者大多时候是一个等待接收消息的程序;生产者,消费者和消息中间件很多时候并不在同一机器上;同一个应用程序既可以是生产者又是可以是消费者
以下是 RabbitMQ 的原理图:
1.2 消息的发送(无交换机态)
这里使用MQ中间件进行简单的消息发送,大致流程图如下所示:
这里需要注意的是,当一次性有多条消息发送到队列时,这时需要多个消费者(工作线程),消费者进行消费信息是根据轮询的方式进行消费
创建一个Utils工具类,与 MQ 进行交互连接:
/**
* 这里是与 MQ 交互的工具类
*/
public class RabbitMQUtils {
public static Channel getChannel() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory(); //创建连接工厂
factory.setHost("192.168.101.65");
factory.setUsername("admin");
factory.setPassword("123");
Connection connection = factory.newConnection(); //创建连接
return connection.createChannel(); //获取连接信道
}
}
【消息生产者】代码如下所示:
/**
* 生产者
*/
public class Produce {
public static final String QUEUE_NAME = "hello"; //队列名称
public static void main(String[] args) throws IOException, TimeoutException {
//这里创建一个工厂,与 RabbitMQ 进行交互
Channel channel01 = RabbitMQUtils.getChannel();
//1.队列名称 2.队列是否持久化 3.消息是否供多个消费者消费 4.消息是否自动删除 5.其他参数
channel01.queueDeclare(QUEUE_NAME,false,false,false,null);
String message = "hello mq"; //发消息
//1.对应的交换机 2.路由的KEY值(本次是队列名) 3.其他参数 4.发送消息的消息体
channel01.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("消息发送完毕!");
}
}
消息栏:
RabbitMQ 中(以上创建的 hello 队列):
【消息消费者】代码如下所示:
/**
* 消费者
*/
public class Consumer {
public static final String QUEUE_NAME = "hello"; //要进行消费消息的队列
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂,与MQ进行交互
Channel channel = RabbitMQUtils.getChannel();
//接收消息的回调
DeliverCallback deliverCallback = (consumerTag,message)->{
System.out.println("成功接收消息:"+new String(message.getBody())); //接收其消息的消息体才能显示对应的消息
};
//取消消息时的回调
CancelCallback cancelCallback = (consumerTag) ->{
System.out.println(consumerTag + "消费者的消息被中断!");
};
/**
* 1.要被消费信息的队列
* 2.消费成功之后是否需要自动应答
* 3.消费成功时的回调
* 4.取消消息发送时的回调
*/
//消费者消费信息
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
}
消息栏:
MQ 中的消息已经被消费:
1.3 关于消息自动重新入队
如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或 TCP 连接丢失),导致消息 未发送 ACK 确认,RabbitMQ 将了解到消息未完全处理,并将对其重新排队;如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者;这样,即使某个消费者偶尔死亡,也可以确 保不会丢失任何消息
1.3.1 消息的常见应答方法(R)
- Channel.basicAck (用于肯定确认) RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了
- Channel.basicNack (用于否定确认)
- Channel.basicReject (用于否定确认) 与 Channel.basicNack 相比少一个参数
不处理该消息了直接拒绝,可以将其丢弃了
丢失的消息重新入队,传递给正常工作的消费者进行消费的大致图:
由于生产者的代码没有改变,这里就不写了,以下是消费者(两个消费者只有 sleep 的时间不一样)关于 ACK 手动应答消息的代码:
/**
* 这里是消费者手动接受消息 ACK,使发送失败的消息重新排队
*/
public class Consumer01 {
public static final String QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtils.getChannel();
SleepUtils.sleep(8); //模拟消息多的情况
//1、接收到消息的回调
DeliverCallback deliverCallback = (consumerTag,message) ->{
System.out.println("接收到消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
/**
* 手动应答
* 1. 消息的标记
* 2. 是否批量应答信道中的消息
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
//2.消息中断的回调
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println(consumerTag + "消费者取消了消息的接收!");
};
//3.使用手动应答
boolean autoACK = false;
channel.basicConsume(QUEUE_NAME,autoACK,deliverCallback,cancelCallback);
}
}
首先创建两个消费者,分别为C1和C2,这里生产者连续发送四条消息:
消费者一 处于正常状态,消费者二 接收了一条消息后就宕机了,这时,消费者一 将发送失败的消息从信道中取出并进行消费,结果图如下所示:
消费者一: 消费者二:
可见, 就算消费者二突然宕机,RabbitMQ 依然采用轮询方式将发送失败的消息轮询给正常工作的消费者
1.4 关于 RabbitMQ 的持久化、不公平分发以及预取值
队列的持久化:
平时消息队列都是保存在内存中,若 RabbitMQ服务 突然停止,则之前的队列都会消失;所以,为了减少损失的可能性,通常将消息队列保存到磁盘上,即持久化
boolean durable =true; //将队列进行持久化
//1.队列名称 2.队列是否持久化 3.消息是否供多个消费者消费 4.消息是否自动删除 5.其他参数
channel01.queueDeclare(QUEUE_NAME,durable,false,false,null);
消息的持久化:
将 MessageProperties.PERSISTENT_TEXT_PLAIN 标识放入 basicPublish消息发送方法的第三个参数中,以开启消息持久化
将消息标记为持久化并不能完全保证不会丢失消息;尽管它告诉 RabbitMQ 将消息保存到磁盘,但是,这里依然存在当消息刚准备存储在磁盘的时候,还没有存储完,消息还在缓存的一个间隔点;此时并没有真正写入磁盘