在上一章节《RabbitMQ 简单的 Hello World 程序》,我们演示了简单的队列模式。
想象一下,如果有多个消费者消费消息会怎么分配消息呢?
我们将接收消费的代码重命名为Work并复制一份。
同时,我们将发送消息的代码循环20次:
for (int i = 0; i < 20; i++) {
channel.basicPublish("", Constant.QUEUE_NAME, null, ("work" + i).getBytes());
System.out.println("message [" + "work" + i + "] be sent");
}
于是,我们看下运行结果:
生产了20
条消息,编号分别是从0
到19
。
Work01
收到消息,是所有的偶数
编号消息。
Work02
收到消息,是所有的奇数
编号消息。
autoACK=true
表示 消费者 接收到消息 就立刻
回复RabbitMQ服务器 :“我收到了
”。
容易混淆的点:消费者接收到消息 和 消费者处理消息业务逻辑 是 两码事。消费者可以先接收消息,把消息缓存起来,等待业务逻辑慢慢处理。
在这种模式下,RabbitMQ服务器 是轮询 分发消息的。
深入一点点:
当 消息 被生产后进入 RabbitMQ 服务器 消息队列中,每个消息是有一个编号[就像似身份证一样,唯一的不重复的] 。
每一个信道连接上 RabbitMQ 服务器 ,都会被记录下来 ,所以 RabbitMQ 服务器 知道有多少个消费者,总数用一个变量 n 表示。
RabbitMQ 服务器 将每条消息开始 推送 给消费者,如果达到一个轮询的效果,会使用一个算法,比如 使用消息的编号 如 247 248 249 ··· 等等,使用 一个变量 m 表示。
m % n ,使用 m 对 n 取余 确定该消息被发送的目标主机。这样,就可以实现一个轮询的机制。
假如 有 work01、work02 两个消费者,消息在不断的产生,假如此时work03消费者也开始消费了。那么, RabbitMQ 服务器 会将消费者增加一个,即 n = n + 1。
autoACK=false
表示 消费者 接收到消息 先放入缓存 再通过回调处理业务逻辑 需要等待业务逻辑处理完 再 回复RabbitMQ服务器 :“我收到了
”。
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("49.232.202.201");
factory.setUsername("gosuncn");
factory.setVirtualHost("/gosuncn");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Constant.QUEUE_NAME, false, false, false, null);
channel.basicConsume(Constant.QUEUE_NAME, false, (consumerTag, message) -> {
System.out.println("DeliverCallback's consumerTag is {" + consumerTag + "}");
System.out.println("消费消息的内容 = " + new String(message.getBody()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, consumerTag -> System.out.println("CancelCallback's consumerTag is {" + consumerTag + "}"));
}
channel.basicConsume()
参数autoACK
设为false
,表示需要手动确认消息。
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
表示 仅 确认 DeliveryTag
这一条消息。若参数二为true,则表示 确认 DeliveryTag
以及之前的所有消息。
目前,比如有10个消费者,那么这10个消费者收到的消息是平均的,仍然是一旦有消息被生产进入RabbitMQ服务器,服务器就用推(Push)的方式,为消费者推送消息,依然是轮询机制来分发消息,因此这10个消费者收到的消息还是平均的。
channel.basicQos(1);
在手动ACK
的情况下,加上Qos
限制,就可以使得性能优秀的服务器可以更加充分地发挥性能优势。
channel.basicQos(1);
的意思是,RabbitMQ服务器发送的消息,未确认的最大数值是void basicQos(int prefetchCount)
中参数prefetchCount
。表示消费者预先拉拽prefetchCount
条消息,如果没有确认就不会再拉取消息。所以,服务器不会按照轮询机制一窝蜂的把数据先发给消费者。
上图中的运行结果,感觉还是轮询分配的啊,我们将消费者的业务逻辑分别加上不同的睡眠,就看的更加清楚了。
很明显了吧?已经不再是轮询机制
分发消息了。这时候,是真的谁的能力强谁做的就多了。能者多劳,体现的淋漓尽致。
总结一下,要想实现能者多劳公平分配,必须 ①、手动确认消息 ②、必须设置basicQos。
如果不使用①、手动确认消息
会出现什么呢?会出现消费还没消费完,就回复服务器确认收到的消息了,服务器就会再给该消费者分配消息。
将channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
删除并设置autoACK
为true
。会发现运行结果业务处理快的Work01
把自己的任务做完了就开始休息了,而Work02
和Work03
还在继续工作,Work02
比Work03
要快一点,所以Work02
完成任务也休息了,Work03
还要继续做,直到完成任务。
为啥呢?原因很简单,消息确认 和 业务逻辑处理 分开了,并不是业务逻辑处理成功再确认消息了。所以,可以理解为消费者的一个线程负责接收消息并确认消息,把收到的消息囤起来给其他的线程去做业务逻辑处理。