Spring AMQP RabbitMQ如何让两个并行的消费者不会同时抓住同一个任务?

时间:2014-01-23 14:13:56

标签: parallel-processing rabbitmq spring-integration

我有两个系统与RabbitMQ集成。

<小时/> 的背景

客户端从Spring-AMQP outbound-Gateway向RabbitMQExchange发送多条请求消息,rabbitmq- DirectExchange 将使用 round-robin 将这些消息分派给多个工作者(这些工作人员)独立位于不同的桌面并行运行相同的工作人员代码,以便通过使用<从RabbitExchange处理不同的消息强> simpleMessageListner


逻辑流程

Rabbitmq Tutorial multiWorker-DirectExchange类似。

  

客户 ----- sendRequests (5tasks)到----&gt; RabbitMQ-DirectExchange

     

然后 Rabbitmq-DirectExchange 将这5个任务分发给工作人员    PC1(Worker1) PC2(Worker2)

<小时/> ExchangeType&amp;我的装订

<!-- rabbit connection factory, rabbit template, and rabbit admin -->
 <rabbit:connection-factory
         id="connectionFactory"
         host="local IP address"
         username="guest"
         password="guest"
         channel-cache-size="10"   /> 

 <rabbit:template id="amqpTemplate" 
                  connection-factory="connectionFactory"
                  reply-timeout="600000" 
                  exchange="JobRequestDirectExchange"/>

 <rabbit:admin connection-factory="connectionFactory" id="rabbitAdmin" />

 <rabbit:direct-exchange name="taskRequests" 
                         auto-delete="false" 
                         durable="true"  >
     <rabbit:bindings>
        <rabbit:binding queue="jobRequests" key="request.doTask"   />
     </rabbit:bindings>

 </rabbit:direct-exchange>

 <rabbit:queue name="jobRequests" auto-delete="false" durable="true" />

工作人员 - 消费者配置

<rabbit:listener-container  id="workerContainer" 
        acknowledge="auto"
        prefetch="1"                         
        connection-factory="connectionFactory">
    <rabbit:listener ref="taskWorker" queue-names="jobRequests" /> 
</rabbit:listener-container>    

工人类是简单的POJO,他将处理请求并完成任务。


使用: RabbitMQ 3.2.2 Spring-Integration-Amqp 2.2


我的期望

我希望 Worker1 可以接收一些任务,而 Worker2 可以选择其余任务(其他任务)。

我希望工人能够并行一起完成整个5项任务。每次每个工作人员执行一项任务时,完成后将逐个分配另一个任务。 ( rabbit-listner 已设置为 prefetch = 1

worker1:t2 t3 t5

worker2:t1 t4

但是

经过大量的运行时测试,有时它可以正确完成任务。

Worker1 ------ task4 task1

Worker2 ------ task3 task2 task5

虽然有时像错误的那样:

Worker1 ------ task4 task1

Worker2 ------ task4 task2 task1

显然,task4和task1将被same1和worker2同时选中。


运行时测试:

我检查客户端 正确向RabbitExchange发送task1 task2 task3 task4 task5请求消息。但每次每个工人都会收到不同的任务。有一个常见的情况可能会引发错误的调度。

RabbitmqExchange中有5个任务(t1,t2,t3,t4,t5),它们将被发送给2个并行工作者(w1,w2)。< / p>

w1 获得了任务: t2 t1 t4

w2 获得了任务: t3 t1

作为Round-Robin调度方法,w1和w2依次获得任务。

w1 t2 w2 获得 t3

t2 t3 正在运行时,RabbitmqExchange会将 t1 发送到 w1 并等待来自的确认W1 即可。

假设 t2 花费更多时间来完成任务而 t3 ,并且 w1 w1 时执行 T1 即可。

w2 完成 t3 任务将收到RabbitmqExchange已发送 t1 ,因为 w2 不忙且RabbitExchange未收到 t1 已完成任务确认消息。

我的理解是

w1 w2 正在执行相同的任务 t1 。一旦完成 t1 将其中一个发送回RabbitmqExchange,然后RabbitmqExchange将出列一条任务消息。由于 t1 已完成两次,因此RabbitmqExchange会再出一条消息。因此, t5 消息已经出现,因为 t1 已经完成了两次。虽然RabbitmqExchange中的5条消息已被确认并且已经出列。但是两名失踪的工人做了 t5 并做了两次 t1


我该怎么办才能阻止两个并行工作程序从同一个Rabbit队列中获取相同的消息?

尝试自动确认方式,消息被正确识别。但是在服务器等待工作者的确认时,rabbitmq可能会重新发送未被确认但已经分发给另一个工作人员的消息。

还要考虑同步已发出的消息或优先发送消息。但是没有明确的愿景如何完成。

我很高兴听到有关此问题的任何想法。谢谢

3 个答案:

答案 0 :(得分:1)

我能想到的一件事就是为消费者带来这些重复的消息是消费者在发送确认消息之前关闭频道。

在这种情况下,RabbitMQ代理将重新排队消息并将其重新传送标记设置为 true 。来自RabbitMQ docs

  

如果消息被传递给消费者然后重新排队(例如,因为它在消费者连接丢失之前没有被确认),那么RabbitMQ将在再次传递时为其设置重新传递的标志(无论是对于同一消费者还是一个不同的)。这是一个消费者之前可能已经看到此消息的暗示(尽管不能保证,消息可能已经从代理中删除但在连接丢失之前没有进入消费者)。相反,如果未设置重新传送的标志,则保证之前没有看到该消息。因此,如果消费者发现重复删除消息或以幂等方式处理消息更加昂贵,它只能对设置了redelivered标志的消息执行此操作。

如果您在测试时在发送确认之前关闭其中一个工作进程,或者如果它们发生故障,则很可能发生这种情况。您可以尝试检查 redelivered 标志,以避免其他消费者再次处理它,如果是这种情况。

我注意到的另一件事是您的消费者配置中的预取设置。您应该将其设置为更高的值(根据您的需要进行调整),而不是仅将其保留为1.您可以详细了解预取 here

希望有所帮助!

答案 1 :(得分:0)

我花了很长时间来研究SpringConfigured-way来实现这个功能但是失败了。

虽然我使用RabbitMQ Java Client API推出了可行的解决方案。

在QuartzScheduler中使用Spring-Asynchronous Gateway,它总是会根据需要发送问题。我猜它有多线程排序的原因。

一开始,我认为因为Channel实例可以由多个线程同时访问。这样就无法正确处理确认。

  

对此的一个重要警告是,在多个线程之间共享Channel时,无法正确处理确认。因此,在这种情况下,确保多个线程不会同时访问Channel实例非常重要。

高于http://www.rabbitmq.com/javadoc/com/rabbitmq/client/Channel.html

最后,我决定放弃使用Spring-way并改回使用RabbitMQ API(在我使用Spring XML之前配置网关/通道,现在使用RabbitMQ-JavaClient java编程方式声明与通道的交换。)。并添加RabbitMQRPC用于异步回调的用法。现在一切都适合当前的要求。


总结 ,我的要求的最终解决方案是:

  • 使用 RabbitMQ JAVAClient API 来声明exchange / channels / binding / routingKey。 对于客户端和服务器端。

  • 使用 RabbitMQ RPC 实现异步回调功能。

(我按照RabbitMQ的java教程,使用此链接:http://www.rabbitmq.com/tutorials/tutorial-six-java.html

答案 2 :(得分:0)

您是否按照讨论的here尝试在侦听器容器上设置concurrentConsumers属性?