我们正在开发一个使用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
。为什么两个队列的行为不同?
另外,什么是正确的行为?我们担心自动删除会删除尚未在队列中发送的消息。另一方面,如果我们一次又一次地运行测试,test-event-topic
下的新队列会不断被创建。我认为这是因为行 cachingConnectionFactory.setClientId(UUID.randomUUID().toString());
。但是对于持久订阅,不设置clientId会导致错误。
应用中使用的连接工厂是一个由 spring-artemis 创建的 CachingConnectionFactory
答案 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 中添加了共享持久主题订阅。