消息分组似乎无效
JMSXGroupID
设置为'product=paper'
后,我的Producer应用程序通过JMS MessageProducer
将消息发送到队列。'product=paper'
发送同样的消息。_AMQ_GROUP_ID
的值均为'product=paper'
。 JMSXGroupID
不存在。_AMQ_GROUP_ID
不存在,并且JMSXGroupID
的值为null而不是'product=paper'
。为什么带有组ID的邮件分组不起作用?这与Artemis没有将_AMQ_GROUP_ID
转换回JMSXGroupID
的事实有关吗?还是Spring JMS不会将其多个使用方线程注册为不同的使用方,以使代理可以看到多个使用方?
编辑: 通过注释掉与使用容器工厂bean方法中的事务处理会话有关的行,我能够使消息分组在我的应用程序中工作。似乎与使用事务处理会话有关。
Edit2:
这是一个针对本地独立Artemis代理(版本2.10.1)并使用Spring Boot 2.2.0运行的自包含应用程序:
GroupidApplication(春季启动应用程序和bean):
package com.reproduce.groupid;
import java.util.HashMap;
import java.util.Map;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient;
import org.apache.activemq.artemis.api.jms.JMSFactoryType;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.connection.JmsTransactionManager;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
@SpringBootApplication
@EnableJms
public class GroupidApplication implements CommandLineRunner {
private static Logger LOG = LoggerFactory
.getLogger(GroupidApplication.class);
@Autowired
private JmsTemplate jmsTemplate;
@Autowired MessageConverter messageConverter;
public static void main(String[] args) {
LOG.info("STARTING THE APPLICATION");
SpringApplication.run(GroupidApplication.class, args);
LOG.info("APPLICATION FINISHED");
}
@Override
public void run(String... args) throws JMSException {
LOG.info("EXECUTING : command line runner");
jmsTemplate.setPubSubDomain(true);
createAndSendTextMessage("Message1");
createAndSendTextMessage("Message2");
createAndSendTextMessage("Message3");
createAndSendTextMessage("Message4");
createAndSendTextMessage("Message5");
createAndSendTextMessage("Message6");
}
private void createAndSendTextMessage(String messageBody) {
jmsTemplate.send("local-queue", session -> {
Message message = session.createTextMessage(messageBody);
message.setStringProperty("JMSXGroupID", "product=paper");
return message;
});
}
// BEANS
@Bean
public JmsListenerContainerFactory<?> containerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer, JmsTransactionManager jmsTransactionManager) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setSubscriptionDurable(true);
factory.setSubscriptionShared(true);
factory.setSessionAcknowledgeMode(Session.SESSION_TRANSACTED);
factory.setSessionTransacted(Boolean.TRUE);
factory.setTransactionManager(jmsTransactionManager);
return factory;
}
@Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
@Bean
@Primary
public JmsTransactionManager jmsTransactionManager(ConnectionFactory connectionFactory) {
JmsTransactionManager jmsTransactionManager = new JmsTransactionManager(connectionFactory);
// Lazily retrieve existing JMS Connection from given ConnectionFactory
jmsTransactionManager.setLazyResourceRetrieval(true);
return jmsTransactionManager;
}
@Bean
@Primary
public ConnectionFactory connectionFactory() throws JMSException {
// Create ConnectionFactory which enables failover between primary and backup brokers
ActiveMQConnectionFactory activeMqConnectionFactory = ActiveMQJMSClient.createConnectionFactoryWithHA(
JMSFactoryType.CF, transportConfigurations());
activeMqConnectionFactory.setBrokerURL("tcp://localhost:61616?jms.redeliveryPolicy.maximumRedeliveries=1");
activeMqConnectionFactory.setUser("admin");
activeMqConnectionFactory.setPassword("admin");
activeMqConnectionFactory.setInitialConnectAttempts(1);
activeMqConnectionFactory.setReconnectAttempts(5);
activeMqConnectionFactory.setConsumerWindowSize(0);
activeMqConnectionFactory.setBlockOnAcknowledge(true);
activeMqConnectionFactory.setCacheDestinations(true);
activeMqConnectionFactory.setRetryInterval(1000);
return activeMqConnectionFactory;
}
private static TransportConfiguration[] transportConfigurations() {
String connectorFactoryFqcn = NettyConnectorFactory.class.getName();
Map<String, Object> primaryTransportParameters = new HashMap<>(2);
primaryTransportParameters.put("host", "localhost");
primaryTransportParameters.put("port", "61616");
TransportConfiguration primaryTransportConfiguration = new TransportConfiguration(connectorFactoryFqcn,
primaryTransportParameters);
return new TransportConfiguration[] { primaryTransportConfiguration,
new TransportConfiguration(connectorFactoryFqcn) };
}
}
CustomSpringJmsListener:
package com.reproduce.groupid;
import javax.jms.JMSException;
import javax.jms.TextMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class CustomSpringJmsListener {
protected final Logger LOG = LoggerFactory.getLogger(getClass());
@JmsListener(destination = "local-queue", subscription = "groupid-example", containerFactory = "containerFactory", concurrency = "15-15")
public void receive(TextMessage message) throws JMSException {
LOG.info("Received message: " + message);
}
}
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.reproduce</groupId>
<artifactId>groupid</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>groupid</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-artemis</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
您可以看到,即使所有这些消息都具有相同的组ID,它们也会由不同的侦听器容器线程记录。如果您从bean定义中注释掉事务管理器,它将再次开始工作。
答案 0 :(得分:1)
所有与消费者缓存有关。默认情况下,使用外部TXM时,将禁用缓存,以便在新使用者上接收每条消息。
对于这个应用程序,您真的不需要事务管理器,sessionTransacted
就足够了-容器将使用本地事务。
如果由于某种原因必须使用外部事务管理器,请考虑更改缓存级别。
factory.setCacheLevel(DefaultMessageListenerContainer.CACHE_CONSUMER);
请参阅DMLC javadocs ...
/**
* Specify the level of caching that this listener container is allowed to apply.
* <p>Default is {@link #CACHE_NONE} if an external transaction manager has been specified
* (to reobtain all resources freshly within the scope of the external transaction),
* and {@link #CACHE_CONSUMER} otherwise (operating with local JMS resources).
* <p>Some Java EE servers only register their JMS resources with an ongoing XA
* transaction in case of a freshly obtained JMS {@code Connection} and {@code Session},
* which is why this listener container by default does not cache any of those.
* However, depending on the rules of your server with respect to the caching
* of transactional resources, consider switching this setting to at least
* {@link #CACHE_CONNECTION} or {@link #CACHE_SESSION} even in conjunction with an
* external transaction manager.
* @see #CACHE_NONE
* @see #CACHE_CONNECTION
* @see #CACHE_SESSION
* @see #CACHE_CONSUMER
* @see #setCacheLevelName
* @see #setTransactionManager
*/
public void setCacheLevel(int cacheLevel) {