Rabbitmq(四)

75 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

4.4 第二种模型(work quene)

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

image-20200314221002008

角色:

  • P:生产者:任务的发布者
  • C1:消费者-1,领取任务并且完成任务,假设完成速度较慢
  • C2:消费者-2:领取任务并完成任务,假设完成速度快
1. 开发生产者
channel.queueDeclare("hello", true, false, false, null);
for (int i = 0; i < 10; i++) {
  channel.basicPublish("", "hello", null, (i+"====>:我是消息").getBytes());
}

生产者类:

public class Provider {

    public static void main(String[] args) throws IOException {
        // 获取连接对象(使用我们自己写的工具类)
        Connection connection = RabbitMQUtils.getConnection();
        // 获取通道对象
        Channel channel = connection.createChannel();

        // 通过通道声明队列   work队列   持久化    不独占     不自动删除  不传额外的参数
        channel.queueDeclare("work", true, false, false, null);

        for (int i = 0; i < 10; i++) {
            // 生产消息           交互机  work队列   消息持久化方式       消息
            channel.basicPublish("", "work", null, (i + "hello work queue").getBytes());
        }
      
        // 关闭资源
        RabbitMQUtils.closeConnectionAndChanel(channel, connection);
    }
}
2.开发消费者-1
channel.queueDeclare("hello",true,false,false,null);
channel.basicConsume("hello",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));
  }
});

消费者1的类:

public class Consumer1 {
    public static void main(String[] args) throws IOException {
        // 获取连接对象(使用我们自己写的工具类)
        Connection connection = RabbitMQUtils.getConnection();
        // 获取通道
        Channel channel = connection.createChannel();
        // 通道绑定队列
        channel.queueDeclare("work", true, false, false, null);
        // 消费消息           队列名称   自动确认    回调接口(写消费的具体逻辑)
        channel.basicConsume("work", true, new DefaultConsumer(channel){
            @Override             // 参数1:标签  参数2:消息传递的信封    参数3:传的一些属性   参数4 body :消息队列中取出的消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者-1:" + new String(body));
            }
        });
    }
}
3.开发消费者-2
channel.queueDeclare("hello",true,false,false,null);
channel.basicConsume("hello",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));  
  }
});

消费者2的类:

public class Consumer2 {
    public static void main(String[] args) throws IOException {
        // 获取连接对象(使用我们自己写的工具类)
        Connection connection = RabbitMQUtils.getConnection();
        // 获取通道
        Channel channel = connection.createChannel();
        // 通道绑定队列
        channel.queueDeclare("work", true, false, false, null);
        // 消费消息           队列名称   自动确认    回调接口(写消费的具体逻辑)
        channel.basicConsume("work", true, new DefaultConsumer(channel){
            @Override              // 参数1:标签  参数2:消息传递的信封    参数3:传的一些属性   参数4 body :消息队列中取出的消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者-2:" + new String(body));
            }
        });
    }
}
4.测试结果

image-20220823220129743

image-20220823220113667

image-20200314223302207

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

平均分配:当其中某一个消费者处理比较慢的时候,会拖垮系统的运行速度

我们更希望处理快的日后能多处理一点,处理慢的能少处理一点,不想让它出现平均处理的这种情况,意思是能者多劳,谁能力强谁就多干,谁能力弱谁就少干一点。那么怎么做呢,往下看吧!

5.消息自动确认机制

Doing a task can take a few seconds. You may wonder what happens if one of the consumers starts a long task and dies with it only partly done. With our current code, once RabbitMQ delivers a message to the consumer it immediately marks it for deletion. In this case, if you kill a worker we will lose the message it was just processing. We'll also lose all the messages that were dispatched to this particular worker but were not yet handled.

But we don't want to lose any tasks. If a worker dies, we'd like the task to be delivered to another worker.

所谓消息确认就是当生产者把消息放到消息队列之后,消息队列本身存储这些消息,紧接着当消费者消费的过程中确认,消息自动确认可能会有一个问题,什么问题呢,平均分配的时候消息队列一次性的把每个消费者对应的所有消息给消费者,由于自动确认,会有一个非常显著的问题,这种情况不关心消费者业务有没有处理完,它只要拿到这些平均分配的消息,它就告诉队列这些消息已经消费完了,此时队列就把分给对应消费者的消息删除了,但实际上消费者在拿到这些消息之后需要经过漫长的业务去执行。假如一个消费者拿到5个消息,消费到第三个消息的时候,自己宕机,还剩2个消息,此时由于自动确认机制导致剩下2个消息丢失,这种情况对于我们的业务的影响实际上是非常大的。所以我们在使用的时候不建议使用自动确认,我们更希望剩下未被处理的消息交给另外的消费者。那我们应该怎么做呢。1.关闭消息自动确认机制,所谓关闭消息确认,我们不能一次性告诉它这些消息都消费了,不会自动确认消息,也就是说当消息即使被消费了,在队列中这个消息标记的同样是未被消费的,2.我们要做的就是我们要告诉消息队列,在执行消息的时候不能一次性把每个消费者对应的这些消息都给消费者,我们要让消费者在消费消息的时候当前的一个通道只能消费一个消息。

只要消息未被确认它就会在队列中保留

channel.basicQos(1);//一次只接受一条未确认的消息
//参数2:关闭自动确认消息
channel.basicConsume("hello",false,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));
    channel.basicAck(envelope.getDeliveryTag(),false);//手动确认消息
  }
});
  • 设置通道一次只能消费一个消息
  • 关闭消息的自动确认,开启手动确认消息

消费者1

public class Consumer1 {
    public static void main(String[] args) throws IOException {
        // 获取连接对象(使用我们自己写的工具类)
        Connection connection = RabbitMQUtils.getConnection();
        // 获取通道
        final Channel channel = connection.createChannel();
        channel.basicQos(1);    // 设置一个通道只能同时处理一个消息
        // 通道绑定队列
        channel.queueDeclare("work", true, false, false, null);
        // 消费消息           队列名称   自动确认    回调接口(写消费的具体逻辑)
        channel.basicConsume("work", false, new DefaultConsumer(channel){
            @Override             // 参数1:标签  参数2:消息传递的信封    参数3:传的一些属性   参数4 body :消息队列中取出的消息
            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));
                // 手动确认     确认队列中的哪个具体消息      false 是否开启多个消息同时确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });
    }
}

消费者2

public class Consumer2 {
    public static void main(String[] args) throws IOException {
        // 获取连接对象(使用我们自己写的工具类)
        Connection connection = RabbitMQUtils.getConnection();
        // 获取通道
        final Channel channel = connection.createChannel();
        channel.basicQos(1);    // 设置一个通道只能同时处理一个消息
        // 通道绑定队列
        channel.queueDeclare("work", true, false, false, null);
        // 消费消息           队列名称   自动确认    回调接口(写消费的具体逻辑)
        channel.basicConsume("work", false, new DefaultConsumer(channel){
            @Override              // 参数1:标签  参数2:消息传递的信封    参数3:传的一些属性   参数4 body :消息队列中取出的消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者-2:" + new String(body));
                // 手动确认     确认队列中的哪个具体消息      false 是否开启多个消息同时确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });
    }
}

image-20220824063828173

image-20220824063805483