简体   繁体   English

Spring AMQP 处理删除的队列

[英]Spring AMQP handle deleted queues

I'm trying to make a Java service using Spring Boot that connects to a Rabbit exchange, discover new queues (that matches with a given prefix) and connect to them.我正在尝试使用连接到 Rabbit 交换器的 Spring Boot 创建一个 Java 服务,发现新队列(与给定前缀匹配)并连接到它们。 I'm using RabbitManagementTemplate to discover and SimpleMessageListenerContainer to create a bind.我正在使用RabbitManagementTemplate来发现并使用SimpleMessageListenerContainer来创建绑定。 It works fine.它工作正常。

The problem is that when one of these dynamic queues gets deleted (by the web interface for example), my service can't handle the exception and I didn't find a place to register some handler to fix this.问题是,当这些动态队列之一被删除时(例如通过 web 接口),我的服务无法处理异常,我找不到注册某些处理程序的地方来解决这个问题。 For these cases I just want to ignore the deletion and move on, I'm not willing to recreate the queue.对于这些情况,我只想忽略删除并继续,我不愿意重新创建队列。

My code is something like我的代码是这样的

@Scheduled(fixedDelay = 3*1000)
public void watchNewQueues() {
    for (Queue queue : rabbitManagementTemplate.getQueues()) {
        final String queueName = queue.getName();
        String[] nameParts = queueName.split("\\.");
        if ("dynamic-queue".equals(nameParts[0]) && !context.containsBean(queueName)) {

            logger.info("New queue discovered! Binding to {}", queueName);
            Binding binding = BindingBuilder.bind(queue).to(exchange).with("testroute.#");
            rabbitAdmin.declareBinding(binding);
            rabbitAdmin.declareQueue(queue);

            SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
            container.setConnectionFactory(connectionFactory);
            container.setQueueNames(queueName);
            container.setMessageListener(new MyMessageListener());
            container.setPrefetchCount(settings.getPrefetch());

            container.setAutoDeclare(false);
            container.setMissingQueuesFatal(true);
            container.setDeclarationRetries(0);
            container.setFailedDeclarationRetryInterval(-1);

            context.getBeanFactory().registerSingleton(queueName, container);

            container.start();
        }
    }
}

@Override
public void onApplicationEvent(ListenerContainerConsumerFailedEvent event) {
    if (event.getSource() instanceof SimpleMessageListenerContainer) {
        SimpleMessageListenerContainer container = (SimpleMessageListenerContainer) event.getSource();
        if (context.getAutowireCapableBeanFactory() instanceof BeanDefinitionRegistry) {
            logger.info("Removing bean! {}", container.getQueueNames()[0]);
            ((BeanDefinitionRegistry)context.getAutowireCapableBeanFactory()).removeBeanDefinition(container.getQueueNames()[0]);
        } else {
            logger.info("Context is not able to remove bean");
        }
    } else {
        logger.info("Got event but is not a SimpleMessageListenerContainer {}", event.toString());
    }
}

And when the queue gets deleted, console logs:当队列被删除时,控制台日志:

2018-03-13 15:01:29.623  WARN 32736 [pool-1-thread-6] --- o.s.a.r.listener.BlockingQueueConsumer   : Cancel received for amq.ctag-wKQUQkUNOSCtjQ9RBUNCig; Consumer: tags=[{amq.ctag-wKQUQkUNOSCtjQ9RBUNCig=dynamic-queue.some-test}], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@localhost:5672/,3), conn: Proxy@23510c77 Shared Rabbit Connection: SimpleConnection@66c17803 [delegate=amqp://guest@localhost:5672/], acknowledgeMode=AUTO local queue size=0
2018-03-13 15:01:30.219  WARN 32736 [SimpleAsyncTaskExecutor-1] --- o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception, processing can restart if the connection factory supports it. Exception summary: org.springframework.amqp.rabbit.support.ConsumerCancelledException
2018-03-13 15:01:30.219  INFO 32736 [SimpleAsyncTaskExecutor-1] --- o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer: tags=[{}], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@localhost:5672/,3), conn: Proxy@23510c77 Shared Rabbit Connection: SimpleConnection@66c17803 [delegate=amqp://guest@localhost:5672/], acknowledgeMode=AUTO local queue size=0
2018-03-13 15:01:30.243  WARN 32736 [SimpleAsyncTaskExecutor-2] --- o.s.a.r.listener.BlockingQueueConsumer   : Failed to declare queue:dynamic-queue.some-test
2018-03-13 15:01:30.246  WARN 32736 [SimpleAsyncTaskExecutor-2] --- o.s.a.r.listener.BlockingQueueConsumer   : Queue declaration failed; retries left=3

org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[dynamic-queue.some-test]
  at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.java:571)
  at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:470)
  at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1171)
  at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.IOException: null
  at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:106)
  at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:102)
  at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:124)
  at com.rabbitmq.client.impl.ChannelN.queueDeclarePassive(ChannelN.java:885)
  at com.rabbitmq.client.impl.ChannelN.queueDeclarePassive(ChannelN.java:61)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at org.springframework.amqp.rabbit.connection.CachingConnectionFactory$CachedChannelInvocationHandler.invoke(CachingConnectionFactory.java:835)
  at com.sun.proxy.$Proxy63.queueDeclarePassive(Unknown Source)
  at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.java:550)
  ... 3 common frames omitted
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'dynamic-queue.some-test' in vhost '/', class-id=50, method-id=10)
  at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:67)
  at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:33)
  at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:361)
  at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:226)
  at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:118)
  ... 12 common frames omitted
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'dynamic-queue.some-test' in vhost '/', class-id=50, method-id=10)
  at com.rabbitmq.client.impl.ChannelN.asyncShutdown(ChannelN.java:484)
  at com.rabbitmq.client.impl.ChannelN.processAsync(ChannelN.java:321)
  at com.rabbitmq.client.impl.AMQChannel.handleCompleteInboundCommand(AMQChannel.java:144)
  at com.rabbitmq.client.impl.AMQChannel.handleFrame(AMQChannel.java:91)
  at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:554)
  ... 1 common frames omitted

Thanks for your attention感谢您的关注

EDIT: Thanks.编辑:谢谢。 I was able to avoid the recreation of the queue: I'm now struggling to remove the queue from the Spring Context :)我能够避免重新创建队列:我现在正在努力从 Spring 上下文中删除队列 :)

You'll get error logs, of course, but with container.setMissingQueuesFatal(true);当然,您会收到错误日志,但是使用container.setMissingQueuesFatal(true); (the default), the container will stop itself after 3 attempts to declare the queue at 5 second intervals. (默认),容器将在以 5 秒为间隔尝试声明队列 3 次后自行停止。

You can affect the time it takes to stop by setting the declarationRetries (default 3) and failedDeclarationRetryInterval (default 5000).您可以通过设置declarationRetries (默认 3)和failedDeclarationRetryInterval (默认 5000)来影响停止所需的时间。

The easiest way is to use ApplicationListener for MissingQueueEvent最简单的方法是对 MissingQueueEvent 使用 ApplicationListener

@Component
public class MissingQueueListener implements ApplicationListener<MissingQueueEvent> {

    private static final Logger logger = LoggerFactory.getLogger(MissingQueueListener.class);

    @Override
    public void onApplicationEvent(MissingQueueEvent missingQueueEvent) {
        ((SimpleMessageListenerContainer) missingQueueEvent.getSource()).removeQueueNames(missingQueueEvent.getQueue());
        logger.error("Removing missing queue {} from its container", missingQueueEvent.getQueue());
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM