如果所有侦听器都被销毁,为什么 ActiveMQ Artemis 会自动删除地址?

时间:2021-06-01 17:15:41

标签: java jms spring-jms activemq-artemis

我们正在开发一个使用ActiveMQ Artemis作为服务间通信方式的微服务系统。由于要求能够在运行时停止侦听器,因此我们不能使用 spring-artemis 提供的 @JmsListener。在网上挖掘并发现 spring 在幕后使用 MessageListenerContainer 后,我们想出了维护一个 MessageListenerContainer 我们自己的列表的想法。

    @Bean(name = "commandJmsListenerContainerFactory")
    public DefaultJmsListenerContainerFactory commandJmsListenerContainerFactory(
        DefaultJmsListenerContainerFactoryConfigurer configurer) {
        var factory = new DefaultJmsListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        factory.setPubSubDomain(false);
        return factory;
    }

    // Use
    private Map<String, DefaultMessageListenerContainer> commandQueue;

    public void subscribeToCommandQueue(String queueName, CommandListener<?> command) {
        commandQueue.computeIfAbsent(queueName, key -> {
            var endPoint = new SimpleJmsListenerEndpoint();
            endPoint.setDestination(queueName);
            endPoint.setMessageListener(message -> {
                try {
                    var body = message.getBody(String.class);
                    command.execute(commandMessageConverter.deserialize(body));
                } catch (JMSException e) {
                    throw new RuntimeException("Error while process message for queue: " + queueName, e);
                }
            });
            var container = commandJmsListenerContainerFactory.createListenerContainer(endPoint);
            // https://stackoverflow.com/questions/44555106/defaultmessagelistenercontainer-not-reading-messages-from-ibm-mq
            // for Every object of Spring classes that implement InitializingBean created manually, we need to call afterPropertiesSet to make the object "work"
            container.afterPropertiesSet();
            container.start();
            return container;
        });
    }

    public void start() {
        commandQueue = new ConcurrentHashMap<>();
    }

    public void stop() {
        commandQueue.values().forEach(DefaultMessageListenerContainer::destroy);
        commandQueue.clear();
    }

在测试时,我注意到在我们通过调用 stop() 销毁所有侦听器后,Artemis 控制台中的队列和地址也被删除了。持久订阅并非如此。

    @Bean(name = "eventJmsListenerContainerFactory")
    public DefaultJmsListenerContainerFactory eventJmsListenerContainerFactory(
        CachingConnectionFactory cachingConnectionFactory,
        DefaultJmsListenerContainerFactoryConfigurer configurer) {
        cachingConnectionFactory.setClientId(UUID.randomUUID().toString());
        var factory = new DefaultJmsListenerContainerFactory();
        configurer.configure(factory, cachingConnectionFactory);
        factory.setPubSubDomain(true);
        factory.setSubscriptionDurable(true);
        return factory;
    }

    // usage is the same as the first block code, except we store multicast subscriptions in another map
    private Map<String, DefaultMessageListenerContainer> eventTopic;

运行单元测试并销毁两个映射的所有侦听器后,仅保留 test-event-topic 地址及其队列,删除 test-command-queue。为什么两个队列的行为不同?

enter image description here

另外,什么是正确的行为?我们担心自动删除会删除尚未在队列中发送的消息。另一方面,如果我们一次又一次地运行测试,test-event-topic 下的新队列会不断被创建。我认为这是因为行 cachingConnectionFactory.setClientId(UUID.randomUUID().toString()); 。但是对于持久订阅,不设置clientId会导致错误。

应用中使用的连接工厂是一个由 spring-artemis 创建的 CachingConnectionFactory

1 个答案:

答案 0 :(得分:2)

默认情况下,当核心 JMS 客户端发送消息或创建消费者时,代理将根据需要自动创建地址和队列。默认情况下,当不再需要这些资源时(即当队列没有消费者和消息或地址不再绑定任何队列时),这些资源也会自动删除。这由 the documentation 中讨论的 broker.xml 中的这些设置控制:

  • auto-create-queues
  • auto-delete-queues
  • auto-create-addresses
  • auto-delete-addresses

需要明确的是,默认情况下自动删除不会导致任何消息丢失,因为只有当队列有 0 个消费者和 0 个消息时才应该删除它们。但是,您始终可以将自动删除设置为 false 以确保 100% 安全。

代表持久 JMS 主题订阅的队列不会被删除,因为它们旨在在消费者离线时保留并收集消息。换句话说,如果使用订阅的客户端在没有首先明确删除订阅的情况下关闭,则持久主题订阅将保留。这就是持久订阅的全部意义——它们持久。如果任何客户端使用相同的客户端 ID 连接并使用相同的订阅名称,则它可以使用持久主题订阅。但是,除非持久订阅是“共享”持久订阅,否则一次只能连接一个客户端。 JMS 2.0 中添加了共享持久主题订阅。