在这一部分中,我们将创建一个工作队列,该队列将用于在多个工作人员之间分配耗时的任务。
Work queues
,也被称为(Task queues
),任务模型。- 当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。
- 导致消息就会堆积越来越多,无法及时处理。
- 此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。
- 队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
工作队列:
的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。运行在同于队列中的消费者,任务将在他们之间共享
场景模拟:
- P:生产者:任务的发布者
- C1:消费者-1,领取任务并且完成任务,假设完成速度较慢
- C2:消费者-2:领取任务并完成任务,假设完成速度快
代码实现
为了连接RabbitMQ方便,我们编写一个RabbitMQ工具类
RabbitMQUtils
package com.op.util;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RabbitMQUtils {
private static ConnectionFactory factory;
//类加载执行,执行一次
static {
factory = new ConnectionFactory(); //创建连接mq的连接工厂对象
factory.setHost("192.168.17.129"); //设置连接rabbitmq主机
factory.setPort(5672); //设置端口号
factory.setVirtualHost("/"); //设置连接哪个虚拟主机
factory.setUsername("guest"); //设置访问虚拟主机的用户名和密码
factory.setPassword("guest");
}
//定义连接对象的方法
public static Connection getConnection(){
try {
return factory.newConnection();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//关闭通道或者关闭连接工具
public static void closeConnectionAndChanel(Channel channel,Connection conn){
try {
if(channel!=null) channel.close();
if(conn!=null) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Provider类
package com.op.work_quene;
import com.op.util.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
public class Provider {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
/**
* 通道绑定对应的消息队列
* 参数1: 队列名称 如果队列不存在自动创建,如果已经存在,其属性要与已经存在的属性一致,否则会有异常
* 参数2: 用来定义队列特性是否要持久化 true 持久化队列 false 不持久化
* 参数3: exclusive 是否独占队列 true 独占队列 false 不独占
* 参数4: autoDelete: 是否在消费完成后自动删除队列 true 自动删除 false 不自动删除
* 参数5: 额外附加参数
*/
channel.queueDeclare("quene_msg",true,false,false,null);
/**
* 发布消息
* 参数1: 交换机名称
* 参数2:队列名称
* 参数3:传递消息额外设置
* 参数4:消息的具体内容
*/
for (int i = 0; i < 10; i++) {
channel.basicPublish("","quene_msg",null,("我是消息"+i).getBytes());
}
//关闭资源
RabbitMQUtils.closeConnectionAndChanel(channel, connection);
}
}
Comsumer1类
package com.op.work_quene;
import com.op.util.RabbitMQUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer1 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
/**
* 通道绑定对象
* '参数1':用来声明通道对应的队列
* '参数2':用来指定是否持久化队列
* '参数3':用来指定是否独占队列
* '参数4':用来指定是否自动删除队列
* '参数5':对队列的额外配置
*/
channel.queueDeclare("quene_msg",true,false,false,null);
/**
* 消费/接收消息
* 参数1: 消费那个队列的消息 队列名称
* 参数2: 开始消息的自动确认机制 true:是 ,false:否
* 参数3: 消费时的回调接口
*/
channel.basicConsume("quene_msg",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1: "+new String(body));
}
});
}
}
Consumer2
package com.op.work_quene;
import com.op.util.RabbitMQUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer2 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
/**
* 通道绑定对象
* '参数1':用来声明通道对应的队列
* '参数2':用来指定是否持久化队列
* '参数3':用来指定是否独占队列
* '参数4':用来指定是否自动删除队列
* '参数5':对队列的额外配置
*/
channel.queueDeclare("quene_msg",true,false,false,null);
/**
* 消费/接收消息
* 参数1: 消费那个队列的消息 队列名称
* 参数2: 开始消息的自动确认机制
* 参数3: 消费时的回调接口
*/
channel.basicConsume("quene_msg",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("消费者2: "+new String(body));
}
});
}
}
运行结果:
优点:
使用任务队列的有点就是能够轻松并行化工作
如果我们正在积压工作,我们可以增加更多的工人,这样就可以轻松扩展。
默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者
平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环
。
- 但是,完成一项任务可能需要几秒钟
- 如果其中一个使用者开始一项漫长的任务并且只有部分完成而死掉,会发生什么情况。
- 我们都知道RabbitMQ一旦向消费者
传递了一条消息
,便立即将其标记为删除
- 但是如果某一个使用着
dead
,我们将丢失正在处理的消息。我们还将丢失所有发送给该特定消费者但尚未处理的消息
- 因此如果不想丢失任何任务。如果消费者死亡,我们希望将任务交付给另一个消费者。
为了确保消息不丢失,RabbitMQ支持消息确认,即消费者发送回确认,以告知RabbitMQ已经接收,处理了特定消息,RabbitMQ才可以自由删除它
。
如果使用者死了(其通道已关闭,连接已关闭或TCP连接丢失)而没有发送确认,RabbitMQ将了解消息未完全处理,并将重新排队。
如果同时有其他消费者在线,它将很快将其重新分发给另一个消费者。这样,您可以确保即使工人偶尔死亡也不会丢失任何消息。
因此我们做如下修改
在消费者1、消费者2部分都进行修改
运行结果:
- 很显然,当消费者2任务未完成,RabbitMQ得不到确认,则将其它任务分配给其它消费者