我需要为我的新项目选择一个新的Queue代理。
这次我需要一个支持pub / sub的可伸缩队列,并且必须保持消息排序。
我读了亚历克西斯的评论:他写道:
“的确,我们认为RabbitMQ比Kafka提供更强的排序”
我在rabbitmq docs中阅读了消息订购部分:
“可以使用具有该功能的AMQP方法将消息返回到队列 重新排队 参数(basic.recover,basic.reject和basic.nack),或者由于某个频道 在收到未确认的消息时关闭...使用2.7.0及更高版本 个人消费者仍然可以观察到消息 如果队列有多个订户,则排序。这是由于的行为 可能会重新排队邮件的其他订阅者。从队列的角度来看 消息始终保留在发布顺序中。“
如果我需要按订单处理邮件,我只能使用带有独占队列的rabbitMQ给每个消费者吗?
RabbitMQ仍然被认为是有序消息排队的好方法吗?
答案 0 :(得分:111)
好吧,让我们仔细看看你上面描述的情景。我认为在问题的代码段之前粘贴the documentation以提供上下文非常重要:
AMQP 0-9-1核心规范的第4.7节解释了 保证订购的条件:发布的消息 一个频道,通过一个交换,一个队列和一个 将以与它们相同的顺序接收传出频道 发送。自2.7.0发布以来,RabbitMQ提供了更强大的保证。
可以使用具有该功能的AMQP方法将消息返回到队列 一个重新排队参数(basic.recover,basic.reject和basic.nack),或 由于频道关闭而持有未确认的消息。任何 这些场景导致消息在后面重新排队 RabbitMQ的队列早于2.7.0发布。来自RabbitMQ发布 2.7.0,即使存在重新排队或频道关闭,消息也始终按发布顺序保存在队列中,。(强调添加)
因此,很明显,从2.7.0开始,RabbitMQ在消息排序方面对原始AMQP规范进行了相当大的改进。
对于多个(并行)消费者,无法保证处理顺序。
第三段(粘贴在问题中)继续给出免责声明,我将解释:“如果队列中有多个处理器,则不再保证消息将按顺序处理。”他们在这里所说的只是RabbitMQ不能违反数学定律。
考虑银行的一系列客户。这家银行以帮助客户进入银行的顺序而自豪。客户排队等候,并由3个可用柜员中的下一个提供服务。
今天早上,所有三名出纳员同时出现,接下来的三位客户接近了。突然之间,三名计票员中的第一名患者病情严重,无法完成服务第一位客户。到这时,柜员2已经完成了客户2,柜员3已经开始为客户3服务。
现在,有两件事情可能发生。 (1)第一个在线的客户可以回到生产线的主管或(2)第一个客户可以抢先第三个客户,导致该柜员停止在第三个客户上工作并开始第一个工作。 RabbitMQ不支持这种类型的抢占逻辑,也不支持我所知道的任何其他消息代理。在任何一种情况下,第一个客户实际上并没有最终获得帮助 - 第二个客户确实如此,幸运地获得了一个好的,快速的出纳员。保证客户安全的唯一方法是帮助客户一个人一个人帮助客户,这将给银行带来重大的客户服务问题。
我希望这有助于说明您所询问的问题。鉴于您有多个消费者,不可能确保在每种可能的情况下按顺序处理消息。如果你有多个队列,多个独家消费者,不同的经纪人等,这没关系 - 没有办法保证先验按顺序回答多个消费者的消息。但RabbitMQ将尽最大努力。
答案 1 :(得分:7)
消息排序在Kafka中保留,但仅限于分区而不是全局。如果您的数据需要全局排序和分区,这确实会让事情变得困难。但是,如果您只需要确保同一个用户的所有相同事件......最终都在同一个分区中,以便正确排序,您可以这样做。生产者负责他们写入的分区,因此如果您能够对数据进行逻辑分区,那么这可能更合适。
答案 2 :(得分:3)
我认为这个问题有两个不相似的,消费顺序和处理顺序。
消息队列可以 - 在某种程度上 - 保证消息将按顺序消费,但是,它们不能保证按照处理顺序保证消息。
这里的主要区别在于消息时无法确定消息处理的某些方面,例如:
如上所述,消费者在处理时可能会失败,这里消息的消费顺序是正确的,但是,消费者未能正确处理它,这将使其返回队列,直到现在消费订单是仍然完好但我们现在不知道处理顺序如何
如果通过“处理”我们的意思是现在丢弃消息并完成处理,那么考虑处理时间不是线性的情况,换句话说处理一个消息需要的时间比另一个消息长,所以如果消息3处理的时间比预期的要长,然后消息4和5可能会被消耗并在消息3之前完成处理
因此,即使您设法将消息返回到队列的前面(这违反了消费顺序),您仍然无法保证在下一条消息之前的所有消息都已完成处理。
如果您想确保处理订单,那么:
答案 3 :(得分:1)
有一些适当的方法可以保证RabbitMQ订阅中消息的顺序。
如果您使用多个使用者,则他们将使用共享的ExecutorService
处理消息。另请参见ConnectionFactory.setSharedExecutor(...)
。您可以设置一个Executors.newSingleThreadExecutor()
。
如果您将一个Consumer
与一个队列一起使用,则可以使用多个 bindingKeys 绑定该队列(它们可能具有通配符)。消息将按照消息代理收到消息的顺序放入队列中。
例如,您有一个发布者发布重要顺序的邮件:
try (Connection connection2 = factory.newConnection();
Channel channel2 = connection.createChannel()) {
// publish messages alternating to two different topics
for (int i = 0; i < messageCount; i++) {
final String routingKey = i % 2 == 0 ? routingEven : routingOdd;
channel2.basicPublish(exchange, routingKey, null, ("Hello" + i).getBytes(UTF_8));
}
}
您现在可能希望按照发布时的顺序,在队列中从两个 topic 接收消息:
// declare a queue for the consumer
final String queueName = channel.queueDeclare().getQueue();
// we bind to queue with the two different routingKeys
final String routingEven = "even";
final String routingOdd = "odd";
channel.queueBind(queueName, exchange, routingEven);
channel.queueBind(queueName, exchange, routingOdd);
channel.basicConsume(queueName, true, new DefaultConsumer(channel) { ... });
Consumer
现在将按照消息的发布顺序接收消息,无论您使用了不同的 topics 。
RabbitMQ文档中有一些不错的5分钟教程,可能会有所帮助: https://www.rabbitmq.com/tutorials/tutorial-five-java.html