2-RabbitMQ的工作队列

本文详细介绍了RabbitMQ的工作队列,包括消息应答机制确保消息不丢失,消息公平派遣实现任务均匀分配,以及消息持久性保证服务重启后队列和消息依然存在。通过示例代码展示了如何设置和使用这些特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RabbitMQ的工作队列

在上一个教程中,我们编写了程序来发送和接收来自命名队列的消息。在这一个中,我们将创建一个工作队列,用于在多个工作人员之间分配耗时的任务。


本次主要介绍的是队列的消息应答、消息公平派遣、消息持久性。

1      消息应答

在我们从队列中取出消息执行任务的时候,可能需要几秒,我们可能会有疑问,如果在我们还在处理这个消息的时候,我们是如何处理的?或者是当消费者取得队列中的消息,队列就把这个消息给删除了,但是如果消费者在取完消息执行任务的时候失败了,那么这个消息也就丢失了;但是实际我们是希望如果如果执行任务的消费者失败了,另外一个消费者还可以继续获得这个消息去执行任务。为了确保消息不被丢失,RabbitMQ支持消息应答。当消费者获得消息任务成功执行后,告诉RabbitMQ,这个消息没用了,可以删除了;RabbitMQ收到消费者应答后,再删除队列中的消息。

如果消费者死机(其通道关闭,连接关闭或TCP连接丢失),而不发送确认信息(不应答),RabbitMQ将会明白消息未被完全处理并重新排队。如果同时有其他消费者在线,则会迅速将其重新提供给另一个消费者。这样就可以确保没有消息丢失,即使工作人员偶尔也会死亡。

消息应答主要是消费者服务中设置,在消费者指定消费队列的时候,打开手动应答机制

      // 消费者指定消费队列,打开应答机制,注意false才是打开手动应对,true为自动应答

      boolean ack =false;

      channel.basicConsume(QUEUE_NAME, ack, consumer);

1.1  消费者完整代码如下

package MQ.WorkQueues.Response;

 

import com.rabbitmq.client.Channel;

import com.rabbitmq.client.Connection;

import com.rabbitmq.client.ConnectionFactory;

import com.rabbitmq.client.QueueingConsumer;

@SuppressWarnings("deprecation")

/**

 * @Title: MQ.WorkQueues.Worker.java

 * @Package MQ.WorkQueues

 * @Description:TODO(模拟MQ接收队列消息后手动应答,持久性)

 * @Copyright: Copyright (c) 2017 YUANH All Rights Reserved

 * @authoryuanh

 * @date 2017-5-10下午3:48:59

 */

public classWorkerResponse {

   private final static String QUEUE_NAME ="hello";

 

   public static void main(String[] argv)throws Exception {

 

      // 创建连接连接到MabbitMQ

      ConnectionFactoryfactory = newConnectionFactory();

      // 设置MabbitMQ所在主机ip或者主机名

      factory.setHost("127.0.0.1");

      factory.setUsername("yuanh");

      factory.setPassword("yuanh");

      factory.setPort(5672);

      factory.setVirtualHost("y_yuanh");

      Connectionconnection = factory.newConnection();

      Channelchannel = connection.createChannel();

 

      channel.queueDeclare(QUEUE_NAME,false, false,false, null);

      System.out.println(" [*]接收消息等待中,推出请按 CTRL+C");

      QueueingConsumerconsumer = newQueueingConsumer(channel);

      // 消费者指定消费队列,打开应答机制,注意false才是打开手动应对,true为自动应答

      boolean ack =false;

      channel.basicConsume(QUEUE_NAME, ack, consumer);

      try {

         while (true) {

            QueueingConsumer.Deliverydelivery = consumer.nextDelivery();

            Stringmessage = newString(delivery.getBody());

            System.out.println("接收 '" + message +"'");

            try {

                doWork(message);

            }finally{

                System.out.println("结束");

                // 另外需要在每次处理完成一个消息后,手动发送一次应答(ack=false)

                channel.basicAck(delivery.getEnvelope().getDeliveryTag(),

                      false);

            }

         }

      }catch(Exception e) {

         channel.close();

         connection.close();

      }

   }

 

   private static void doWork(String task)throws InterruptedException {

      for (char ch :task.toCharArray()) {

         if (ch =='.')

            Thread.sleep(1000);

      }

   }

}

1.2  生产者完整代码如下

 

package MQ.WorkQueues.Response;

 

import java.io.IOException;

import java.util.concurrent.TimeoutException;

 

import com.rabbitmq.client.Channel;

import com.rabbitmq.client.Connection;

import com.rabbitmq.client.ConnectionFactory;

/**

 * @Title: MQ.WorkQueues.NewTask.java

 * @Package MQ.WorkQueues

 * @Description:TODO(MQ往队列中放消息)

 * @Copyright: Copyright (c) 2017 YUANH All Rights Reserved

 * @authoryuanh

 * @date 2017-5-10下午3:50:35

 */

public classNewTask {

   private final static String QUEUE_NAME ="hello";

 

   public static void main(String[] args)throws IOException,TimeoutException {

      // 创建连接连接到MabbitMQ

      ConnectionFactoryfactory = newConnectionFactory();

      // 设置MabbitMQ所在主机ip或者主机名

      factory.setHost("127.0.0.1");

      factory.setUsername("yuanh");

      factory.setPassword("yuanh");

      factory.setPort(5672);

      factory.setVirtualHost("y_yuanh");

      Connectionconnection = factory.newConnection();

      // 创建一个频道

      Channelchannel = connection.createChannel();

      // 创建一个队列

      channel.queueDeclare(QUEUE_NAME,false, false,false, null);

      Stringmessage = getMessage(args);

      // 将消息放到队列里面

      channel.basicPublish("",QUEUE_NAME, null, message.getBytes());

      System.out.println("发送 '" + message +"'");

      // 关闭通道和连接

      channel.close();

      connection.close();

   }

  

   private static StringgetMessage(String[] strings){

       if (strings.length < 1)

           return"Hello World..!";

       returnjoinStrings(strings, " ");

   }

 

   private static StringjoinStrings(String[] strings, String delimiter) {

       int length = strings.length;

       if (length == 0)return "";

       StringBuilder words = newStringBuilder(strings[0]);

       for (int i = 1; i < length; i++) {

          words.append(delimiter).append(strings[i]);

       }

       return words.toString();

   }

 

}

 

 

2      消息公平派遣

       例如在两个工人的情况下,当所有信息都很重,甚至信息很轻的时候,一个工作人员将不断忙碌,另一个工作人员几乎不会做任何工作。然而,RabbitMQ不知道什么,只知道随机分配。它只是盲目地向第n个消费者发送每个第n个消息。这是因为当消息进入队列时,RabbitMQ只会分派消息。它不看消费者的未处理完的消息数量。


       为了打破这个机制,我们可以使用basicQos方法与 prefetchCount = 1设置。这告诉RabbitMQ不要一次给一个工作者多个消息。或者换句话说,在处理并确认前一个消息之前,不要向工作人员发送新消息。相反,它将发送到下一个还不忙的消费者。

       在消费者中添加如下代码

      // 消费者指定消费队列,打开应答机制,注意false才是打开手动应对,true为自动应答

      boolean ack =false;

      channel.basicConsume(QUEUE_NAME, ack, consumer);

      // 消费者设置最大服务转发消息数量,公平转发

      int prefetchCount = 1;

      channel.basicQos(prefetchCount);

2.1  消费者完整代码如下(可设置多个消费者模拟)

 

package MQ.WorkQueues.FairForward;

 

import com.rabbitmq.client.Channel;

import com.rabbitmq.client.Connection;

import com.rabbitmq.client.ConnectionFactory;

import com.rabbitmq.client.QueueingConsumer;

 

@SuppressWarnings("deprecation")

/**

 * @Title: MQ.WorkQueues.Worker.java

 * @Package MQ.WorkQueues

 * @Description:TODO(模拟MQ公平转发队列消息)

 * @Copyright: Copyright (c) 2017 YUANH All Rights Reserved

 * @authoryuanh

 * @date 2017-5-10下午3:48:59

 */

public classWorkerResponse {

   private final static String QUEUE_NAME ="hello";

 

   public static void main(String[] argv)throws Exception {

 

      // 创建连接连接到MabbitMQ

      ConnectionFactoryfactory = newConnectionFactory();

      // 设置MabbitMQ所在主机ip或者主机名

      factory.setHost("127.0.0.1");

      factory.setUsername("yuanh");

      factory.setPassword("yuanh");

      factory.setPort(5672);

      factory.setVirtualHost("y_yuanh");

      Connectionconnection = factory.newConnection();

      Channelchannel = connection.createChannel();

 

      channel.queueDeclare(QUEUE_NAME,false, false,false, null);

      System.out.println(" [*]接收消息等待中,推出请按 CTRL+C");

      QueueingConsumerconsumer = newQueueingConsumer(channel);

      // 消费者指定消费队列,打开应答机制,注意false才是打开手动应对,true为自动应答

      boolean ack =false;

      channel.basicConsume(QUEUE_NAME, ack, consumer);

      // 消费者设置最大服务转发消息数量,公平转发

      int prefetchCount = 1;

      channel.basicQos(prefetchCount);

      try {

         while (true) {

            QueueingConsumer.Deliverydelivery = consumer.nextDelivery();

            Stringmessage = newString(delivery.getBody());

            System.out.println("接收 '" + message +"'");

            try {

                doWork(message);

            }finally{

                System.out.println("结束");

                // 另外需要在每次处理完成一个消息后,手动发送一次应答(ack=false)

                channel.basicAck(delivery.getEnvelope().getDeliveryTag(),

                      false);

            }

         }

      }catch(Exception e) {

         channel.close();

         connection.close();

      }

   }

 

   private static void doWork(String task)throws InterruptedException {

      for (char ch :task.toCharArray()) {

         if (ch =='.')

            Thread.sleep(1000);

      }

   }

}

 

2.2  生产者完整代码如消息应答

 

3      消息持久性

       我们已经学会了如何确保即使消费者死亡,任务也不会丢失。但是如果RabbitMQ服务器停止,我们的任务仍然会丢失。当RabbitMQ退出或崩溃时,它会忘记队列和消息,除非你不告诉它。需要两件事来确保消息不会丢失:我们需要将队列和消息标记为耐用。

       首先,我们需要确保RabbitMQ不会失去我们的队列。为了这样做,我们需要将其声明为持久的。

生产者、消费者都应在在声明队列的时候设置持久化,添加代码如下

// 声明队列、设置队列持久化

      boolean durable =true;

      channel.queueDeclare(QUEUE_NAME, durable,false, false,null);

3.1  消费者完整代码如下

 

package MQ.WorkQueues.Persistent;

 

import com.rabbitmq.client.Channel;

import com.rabbitmq.client.Connection;

import com.rabbitmq.client.ConnectionFactory;

import com.rabbitmq.client.QueueingConsumer;

 

@SuppressWarnings("deprecation")

/**

 * @Title: MQ.WorkQueues.Worker.java

 * @Package MQ.WorkQueues

 * @Description:TODO(模拟MQ在停止服务后队列信息还是存在,持久性)

 * @Copyright: Copyright (c) 2017 YUANH All Rights Reserved

 * @authoryuanh

 * @date 2017-5-10下午3:48:59

 */

public classWorkerResponse {

   private final static String QUEUE_NAME ="hello";

 

   public static void main(String[] argv)throws Exception {

 

      // 创建连接连接到MabbitMQ

      ConnectionFactoryfactory = newConnectionFactory();

      // 设置MabbitMQ所在主机ip或者主机名

      factory.setHost("127.0.0.1");

      factory.setUsername("yuanh");

      factory.setPassword("yuanh");

      factory.setPort(5672);

      factory.setVirtualHost("y_yuanh");

      Connectionconnection = factory.newConnection();

      Channelchannel = connection.createChannel();

      // 1声明队列、设置队列持久化

      boolean durable =true;

      channel.queueDeclare(QUEUE_NAME, durable,false, false,null);

      System.out.println(" [*]接收消息等待中,推出请按 CTRL+C");

      QueueingConsumerconsumer = newQueueingConsumer(channel);

      // 2消费者指定消费队列,打开应答机制,注意false才是打开手动应对,true为自动应答

      boolean ack =false;

      channel.basicConsume(QUEUE_NAME, ack, consumer);

      // 3消费者设置最大服务转发消息数量,公平转发

      int prefetchCount = 1;

      channel.basicQos(prefetchCount);

      try {

         while (true) {

            QueueingConsumer.Deliverydelivery = consumer.nextDelivery();

            Stringmessage = newString(delivery.getBody());

            System.out.println("接收 '" + message +"'");

            try {

                doWork(message);

            }finally{

                System.out.println("结束");

                // 另外需要在每次处理完成一个消息后,手动发送一次应答(ack=false)

                channel.basicAck(delivery.getEnvelope().getDeliveryTag(),

                      false);

            }

         }

      }catch(Exception e) {

         channel.close();

         connection.close();

      }

   }

 

   private static void doWork(String task)throws InterruptedException {

      for (char ch :task.toCharArray()) {

         if (ch =='.')

            Thread.sleep(1000);

      }

   }

}

 

3.2  生产者完整代码如下

 

package MQ.WorkQueues.Persistent;

 

import java.io.IOException;

import java.util.concurrent.TimeoutException;

 

import com.rabbitmq.client.Channel;

import com.rabbitmq.client.Connection;

import com.rabbitmq.client.ConnectionFactory;

/**

 * @Title: MQ.WorkQueues.NewTask.java

 * @Package MQ.WorkQueues

 * @Description:TODO(模拟MQ在停止服务后队列信息还是存在)

 * @Copyright: Copyright (c) 2017 YUANH All Rights Reserved

 * @authoryuanh

 * @date 2017-5-10下午3:50:35

 */

public classNewTask {

   private final static String QUEUE_NAME ="hello";

 

   public static void main(String[] args)throws IOException,TimeoutException {

      // 创建连接连接到MabbitMQ

      ConnectionFactoryfactory = newConnectionFactory();

      // 设置MabbitMQ所在主机ip或者主机名

      factory.setHost("127.0.0.1");

      factory.setUsername("yuanh");

      factory.setPassword("yuanh");

      factory.setPort(5672);

      factory.setVirtualHost("y_yuanh");

      Connectionconnection = factory.newConnection();

      // 创建一个频道

      Channelchannel = connection.createChannel();

        // 声明队列、设置队列持久化

      boolean durable =true;

      channel.queueDeclare(QUEUE_NAME, durable,false, false,null);

      Stringmessage = getMessage(args);

      // 将消息放到队列里面

      channel.basicPublish("",QUEUE_NAME, null, message.getBytes());

      System.out.println("发送 '" + message +"'");

      // 关闭通道和连接

      channel.close();

      connection.close();

   }

  

   private static StringgetMessage(String[] strings){

       if (strings.length < 1)

           return"Hello World..!";

       returnjoinStrings(strings, " ");

   }

 

   private static StringjoinStrings(String[] strings, String delimiter) {

       int length = strings.length;

       if (length == 0)return "";

       StringBuilder words = newStringBuilder(strings[0]);

       for (int i = 1; i < length; i++) {

          words.append(delimiter).append(strings[i]);

       }

       return words.toString();

   }

 

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值