「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」
1、Work消息模型之发布者发布消息
Wlork queues ,也被称为(Task queues ),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多无法及时处理。此时就可以使用work模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
Work消息模型结构图
- P:生产者:要发送消息的程序
- C1:消费者1,领取消息并消费消息,假设完成速度较快
- C2:消费者2,领取消息并消费消息,假设完成速度较慢
- queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。
消息生产者的开发
ConnectUtils 是上一篇写的工具类
public class Provider {
public static void main(String[] args) throws IOException, TimeoutException {
//使用创建连接的工具类创建连接,连接到rabbitmq
Connection connection = ConnectUtils.getConnection("121.199.53.150", 5672, "/ems", "ems", "ems");
//创建通道
Channel channel = connection.createChannel();
//把通道和队列绑定
channel.queueDeclare("work",true,false,false,null);
for (int i = 0; i < 20; i++) {
//往队列中发布消息
channel.basicPublish("","work", MessageProperties.PERSISTENT_TEXT_PLAIN,("work rabbitmq"+ i).getBytes());
}
channel.close();
connection.close();
}
}
复制代码
运行后,查看rabbitmq的管理控制页面:
Queues里面多了一个名为work队列,里面有创建的20条消息,work消息模型发布消息成功。
2、Work消息模型之消费者消费消息
消息消费者的开发
work消息模型消费者A (消费消息速度快):
package com.cheng.work;
import com.cheng.utils.ConnectUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Provider {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectUtils.getConnection("121.199.53.150", 5672, "/ems", "ems", "ems");
Channel channel = connection.createChannel();
channel.queueDeclare("work",true,false,false,null);
for (int i = 0; i < 20; i++) {
channel.basicPublish("","work", MessageProperties.PERSISTENT_TEXT_PLAIN,("work rabbitmq"+ i).getBytes());
}
}
}
复制代码
work消息模型消费者B (消费消息速度慢):
package com.cheng.work;
import com.cheng.utils.ConnectUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
public class ConsumerB {
public static void main(String[] args) throws IOException {
Connection connection = ConnectUtils.getConnection("121.199.53.150", 5672, "/ems", "ems", "ems");
//创建通道
Channel channel = connection.createChannel();
//把通道和队列绑定
channel.queueDeclare("work", true, false, false, null);
channel.basicConsume("work", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("consumerB:" + new String(body));
}
});
}
}
复制代码
先执行两个消息消费者,监听队列中的消息,再执行消息生产者发送消息,查看控制台的输出信息:
消费者A:快速消费完10条消息
消费者B:每隔一秒消费一条消息,执行速度慢
从上面两个图可以看出,两个消费者消费的信息数量相同。
默认情况下,RabbitMQ 会按顺序将每条消息发送给下一个消费者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。
但是这种方式存在问题,执行速度快的消费者和执行速度慢的消费者拿到了相同数量的消息,执行速度快的消费者消费完消息后,执行速度慢的消费者还在执行,就会拖慢整个服务。我们更希望“能者多劳”,执行速度快的就多处理一点,执行速度慢的就少处理一点。
3、消息自动确认机制
basicConsume() 方法的第二个参数为 boolean autoAck,这个参数表示是否开启消息自动确认机制。
如果 autoAck=true,表示开启消息自动确认机制,那么只要消费者拿到队列中的消息,不管执行与否,都会给 RabbitMQ 发回一个确认,表示消息已经接收,那么RabbitMQ就把消息标记为删除;但如果其中一个消费者开始一项长期任务并且只完成了部分任务而死去会发生什么?我们将丢失它刚刚处理的消息。我们还将丢失所有发送给该消费者但尚未处理的消息。
如果 autoAck=false,表示关闭消息自动确认机制,如果消费者在没有给 RabbitMQ 发送确认的情况下死亡(或通道关闭、连接关闭或 TCP 连接丢失),RabbitMQ 将理解消息未完全处理并将重新排队。如果同时有其他消费者在线,它会迅速将其重新发送给另一个消费者。这样,即使工人偶尔死亡,您也可以确保不会丢失任何消息。
在消费者A和消费者B中关闭消息自动确认,开启手动消息确认,并设置通道一次只能发送一个消息,这样的话,消费者每拿到一个会处理完再给 RabbitMQ 发回一个确认,然后队列再给消费者发送下一个消息,这样也实现了 “能者多劳”:
public class ConsumerA {
public static void main(String[] args) throws IOException {
Connection connection = ConnectUtils.getConnection("121.199.53.150", 5672, "/ems", "ems", "ems");
//创建通道
final Channel channel = connection.createChannel();
channel.basicQos(1);
//把通道和队列绑定
channel.queueDeclare("work",true,false,false,null);
channel.basicConsume("work", false,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerA:"+new String(body));
//参数一:手动确认消息标识,参数二:false 每次确认一个
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
复制代码
public class ConsumerB {
public static void main(String[] args) throws IOException {
Connection connection = ConnectUtils.getConnection("121.199.53.150", 5672, "/ems", "ems", "ems");
//创建通道
final Channel channel = connection.createChannel();
channel.basicQos(1);
//把通道和队列绑定
channel.queueDeclare("work", true, false, false, null);
channel.basicConsume("work", false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("consumerB:" + new String(body));
//参数一:手动确认消息标识,参数二:false 是否开启多个消息同时确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
复制代码
先执行两个消息消费者,监听队列中的消息,再执行消息生产者发送消息,查看控制台的输出信息:
消费者A:因为处理速度快,处理的消息多
消费者B:因为处理速度慢,就处理了一条信息