工作队列
生产者(producers)将消息发送到队列(queue),可以有多个消费者(consumers)从队列中获取消息
目的:避免等待占用大量资源、时间的操作,消费者之间任务共享
主要任务:避免立刻执行资源密集型任务,然后必须等待其完成,多个消费者之间可以进行任务调度
消息应答(message acknowledgments)
根据队列执行原理,一旦消费者从队列中获取消息后,则此消息会马上在内存中移除。如果消费者在任务执行过程中发生中断,则正在处理的消息会丢失,也就是说这个消息将会处理失败。
如果想避免消息丢失,需要如何处理?
为了保证消息永远不会丢失,RabbitMQ支持消息应答。
原理:消费者在消息处理结束后,发送应答给RabbitMQ,告知消息已经接收并处理,然后RabbitMQ才会把消息删除。
消息应答机制不会超时,只有在消费者断开连接时重新转发消息,好费时间可以很长。
消息应答默认是打开的。
消息持久化(Message durability)
消费者被杀死,消息不会丢失,但如果RabbitMQ服务器停止,消息仍然会丢失
为了使消息不会丢失,我们需要给所有的队列和消息设置持久化标志
1、确认RabbitMQ不会丢失队列。需要将其声明为持久化的。
// 声明(创建)队列
//在RMQ中,队列声明是幂等性的,(一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同)
//也就是如果不存在,就创建;如果存在,不会对已存在的队列产生影响。
//durable持久性、exclusive唯一性,autoDelete自动清理,arguments参数
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
注意:RabbitMQ不允许使用不同的参数重新定义一个队列,如果已经存在,无法修改其属性
2、标识信息为持久化的。
通过设置MessageProperties值为PERSISTENT_TEXT_PLAIN
//消息设置持久化标志,第三个参数位置
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
将这两处设置好后,即可进行测试。
公平转发(Fair dispatch)
默认情况下,消息转发机制是公平转发的。如:存在两个消费者,同时执行任务,所有的消息会平均分配给两个消费者进行处理。由于消费者的处理能力不同,便可能出现一个消费者一直繁忙,另一个消费者很快执行完任务然后等待的情况。
RabbitMQ接收到消息后进行转发,并没有关注有多少任务消费者并未返回应答,只是盲目的平均分配了消息给消费者。
此时我们可以使用basicQos方法,传递参数prefetchCount = 1
即告诉RabbitMQ,同一时间只给消费者发送一条消息,只有存在空闲消费者时才会发送下一条消息。
// 同一时刻服务器只会发一条消息给消费者 ,即只有在消费者空闲的时候会发送下一条信息。
//如果把这条语句注释掉,由于队列的公平转发机制,两个消费者获取的消息数将会是相同的
channel.basicQos(1);
完整代码
生产者producer:
public class Send {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明(创建)队列
//在RMQ中,队列声明是幂等性的,(一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同)
//也就是如果不存在,就创建;如果存在,不会对已存在的队列产生影响。
//durable持久性、exclusive唯一性,autoDelete自动清理,arguments参数
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
for (int i = 0; i < 50; i++) {
// 消息内容
String message = "" + i;
//消息设置持久化标志,第三个参数位置
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
Thread.sleep(i * 10);
}
//关闭通道和连接
channel.close();
connection.close();
}
}
消费者1(consumer1):
public class Recv {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 同一时刻服务器只会发一条消息给消费者 ,即只有在消费者空闲的时候会发送下一条信息。
//如果把这条语句注释掉,由于队列的公平转发机制,两个消费者获取的消息数将会是相同的
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
//休眠
Thread.sleep(1000);
// 返回确认状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
消费者2(consumer2):
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成状态
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
// 休眠1秒
Thread.sleep(10000);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}