Apache Camel:RabbitMQ将消息重新排队到同一队列会导致重复消息

时间:2017-08-25 10:28:16

标签: duplicates rabbitmq apache-camel

我正在做类似

的事情
  from(rabbitmq:pollingQueue?prefetchSize=1&concurrentConsumer=10)
        .process(pollingRequestStatus) // check status of the request, if not ready, requeue = true
        .Choice
           .when(requeue == true) // request not ready
           .to(rabbitmq:pollingQueue)//back to the same queue
        .endChoice
        .otherwise
        .to(proceedToSomethingElse)
        .endChoice.end;

当重新发生重新排队时,消息会重复,这是将消息发送回同一队列时的预期行为吗?

我也按照建议尝试了类似下面的内容,但它不起作用,消息似乎只是被消耗而且不会重新排队

from(rabbitmq:pollingQueue? prefetchSize=1&concurrentConsumer=10)
     .onException(NotReadyException.class)
     .handled(true)
     .setHeader(RabbitMQConstants.REQUEUE, constant(true))
     .end() 
     .process(pollingRequestStatus) // check status of the request, if not ready, throw NotReadyEception
        .to(proceedToSomethingElse);

我试过的另外两种方法至少不会产生重复,

1。)在NotReadyExeption上,将消息发送回pollingQueue

from(rabbitmq:pollingQueue? prefetchSize=1&concurrentConsumer=10)
    .onException(NotReadyException.class)
    .to(rabbitmq:pollingQueue)
    //.delay(constant(8000)) //not sure why it throws error if i set delay
    .end
    .process(pollingRequestStatus); // check status of the request, if not ready, throw NotReadyEception

然而,它的运行速度太快,就像瞬间一样。 如果我设置延迟(常数(数字)),则抛出以下错误,

Exception in thread "main" org.apache.camel.FailedToCreateRouteException: Failed to create route route13 at: >>> From [bla bla bla...]
    at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:1062)
    at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:196)
    at org.apache.camel.impl.DefaultCamelContext.startRoute(DefaultCamelContext.java:984)
    at org.apache.camel.impl.DefaultCamelContext.startRouteDefinitions(DefaultCamelContext.java:3401)
    at org.apache.camel.impl.DefaultCamelContext.doStartCamel(DefaultCamelContext.java:3132)
    at org.apache.camel.impl.DefaultCamelContext.access$000(DefaultCamelContext.java:183)
    at org.apache.camel.impl.DefaultCamelContext$2.call(DefaultCamelContext.java:2961)
    at org.apache.camel.impl.DefaultCamelContext$2.call(DefaultCamelContext.java:2957)
    at org.apache.camel.impl.DefaultCamelContext.doWithDefinedClassLoader(DefaultCamelContext.java:2980)
    at org.apache.camel.impl.DefaultCamelContext.doStart(DefaultCamelContext.java:2957)
    at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
    at org.apache.camel.impl.DefaultCamelContext.start(DefaultCamelContext.java:2924)
    at com.mbww.ithink.runner.Main.main(Main.java:174)
Caused by: java.lang.IllegalArgumentException: Route route13 has no output processors. You need to add outputs to the route such as to("log:foo").
    at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:1060)

2.。)在NotReadyException上,基于redeliveryPolicy的重新发送

from(rabbitmq:pollingQueue? prefetchSize=1&concurrentConsumer=10)
    .onException(NotReadyException.class)
    .setFaultBody(constant(false))
    .maximumRedeliveries(-1) // -1 = redeliver forever
    .redeliveryDelay(10000)
    .end
    .process(pollingRequestStatus); // check status of the request, if not ready, throw NotReadyEception

最初重新排队的想法是,如果请求没有准备就绪,请将消息重新排队回队列,设置延迟并检查下一个请求的状态,并避免出现像Ratelimit这样的错误。 似乎重新交付政策是现在的方式。

由于

2 个答案:

答案 0 :(得分:4)

为了能够重新排列消息,您必须关闭RabbitMQ的自动确认。在这种情况下,您必须手动将acknackreject消息发送回发布商。 (https://www.rabbitmq.com/confirms.html

这意味着您必须在当前basicAck实施中手动调用其中一个basicNackbasicRejectChannel函数。

转换为Camel:

要启用自动确认功能,请将autoAck=false添加到端点参数。

AFAIK,Camel Endpoint的底层频道无法访问(source),因此您无法直接调用频道的basicReject(long deliveryTag, boolean requeue)功能,但Camel会在交换失败时调用它(在此期间发生异常)路由)。

解决方法可能如下:(伪编码,我没有尝试过,但是基于检查camel-rabbitmq端点的来源,特别是this部分)

更新了解决方法(已测试并正常工作):

from("rabbitmq://localhost:5672/first?queue=test&concurrentConsumers=10prefetchSize=1&autoAck=false&autoDelete=false")
            .onException(NotReadyException.class)
                .log("Error for ${body}! Requeue")
                .asyncDelayedRedelivery().redeliveryDelay(5000) // wait 5 secs to redeliver and requeue
                .maximumRedeliveries(1)
                .setHeader(RabbitMQConstants.REQUEUE, constant(true))
                .handled(true)
                .setFaultBody(constant(true))
            .end()
            .log("Received: ${body}")
            .process((e) -> {
                if(notReady(e))
                    throw new NotReadyException(); // create a new Exception and throw it if the status is not ready
                }
            })
            .to("direct:somethingElse");

我还创建了一个gist,它实现了几乎相同的场景。 希望它有所帮助!

答案 1 :(得分:0)

您不需要再次发送消息,只需将rabbitmq.REQUEUE属性设置为true即可。如果设置了此属性,rabbitmq组件将自动重新排队消息而不是丢弃它。来自the docs

  

Camel 2.14.2:消费者使用它来控制拒绝   信息。当消费者完成处理交换时,如果   交换失败,然后消费者将拒绝该消息   来自RabbitMQ经纪人。此标头的值控制了这一点   行为。如果值为false(默认情况下),则消息为   丢弃/死字母。如果值为true,则消息为   重新排队。

因此,在处理器内部,您可以执行以下操作:

exchange.getIn().setHeader("rabbitmq.REQUEUE", true);

然后在您的路线中检查REQUEUE header == false以致电proceedToSomethingElse