在这一部分中,我们将创建一个工作队列,该队列将用于在多个工作人员之间分配耗时的任务。

  • 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));
                
            }
        });
    }
}

运行结果:

rabbit queues的数据如何查看 rabbitmq work queue_持久化


rabbit queues的数据如何查看 rabbitmq work queue_持久化_02

  • 优点: 使用任务队列的有点就是能够轻松并行化工作
    如果我们正在积压工作,我们可以增加更多的工人,这样就可以轻松扩展。

默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者
平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环

  • 但是,完成一项任务可能需要几秒钟
  • 如果其中一个使用者开始一项漫长的任务并且只有部分完成而死掉,会发生什么情况。
  • 我们都知道RabbitMQ一旦向消费者传递了一条消息,便立即将其标记为删除
  • 但是如果某一个使用着dead,我们将丢失正在处理的消息。我们还将丢失所有发送给该特定消费者但尚未处理的消息
  • 因此如果不想丢失任何任务。如果消费者死亡,我们希望将任务交付给另一个消费者。

为了确保消息不丢失,RabbitMQ支持消息确认,即消费者发送回确认,以告知RabbitMQ已经接收,处理了特定消息,RabbitMQ才可以自由删除它

如果使用者死了(其通道已关闭,连接已关闭或TCP连接丢失)而没有发送确认,RabbitMQ将了解消息未完全处理,并将重新排队。
如果同时有其他消费者在线,它将很快将其重新分发给另一个消费者。这样,您可以确保即使工人偶尔死亡也不会丢失任何消息。

因此我们做如下修改

在消费者1、消费者2部分都进行修改

rabbit queues的数据如何查看 rabbitmq work queue_ide_03

运行结果:

rabbit queues的数据如何查看 rabbitmq work queue_rabbitmq_04


rabbit queues的数据如何查看 rabbitmq work queue_java_05

  • 很显然,当消费者2任务未完成,RabbitMQ得不到确认,则将其它任务分配给其它消费者