我有一个使用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)
来模拟一些耗时的过程。我的测试用例是:
所以,在这种情况下我期望发生的是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
。
在我们的生产环境中出现此问题,应用程序经常处理大量的消息和确认。每次断开连接时,都会收到几条已经确认的消息。
答案 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)