这一节我们要讨论的场景是:首先我们开启了多个消费者,一个生产者。此时,我们的消费者如何接收消息。
我们对第 1 节的代码进行修改,这次我们生产者端发送多个消息
// 发送多个消息,每个消息的结尾带上数字标识,消费者端根据这个数字标识决定须要处理多少秒时间。
for (int i = 0; i < 10; i++) {
String messages = "helloworld " + i;
channel.basicPublish("", QUEUE_NAME, null, messages.getBytes());
}
消费者端:
public class Receiver {
private final static String QUEUE_NAME = "liwei";
public static void main(String[] args) throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
// 定义 rabbitmq 服务器的连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.199.121");
Connection connection = factory.newConnection();
// 声明要接收的消息队列(这一步和发送端还是一样的)
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定义消费者进行消费
QueueingConsumer consumer = new QueueingConsumer(channel);
// 如果第 2 个参数为 false ,消费者从一个 queue 中可以多次获取
// 如果为 true ,表示消费者给了中间件一个应答,队列中的消息就不会再被消费了
channel.basicConsume(QUEUE_NAME, true, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("接收端收到:" + message);
// 根据接收到得不同消息的尾数,决定处理多少时间
dowork(message);
}
}
// 这里假装很忙的样子
private static void dowork(String message) throws InterruptedException {
Integer num = Integer.valueOf(message.substring(message.length()-1));
Thread.sleep(num*1000);
}
}
然后我们先运行多个消费者,再运行一个生产者。我们会发现三个消费者的控制台是这样输出的:
接收端收到:helloworld 0
接收端收到:helloworld 3
接收端收到:helloworld 6
接收端收到:helloworld 9
接收端收到:helloworld 1
接收端收到:helloworld 4
接收端收到:helloworld 7
接收端收到:helloworld 2
接收端收到:helloworld 5
接收端收到:helloworld 8
结论:默认来说,RabbitMQ 会按顺序得把消息发送给每个消费者(Consumer)。平均每个消费者都会收到同等数量得消息。这种发送消息得方式叫做——轮询(round-robin)。
我们再做一个实验,同样开启三个消费者,一个发送者。然后再运行中 kill 掉 2 个消费者。可以看到它们接收的情况:
接收到 1 条消息以后就被 kill 的消费者:
接收端收到:helloworld 0
接收到 1 条消息以后就被 kill 的消费者:
接收端收到:helloworld 1
唯一正常留下来的消费者:
接收端收到:helloworld 2
接收端收到:helloworld 5
接收端收到:helloworld 8
我们可以得出结论:自动应答的情况下,消费者好像仍然在“接收消息”,但是并没有在处理消息。这种情况并不是我们想要看到的,因为有些消息没有得到正确的响应和处理。
下面我们介绍如何关闭自动应答,并给出回执。
第 1 步:在消费者声明接收的时候,取消自动应答:
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
第 2 步:在消费者接收消息以后,给一个回执给生产者
// 接收到消息以后,给一个回执 channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
源代码:
/**
* Acknowledge one or several received
* messages. Supply the deliveryTag from the {@link com.rabbitmq.client.AMQP.Basic.GetOk}
* or {@link com.rabbitmq.client.AMQP.Basic.Deliver} method
* containing the received message being acknowledged.
* @see com.rabbitmq.client.AMQP.Basic.Ack
* @param deliveryTag the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
* @param multiple true to acknowledge all messages up to and
* including the supplied delivery tag; false to acknowledge just
* the supplied delivery tag.
* @throws java.io.IOException if an error is encountered
*/
void basicAck(long deliveryTag, boolean multiple) throws IOException;
这时,我们再做同样的测试:
被 kill 掉的消费者,原来 9 这条任务应该由这个消费者处理的,因为被 kill 掉,所以消息中间件没有收到 9 这个任务的回执信息,因此消息中间件又发给了下一个消费者来处理。
接收端收到:helloworld 0
接收端收到:helloworld 3
接收端收到:helloworld 6
接收端收到:helloworld 1
接收端收到:helloworld 5
接收端收到:helloworld 7
接收端收到:helloworld 9
接收端收到:helloworld 2
接收端收到:helloworld 4
接收端收到:helloworld 8