我想在link中提到的使用DefaultMessageListenerConatiner Of Spring(通过覆盖doReceiveAndExecute)在一些timeLimit(Ex:收到第一条消息后300毫秒)之后接收来自Queue的大量消息。
我可以将批量大小的消息分组,即当队列有太多消息时,我可以收到少于20条消息,当队列中的消息非常少时,我可以收到少于20条消息。
问题 :
我发现即使队列已满,也需要花费太多时间(有时1秒,有时2秒甚至更长)才能将消息发送到Listener。
当我尝试使用DefaultMessageListenerConatiner同时接收单个消息时,我看到消息是在几毫秒的延迟(如1毫秒或最大30到60毫秒)内接收的
我没有指定transactionTimeout或receiveTimeout,也没有链接任何transactionManager。
Springers可以帮我找一下timeOut的指定位置或者如何减少时间延迟?
BatchMessageListenerContainer:
package com.mypackage;
import javax.jms.Session;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import java.util.ArrayList;
import java.util.List;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.connection.ConnectionFactoryUtils;
import org.springframework.jms.support.JmsUtils;
import org.springframework.transaction.TransactionStatus;
/**
* Listener Container that allows batch consumption of messages. Works only with transacted sessions
*/
public class BatchMessageListenerContainer extends DefaultMessageListenerContainer {
public static final int DEFAULT_BATCH_SIZE = 20;
private int batchSize = DEFAULT_BATCH_SIZE;
public BatchMessageListenerContainer() {
super();
setSessionTransacted(true);
}
/**
* @return The batch size on this container
*/
public int getBatchSize() {
return batchSize;
}
/**
* @param batchSize The batchSize of this container
*/
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
/**
* The doReceiveAndExecute() method has to be overriden to support multiple-message receives.
*/
@Override
protected boolean doReceiveAndExecute(Object invoker, Session session, MessageConsumer consumer,
TransactionStatus status) throws JMSException {
Connection conToClose = null;
MessageConsumer consumerToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = session;
MessageConsumer consumerToUse = consumer;
if (sessionToUse == null) {
Connection conToUse = null;
if (sharedConnectionEnabled()) {
conToUse = getSharedConnection();
}
else {
conToUse = createConnection();
conToClose = conToUse;
conToUse.start();
}
sessionToUse = createSession(conToUse);
sessionToClose = sessionToUse;
}
if (consumerToUse == null) {
consumerToUse = createListenerConsumer(sessionToUse);
consumerToClose = consumerToUse;
}
List<Message> messages = new ArrayList<Message>();
int count = 0;
Message message = null;
// Attempt to receive messages with the consumer
do {
message = receiveMessage(consumerToUse);
if (message != null) {
messages.add(message);
}
}
// Exit loop if no message was received in the time out specified, or
// if the max batch size was met
while ((message != null) && (++count < batchSize));
if (messages.size() > 0) {
// Only if messages were collected, notify the listener to consume the same.
try {
doExecuteListener(sessionToUse, messages);
sessionToUse.commit();
}
catch (Throwable ex) {
handleListenerException(ex);
if (ex instanceof JMSException) {
throw (JMSException) ex;
}
}
return true;
}
// No message was received for the period of the timeout, return false.
noMessageReceived(invoker, sessionToUse);
return false;
}
finally {
JmsUtils.closeMessageConsumer(consumerToClose);
JmsUtils.closeSession(sessionToClose);
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true);
}
}
protected void doExecuteListener(Session session, List<Message> messages) throws JMSException {
if (!isAcceptMessagesWhileStopping() && !isRunning()) {
if (logger.isWarnEnabled()) {
logger.warn("Rejecting received messages because of the listener container "
+ "having been stopped in the meantime: " + messages);
}
rollbackIfNecessary(session);
throw new JMSException("Rejecting received messages as listener container is stopping");
}
@SuppressWarnings("unchecked")
SessionAwareBatchMessageListener<Message> lsnr = (SessionAwareBatchMessageListener<Message>) getMessageListener();
try {
lsnr.onMessages(session, messages);
}
catch (JMSException ex) {
rollbackOnExceptionIfNecessary(session, ex);
throw ex;
}
catch (RuntimeException ex) {
rollbackOnExceptionIfNecessary(session, ex);
throw ex;
}
catch (Error err) {
rollbackOnExceptionIfNecessary(session, err);
throw err;
}
}
@Override
protected void checkMessageListener(Object messageListener) {
if (!(messageListener instanceof SessionAwareBatchMessageListener<?>)) {
throw new IllegalArgumentException("Message listener needs to be of type ["
+ SessionAwareBatchMessageListener.class.getName() + "]");
}
}
@Override
protected void validateConfiguration() {
if (batchSize <= 0) {
throw new IllegalArgumentException("Property batchSize must be a value greater than 0");
}
}
public void setSessionTransacted(boolean transacted) {
if (!transacted) {
throw new IllegalArgumentException("Batch Listener requires a transacted Session");
}
super.setSessionTransacted(transacted);
}
}
SessionAwareBatchMessageListener:
package com.mypackage;
import java.util.List;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
public interface SessionAwareBatchMessageListener<M extends Message> {
/**
* Perform a batch action with the provided list of {@code messages}.
*
* @param session JMS {@code Session} that received the messages
* @param messages List of messages
* @throws JMSException JMSException thrown if there is an error performing the operation.
*/
public void onMessages(Session session, List<M> messages) throws JMSException;
}
applicationContext.xml中的Bean:
<bean id="myMessageListener" class="org.mypackage.MyMessageListener">
<bean id="jmsContainer" class="com.mypackage.BatchMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationName" ref="queue"/>
<property name="messageListener" ref="myMessageListener"/>
<property name ="concurrentConsumers" value ="10"/>
<property name ="maxConcurrentConsumers" value ="50"/>
</bean>
MyMessageListner:
package org.mypackage;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.mypackage.service.MyService;
public class MyMessageListener implements SessionAwareBatchMessageListener<TextMessage> {
@Autowired
private MyService myService;
@Override
public void onMessage(Session session, List<TextMessage> messages) {
try {
for(TextMessage tm :messages) {
TextMessage textMessage = (TextMessage) message;
// parse the message and add to list
}
//process list of Objects to DB
} catch (JMSException e1) {
e1.printStackTrace();
}
}
}
我认为在将消息发送给消费者之前花费的时间是由你的while循环引起的,因为你每次等待列表都要等待,但是这个只是由当前线程填充,因为它是在doReceiveAndExecute方法中创建的!
// Exit loop if no message was received in the time out specified, or // if the max batch size was met while ((message != null) && (++count < batchSize));
也许这可以做得很好:
...
List<Message> messages = Collections.synchronizedList(new ArrayList<Message>());
@Override
protected boolean doReceiveAndExecute(Object invoker, Session session, MessageConsumer consumer,
TransactionStatus status) throws JMSException {
Connection conToClose = null;
MessageConsumer consumerToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = session;
MessageConsumer consumerToUse = consumer;
if (sessionToUse == null) {
Connection conToUse = null;
if (sharedConnectionEnabled()) {
conToUse = getSharedConnection();
}
else {
conToUse = createConnection();
conToClose = conToUse;
conToUse.start();
}
sessionToUse = createSession(conToUse);
sessionToClose = sessionToUse;
}
if (consumerToUse == null) {
consumerToUse = createListenerConsumer(sessionToUse);
consumerToClose = consumerToUse;
}
Message message = null;
// Attempt to receive messages with the consumer
do {
message = receiveMessage(consumerToUse);
if (message != null) {
messages.add(message);
}
}
if (messages.size() >= batchSize)) {
synchronized (messages) {
// Only if messages were collected, notify the listener to consume the same.
try {
doExecuteListener(sessionToUse, messages);
sessionToUse.commit();
// clear the list!!
messages.clear();
}
catch (Throwable ex) {
handleListenerException(ex);
if (ex instanceof JMSException) {
throw (JMSException) ex;
}
}
}
return true;
}
// No message was received for the period of the timeout, return false.
noMessageReceived(invoker, sessionToUse);
return false;
}
finally {
JmsUtils.closeMessageConsumer(consumerToClose);
JmsUtils.closeSession(sessionToClose);
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true);
}
}