是否有可靠的方法来了解消息何时被成功确认?

时间:2017-12-15 11:35:40

标签: spring-boot rabbitmq spring-amqp spring-rabbit spring-rabbitmq

我有一个使用Spring AMQP的春季启动应用程序。 AMQP配置就像这样

@Configuration
public class AmqpConfig {
  @Bean
  DirectExchange directExchange() { return new DirectExchange("amq.direct"); }

  @Bean
  Queue testQueue() { return QueueBuilder.durable("test").build(); }

  @Bean
  Binding testBinding(Queue testQueue, DirectExchange directExchange) {
    return BindingBuilder.bind(testQueue).to(directExchange).with("test.routing.key");
  }

  @Bean
  SimpleRabbitListenerContainerFactory manualContainerFactory(ConnectionFactory connectionFactory,
                                                                     SimpleRabbitListenerContainerFactoryConfigurer configurer) {
    SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
    configurer.configure(containerFactory, connectionFactory);
    containerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    return containerFactory;
  }
}

所以我使用MANUAL确认。有一个听众

@Slf4j
@Component
public class ManualListener {
  @RabbitListener(queues = "test", containerFactory = "manualContainerFactory")
  public void processMsg(Message message, Channel channel, @Header(DELIVERY_TAG) long tag) throws IOException {
    try {
      log.info("Message received");
      Thread.sleep(20000);
      channel.basicAck(tag, false);
      log.info("Message processed");
    } catch (Exception e) {
      log.error("Something went wrong: {}", message, e);
      channel.basicNack(tag, false, false);
    }
  }
}

Thread.sleep(20000)来模拟一些耗时的过程。我的测试用例是:

  1. 向上方的听众发送消息
  2. 在20秒睡眠期间,重启rabbitmq。这有效地终止了队列中的所有通道
  3. 所以,在这种情况下我期望发生的是channel.basicAck抛出一个异常,即关闭通道,以便我可以采取相应的行动(恢复之前的行动或类似行为)。实际发生的是basicAck表示一切正常,CachingConnectionFactory只是在PRECONDITION_FAILED交付的背景中记录一个例外。

    2017-12-15 11:00:47.627  INFO 39397 --- [cTaskExecutor-1] .ManualListener : Message processed
    2017-12-15 11:00:47.628 ERROR 39397 --- [ 127.0.0.1:5672] nnectionFactory : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - unknown delivery tag 1, class-id=60, method-id=80)
    

    我的问题是:@RabbitListener是否有可靠的方法知道邮件何时被成功确认?

    Spring-boot:1.5.2.RELEASE,Spring-rabbit:1.7.1.RELEASE

    修改

    我尝试用Gary Russell提出的((ChannelProxy) channel).getTargetChannel()解决方案。看起来它只能缓解问题,仍有一些消息被错误地确认。我创建了这个测试,我认为它证明了这一点。

    @Slf4j
    @Component
    public class ManualListener {
      static int counter = 0;
    
      @RabbitListener(queues = "test", containerFactory = "manualContainerFactory")
      public void processMsg(Message message, Channel channel, @Header(DELIVERY_TAG) long tag) throws IOException {
          log.info("Message received with delivery tag {} and redelivered {}", message.getMessageProperties().getDeliveryTag(), message.getMessageProperties().getRedelivered());
          if (!message.getMessageProperties().getRedelivered()) {
            new Thread(() -> {
              try {
                channel.getConnection().close();
                log.info("Connection closed");
              } catch (Exception e) {
                log.error("Connection closed with timeout", e);
              }
            }).start();
          }
          new Thread(() -> {
            Channel actualChannel = ((ChannelProxy) channel).getTargetChannel();
            try {
              actualChannel.basicAck(tag, false);
              log.info("Number of acknowledged messages: {}", ++counter);
            } catch (Exception e) {
              log.error("Something went wrong: {}", message, e);
            }
          }).start();
      }
    }
    

    我在日志中看到的是:

    2018-01-10 13:17:34.133  INFO 17250 --- [cTaskExecutor-1] .ManualListener : Message received with delivery tag 1 and redelivered false
    2018-01-10 13:17:34.137  INFO 17250 --- [      Thread-27] .ManualListener : Number of acknowledged messages: 1
    2018-01-10 13:17:34.163  INFO 17250 --- [      Thread-26] .ManualListener : Connection closed
    2018-01-10 13:17:35.162  INFO 17250 --- [cTaskExecutor-2] .ManualListener : Message received with delivery tag 1 and redelivered true
    2018-01-10 13:17:35.162  INFO 17250 --- [      Thread-28] .ManualListener : Number of acknowledged messages: 2
    

    所以在测试中我两次同样的消息。在这种情况下,我期望发生的是在调用IOException时收到basicAck

    在我们的生产环境中出现此问题,应用程序经常处理大量的消息和确认。每次断开连接时,都会收到几条已经确认的消息。

1 个答案:

答案 0 :(得分:2)

当检测到关闭时,通道代理恢复(刷新)基础rabbitmq通道。

您可以拨打isOpen(),但该通话与您的basicAck()之间仍存在较小的竞争条件。

修改

您还可以添加关闭侦听器...

channel.addShutdownListener(s -> {
    System.out.println(s);
});
channel.basicAck(tag, false);

修改

这是一个可靠的解决方法......

@RabbitListener(queues = "foo")
public void foo(Message m, @Header(AmqpHeaders.CHANNEL) Channel channel,
        @Header(AmqpHeaders.DELIVERY_TAG) Long tag) throws Exception {
    System.in.read();
    Channel actualChannel = ((ChannelProxy) channel).getTargetChannel();
    try {
        actualChannel.basicAck(tag, false);
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

com.rabbitmq.client.AlreadyClosedException: connection is already closed due to connection error; protocol method: #method<connection.close>(reply-code=320, reply-text=CONNECTION_FORCED - Closed via management plugin, class-id=0, method-id=0)
    at com.rabbitmq.client.impl.AMQChannel.ensureIsOpen(AMQChannel.java:253)
    at com.rabbitmq.client.impl.AMQChannel.transmit(AMQChannel.java:422)
    at com.rabbitmq.client.impl.AMQChannel.transmit(AMQChannel.java:416)
    at com.rabbitmq.client.impl.ChannelN.basicAck(ChannelN.java:1164)
    at com.example.So47454769Application.foo(So47454769Application.java:42)