RabbitMQ系列-- 工作队列模型

介绍

Work queues,也被称为(Task queues)任务模型。当消息处理比较耗时的时候,可以生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work模型:**让多个消费者绑定到一个队列,共同消费队列中的消息。**队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。 如下图所示:
在这里插入图片描述
角色:

  • P: 生产者:任务的发布者。
  • C1:消费者-1,领取任务并且完成任务,假设完成速度较慢
  • C2:消费者-2,领取任务并完成任务,假设完成速度快。

工作模型之平均消费消息

1.先编写一个RabbitMQ工具类,封装一些共用的方法。

public class RabbitMQUtils {
    private  static ConnectionFactory connectionFactory;

    static {
        //创建连接mq的连接工厂对象
        connectionFactory = new ConnectionFactory();
        //设置连接rabbitmq主机
        connectionFactory.setHost("172.16.114.135");
        //设置端口号
        connectionFactory.setPort(5672);
        //设置连接哪个虚拟主机
        connectionFactory.setVirtualHost("/ems");
        //设置访问虚拟主机的用户名和密码
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("123");
    }

    public  static  Connection getConnection(){
        try {
            return  connectionFactory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  null;
    }

    public  static  void closeChannelAndConnection(Channel channel,Connection connection){
        try {
            if(channel!=null){
                channel.close();
            }
            if(connection!=null){
                connection.close();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

2.生产者(Provider.java)

public class Provider {

    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        //获取连接中的通道
        Channel channel = connection.createChannel();

        //通道绑定对应消息队列
        channel.queueDeclare("work",true,false,true,null);

        //发布消息---循环发布
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("","work", null,(i+"hello work queue").getBytes());
        }

        RabbitMQUtils.closeChannelAndConnection(channel,connection);
    }
}

3.消费者1(Customer1.java)

public class Customer1 {
    public static void main(String[] args) throws IOException {

        Connection connection = RabbitMQUtils.getConnection();
        //创建通道
        Channel channel = connection.createChannel();

        //通道绑定队列  //这里定义队列的方式需要和生产者定义队列的方式一致,不然可能会出现问题
        channel.queueDeclare("work",true,false,true,null);

        //消费消息
        //参数1:消费哪个队列的消息  说白了就是队列的名称
        //参数2:开启消息的自动确认机制
        //参数3:消费消息时的回调接口
        channel.basicConsume("work",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));
            }
        });
    }
}

4.消费者2(Customer2.java)

public class Customer2 {
    public static void main(String[] args) throws IOException {

        Connection connection = RabbitMQUtils.getConnection();
        //创建通道
        Channel channel = connection.createChannel();

        //通道绑定队列  //这里定义队列的方式需要和生产者定义队列的方式一致,不然可能会出现问题
        channel.queueDeclare("work",true,false,true,null);

        //消费消息
        //参数1:消费哪个队列的消息  说白了就是队列的名称
        //参数2:开启消息的自动确认机制
        //参数3:消费消息时的回调接口
        channel.basicConsume("work",true,new DefaultConsumer(channel){

            //处理消息的回调
            @Override  //最后一个参数: 消息队列中取出的消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者-2  "+new String(body));
            }
        });
    }
}

我们先运行两个消费者,在运行生产者。两个消防者都会得到消息,分配情况如下:
在这里插入图片描述
在这里插入图片描述
可以看到,默认情况下,RabbitMQ将按顺序将每个消息发送给下一个消费者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。

那现在有这么一种情况,如果消费者1执行过慢,那么消费者2执行完之后就没事情可做了。因为任务是平均分配的,会导致资源浪费问题。接下来我们来模仿一下这种情况,给消费者1消费数据的时候加上延迟执行,修改后代码如下:

public class Customer1 {
    public static void main(String[] args) throws IOException {

        Connection connection = RabbitMQUtils.getConnection();
        //创建通道
        Channel channel = connection.createChannel();

        //通道绑定队列  //这里定义队列的方式需要和生产者定义队列的方式一致,不然可能会出现问题
        channel.queueDeclare("work",true,false,true,null);

        //消费消息
        //参数1:消费哪个队列的消息  说白了就是队列的名称
        //参数2:开启消息的自动确认机制
        //参数3:消费消息时的回调接口
        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(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者-1  "+ new String(body));
            }
        });
    }
}

执行代码可以发现,消费者1还在缓慢执行,消费者2已经执行完毕了。 那么这有什么办法可以解决这个问题? 那就需要用到工作队列中的能者多劳体现。也就是说,消费者2执行完之后去帮助消费者1去执行消息。

工作模型消息确认机制和能者多劳实现

在实现之前,我们需要先来了解一下工作队列中的消息确认机制。

  channel.basicConsume("work",true,new DefaultConsumer(channel){

消费消息方法中的参数2 true:就是开启自动消息确认机制,false:关闭自动消息确认机制

比如说,有这么一种情况,消费者1在消费消息的时候,在执行到某一条消息的时候发生了中断,导致后面的消息没有执行,由于我们上面的代码开启了消息自动确认机制。 一旦RabbitMQ交付了一个消息给消费者,会马上从队列中移除这个消息。在这种情况下,消费者1在执行消息的过程发生了中断,我们会丢失它正在处理的消息。也会丢失已经转发给消费者1且它还未执行的消息。

为了解决消息丢失的情况(当消费者1被杀死时,可以将任务传递给消费者2)。RabbitMQ支持消息应答,也就是说我们可以在消费者消费完一条消息后,手动确认这条消息已经被消费了。如果消费者被杀死而没有发送应答,RabbitMQ会认为该信息没有被完全的处理,然后将会重新转发给别的消费者。通过这种方式,你可以确认信息不会被丢失,即使消者偶尔被杀死。

代码实现如下:
Customer1.java

public class Customer1 {
    public static void main(String[] args) throws IOException {

        Connection connection = RabbitMQUtils.getConnection();
        //创建通道
        final Channel channel = connection.createChannel();

        //通道绑定队列  //这里定义队列的方式需要和生产者定义队列的方式一致,不然可能会出现问题
        channel.queueDeclare("work",true,false,true,null);

        //消费消息
        //参数1:消费哪个队列的消息  说白了就是队列的名称
        //参数2:开启消息的自动确认机制
        //参数3:消费消息时的回调接口
        channel.basicQos(1);
        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("消费者-1  "+ new String(body));
                //参数1: 确认队列中哪个具体消息被消费了 参数2:是否开启多个消息同时确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
    }
}

Customer2.java

public class Customer2 {
    public static void main(String[] args) throws IOException {

        Connection connection = RabbitMQUtils.getConnection();
        //创建通道
        final Channel channel = connection.createChannel();

        //通道绑定队列  //这里定义队列的方式需要和生产者定义队列的方式一致,不然可能会出现问题
        channel.queueDeclare("work",true,false,true,null);

        //消费消息
        //参数1:消费哪个队列的消息  说白了就是队列的名称
        //参数2:开启消息的自动确认机制 false 关闭消息自动确认机制
        //参数3:消费消息时的回调接口
        //每次消费一条消息
        channel.basicQos(1);
        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("消费者-2  "+new String(body));
                //手动确认消费已经消费完毕
                //参数1: 确认队列中哪个具体消息被消费了 参数2:是否开启多个消息同时确认
                //envelope.getDeliveryTag()可以获取到当前消费的消息
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
    }
}

重新执行消费1和消费2,可以发现消费者2完全的体现了能者多劳
在这里插入图片描述

在这里插入图片描述
channel.basicQos(1)的作用:

  • 告诉RabbitMQ不要在同一时间给一个消费者超过一条消息。也就是说,只有消费者在空闲的时候会发送下一条消息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值