Spring integration 如何在Spring Cloud Stream中手动确认RabbitMQ消息?

Spring integration 如何在Spring Cloud Stream中手动确认RabbitMQ消息?,spring-integration,spring-cloud-stream,spring-integration-amqp,Spring Integration,Spring Cloud Stream,Spring Integration Amqp,对于基于流的服务,我希望当在@StreamListener中调用的基础服务失败时,消息保留在队列中。为此,我的理解是,唯一的方法是配置spring.cloud.stream.bindings.channel\u name.consumer.acknowledge mode=MANUAL 在进行此配置更改之后,我尝试将@Header(AmqpHeaders.CHANNEL)CHANNEL CHANNEL、@Header(AmqpHeaders.DELIVERY_标记)Long deliveryTa

对于基于流的服务,我希望当在
@StreamListener
中调用的基础服务失败时,消息保留在队列中。为此,我的理解是,唯一的方法是配置
spring.cloud.stream.bindings.channel\u name.consumer.acknowledge mode=MANUAL

在进行此配置更改之后,我尝试将
@Header(AmqpHeaders.CHANNEL)CHANNEL CHANNEL、@Header(AmqpHeaders.DELIVERY_标记)Long deliveryTag
作为方法参数添加到我现有的
@StreamListener
实现中,如文档所示。使用此代码时,我遇到了以下异常:

org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener threw exception
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.wrapToListenerExecutionFailedExceptionIfNeeded(AbstractMessageListenerContainer.java:941)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:851)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:771)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$001(SimpleMessageListenerContainer.java:102)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$1.invokeListener(SimpleMessageListenerContainer.java:198)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.java:1311)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:752)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:1254)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:1224)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:102)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1470)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.messaging.MessageHandlingException: Missing header 'amqp_channel' for method parameter type [interface com.rabbitmq.client.Channel]
    at org.springframework.messaging.handler.annotation.support.HeaderMethodArgumentResolver.handleMissingValue(HeaderMethodArgumentResolver.java:100)
    at org.springframework.messaging.handler.annotation.support.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:103)
    at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:112)
然后我发现了以下内容:,它显示了如何使用Kafka执行消息确认的示例,但我目前正在使用RabbitMQ绑定。我们计划最终转移到Kafka,但目前,我如何配置和编写解决方案,以对成功处理的消息进行手动消息确认和手动消息拒绝,从而在遇到异常时将消息留在队列中。我目前使用SpringCloud
Edgware.RELEASE
和SpringCloudStream
Ditmars.RELEASE

更新

现在我有以下配置:

spring:
  cloud:
    stream:
      bindings:
        do-something-async-reply:
          group: xyz-service-do-something-async-reply
      rabbit:
        bindings:
          do-something-async-reply:
            consumer:
              autoBindDlq: true
              dlqDeadLetterExchange:
              dlqTtl: 10000
              requeueRejected: true
我在服务启动时收到以下错误:

2018-01-12 14:46:34.346 ERROR [xyz-service,,,] 2488 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue 'do-something-async-reply.xyz-service-do-something-async-reply' in vhost '/': received the value 'DLX' of type 'longstr' but current is none, class-id=50, method-id=10)
2018-01-12 14:46:34.346错误[xyz服务,,,]2488---[127.0.0.1:5672]o.s.a.r.c.CachingConnectionFactory:通道关闭:通道错误;协议方法:#方法(回复代码=406,回复文本=Premission_失败-vhost'/'中队列'do something async reply.xyz service do something async reply'的不等价参数'x-dead-letter-exchange':收到'longstr'类型的值'DLX',但当前值为none,类id=50,方法id=10)

什么配置错误/我丢失了吗?

属性名称不正确;您缺少
.rabbit
。它是

spring.cloud.stream.rabbit.bindings.
.consumer.acknowledge mode=MANUAL

因为这是兔子特有的属性-请参阅

编辑

例如:

@SpringBootApplication
@EnableBinding(Sink.class)
public class So481977082Application {

    public static void main(String[] args) {
        SpringApplication.run(So481977082Application.class, args);
    }

    @StreamListener(Sink.INPUT)
    public void in(String in, @Header(AmqpHeaders.CHANNEL) Channel channel,
            @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
        System.out.println(in);
        Thread.sleep(60_000);
        channel.basicAck(tag, false);
        System.out.println("Ackd");
    }

}
请记住,需要手动确认通常是一种气味;一般来说,最好让容器处理ACK;请参见同一doco链接中的
requeueRejected
。无条件重新排队可能导致无限循环

EDIT2

对我来说很好

@SpringBootApplication
@EnableBinding(Processor.class)
public class So48197708Application {

    public static void main(String[] args) {
        SpringApplication.run(So48197708Application.class, args);
    }

    @Bean
    ApplicationRunner runner(MessageChannel output) {
        return args -> {
            output.send(new GenericMessage<>("foo"));
        };
    }

    @StreamListener(Sink.INPUT)
    public void listen(@Header(name = "x-death", required = false) List<?> death) {
        System.out.println(death);
        throw new RuntimeException("x");
    }

}
结果:

null
...
Caused by: java.lang.RuntimeException: x
...
[{reason=expired, count=1, exchange=DLX, routing-keys=[foo.foo], time=Fri Jan 12 17:20:28 EST 2018, queue=foo.foo.dlq}, 
    {reason=rejected, count=1, exchange=foo, time=Fri Jan 12 17:20:18 EST 2018, routing-keys=[foo], queue=foo.foo}]
...

...
[{reason=expired, count=3, exchange=DLX, routing-keys=[foo.foo], time=Fri Jan 12 17:20:28 EST 2018, queue=foo.foo.dlq}, 
    {reason=rejected, count=3, exchange=foo, routing-keys=[foo], time=Fri Jan 12 17:20:18 EST 2018, queue=foo.foo}]

Gary,当requeueRejected设置为true时,当从
@StreamListener
引发异常时,消息是否会自动重新发出请求?这就是所有需要做的吗?好的,我在网站上找到了一些关于这方面的信息。当文档状态为“将DLQDEADLETTERETEXchange设置为默认交换”时,默认交换的值是多少?除了
AmqpRejectAndDontRequeueException
之外的任何异常都将导致重新查询消息,只要禁用重试功能。如果启用了重试,则当重试次数用尽时,绑定器将抛出该异常。还有一个
republishToDlq
,活页夹将在其中发布故障,包括包含故障信息的标题。默认交换为空字符串。所有队列都以其队列名称作为路由密钥绑定到该交换。如中所示,您只需为
dlqDeadLetterExchange
not''使用空白值。有关工作示例,请参阅我的第二次编辑。由于您的队列已经存在,您必须将其删除;无法更改现有队列上的参数。
null
...
Caused by: java.lang.RuntimeException: x
...
[{reason=expired, count=1, exchange=DLX, routing-keys=[foo.foo], time=Fri Jan 12 17:20:28 EST 2018, queue=foo.foo.dlq}, 
    {reason=rejected, count=1, exchange=foo, time=Fri Jan 12 17:20:18 EST 2018, routing-keys=[foo], queue=foo.foo}]
...

...
[{reason=expired, count=3, exchange=DLX, routing-keys=[foo.foo], time=Fri Jan 12 17:20:28 EST 2018, queue=foo.foo.dlq}, 
    {reason=rejected, count=3, exchange=foo, routing-keys=[foo], time=Fri Jan 12 17:20:18 EST 2018, queue=foo.foo}]