引言
简单队列有个缺点,简单队列是一一对应的关系,即点对点,一个生产者对应一个消费者,按照这个逻辑,如果我们有一些比较耗时的任务,也就意味着需要大量的时间才能处理完毕,显然简单队列模式并不能满足我们的工作需求,我们今天再来看看工作队列。
工作队列模式
一个消息生产者,一个消息队列,多个消费者。同样也称为点对点模式。在这种模式中又可以分为两种模式,一种是两个消费中平均消费队列中的消息。也就说无论两个消费者的消费能力如何,都会平均获取消息。另一种方式则是,能者多劳模式,处理消息能力强的消费者会获取更多的消息,这种模式似乎更符合一些。
P:消息的生产者
C:消息的消费者
红色:队列
平均消费
消费者一
public class ReceiveOne {
private static final String QUEUE = "work_queue_message";
public static void main(String[] args) throws Exception {
//获取链接
Connection connection = ConnectionUtil.getConnection();
//创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务
Channel channel = connection.createChannel();
//声明队列,如果Rabbit中没有此队列将自动创建
channel.queueDeclare(QUEUE, false, false, false, null);
//同一时刻服务器只会发一条消息给消费者
//channel.basicQos(1);
// 监听队列
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
String message = new String(body,"utf-8");
System.out.println(" 消费者一消费消息 " + message);
//休眠
Thread.sleep(10);
//手动确认消息
//channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//回调,true 消息自动确认,false 消息手动确认
channel.basicConsume(QUEUE, true, consumer);
//能者多劳模式将自动确认改为手动确认
//channel.basicConsume(QUEUE, false, consumer);
}
}
消费者二
public class ReceiveTwo {
private static final String QUEUE = "work_queue_message";
public static void main(String[] args) throws Exception {
//获取链接
Connection connection = ConnectionUtil.getConnection();
//创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务
Channel channel = connection.createChannel();
//声明队列,如果Rabbit中没有此队列将自动创建
channel.queueDeclare(QUEUE, false, false, false, null);
//同一时刻服务器只会发一条消息给消费者
//channel.basicQos(1);
// 监听队列
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
String message = new String(body, "utf-8");
System.out.println(" 消费者二消费消息 " + message);
//休眠
Thread.sleep(1000);
//手动确认消息
//channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//回调,true 消息自动确认,false 消息手动确认
channel.basicConsume(QUEUE, true, consumer);
//能者多劳模式将自动确认改为手动确认
// channel.basicConsume(QUEUE, false, consumer);
}
}
生产者
public class Producer {
//队列名称
private static final String QUEUE = "work_queue_message";
public static void main(String[] args) throws Exception {
//获取链接
Connection connection = ConnectionUtil.getConnection();
//创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务
Channel channel = connection.createChannel();
//声明队列,如果Rabbit中没有此队列将自动创建
channel.queueDeclare(QUEUE, false, false, false, null);
for (int i = 1; i <= 100; i++) {
String message = " 生产消息_" + i;
//消息发布方法
channel.basicPublish("", QUEUE, null, message.getBytes());
System.out.println("生产者发送消息 :" + message );
}
//关闭资源
channel.close();
connection.close();
}
}
测试结果
1、消费者1和消费者2获取到的消息内容是不同的,同一个消息只能被一个消费者获取。
2、消费者1和消费者2获取到的消息的数量是相同的,一个是消费奇数号消息,一个是偶数。
上面的代码我们可以看到,消费者1的处理能力比消费者2更强,但是当我们看到结果的时候会发现,两个消费者获得消息的数量却是一样的。这就是work模式中的平均消费模式。
能者多劳
由于上方模拟的是非常简单的消息队列的消费,假如有一些非常耗时的任务,某个消费者在缓慢地进行处理,而另一个消费者则空闲,显然是非常消耗资源的。
再举一个例子,一个1年的程序员,跟一个3年的程序员,分配相同的任务量,明显3年的程序员处理起来更加得心应手,很快就无所事事了,但是3年的程序员拿着非常高的薪资!显然3年的程序员应该承担更多的责任,那怎么办呢?
公平分发。
其实发生上述问题的原因是 RabbitMQ 收到消息后就立即分发出去,而没有确认各个工作者未返回确认的消息数量,类似于TCP/UDP中的UDP,面向无连接。
因此我们可以使用 basicQos 方法,并将参数 prefetchCount 设为1,告诉 RabbitMQ 我每次值处理一条消息,你要等我处理完了再分给我下一个。这样 RabbitMQ 就不会轮流分发了,而是寻找空闲的工作者进行分发。
打开上述代码的注释:
同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
开启这行 表示使用手动确认模式
channel.basicAck(envelope.getDeliveryTag(), false);
同时改为手动确认:
channel.basicConsume(QUEUE, false, consumer);
需要注意的 basicAck 方法需要传递两个参数
deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel
multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息。
测试结果
相关博文:
RabbitMQ系列(一)工作队列模式
RabbitMQ系列(二)确认模式
RabbitMQ系列(三)发布订阅模式
RabbitMQ系列(四)路由模式
RabbitMQ系列(五)主题模式