Наше приложение построено и работает: -
spring.version = 3.2.8.RELEASE
spring.amqp.version = 1.2.0.RELEASE
rabbitmq.version = 3.1.3 (клиент)
Версия сервера RabbitMQ - 3.1.5
Мы хотели обновить сервер rabbitmq с 3.1.5 до 3.3.5, и мы сделали это успешно.
Теперь мы хотели обновить приложение, чтобы использовать последнюю версию java-клиента Spring-amqp, RabbitMQ, поэтому мы обновили следующие компоненты:
spring.version = 3.2.8.RELEASE
spring.amqp.version = 1.3.0.RELEASE
rabbitmq.version = 3.2.4 (клиент)
Версия сервера RabbitMQ - 3.3.5
Однако после обновления до весны-amqp до 1.3.0 наше приложение начало зависать. В основном мы запускаем множество контейнеров-слушателей во время запуска приложения, и запуск каждого контейнера-слушателя теперь занимает ровно 60 секунд, чтобы разрешить следующий шаг
после глубокого рытья я обнаружил, что программа получает зависание в методе run() в классе SimpleMessageListenerContainer: -
org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer
public void run() {
boolean aborted = false;
int consecutiveIdles = 0;
int consecutiveMessages = 0;
try {
try {
SimpleMessageListenerContainer.this.redeclareElementsIfNecessary(); // Here is where the programing thread hangs.
this.consumer.start();
this.start.countDown();
}
Как уже упоминалось выше, поток зависает в методе redeclareElementsIfNecessary(), и этот метод вводится только в этой версии весеннего кролика. Я не знаю, почему его повесили там, независимо от предлагаемого параметра, который я передаю этому SimpleMessageListenerContainer, похоже, что он не работает.
Если я вернусь к выпуску Spring-amqp 1.2.0 с новым сервером RabbitMQ 3.3.5, все, кажется, работает нормально, но все не работает с новым клиентом spring-amqp.
Я сейчас застрял здесь пару дней. Spring/Rabbitmq мастер, вы можете помочь мне решить эту проблему?
Спасибо за ваш быстрый ответ, однако кажется, что код не доходит до этого момента, и он просто подвешивается чуть выше фрагмента, который вы предоставили, у меня есть комментарий ниже, где код подвешен точно ниже
Set<String> queueNames = this.getQueueNamesAsSet();
Map<String, Queue> queueBeans = ((ListableBeanFactory) applicationContext).getBeansOfType(Queue.class); // The code started to hung here
for (Entry<String, Queue> entry : queueBeans.entrySet()) {
Queue queue = entry.getValue();
if (queueNames.contains(queue.getName()) && queue.isAutoDelete()
&& this.rabbitAdmin.getQueueProperties(queue.getName()) == null) {
if (logger.isDebugEnabled()) {
logger.debug("At least one auto-delete queue is missing: " + queue.getName()
+ "; redeclaring context exchanges, queues, bindings.");
}
this.rabbitAdmin.initialize();
break;
}
}
На самом деле мы обновили до последней версии только Spring-amqp, то есть
spring.version = 3.2.8.RELEASE
spring.amqp.version = 1.3.6.RELEASE
rabbitmq.version = 3.3.4 (client)
RabbitMQ Server version is 3.3.5
однако мы столкнулись с одной и той же проблемой, поэтому, чтобы узнать, из какой версии проблема началась, я спустился к более низким версиям до 1.3.0, похоже, проблема начинается с версии 1.0.0 Spring-amqp. что причина.
Я приложил запрошенную информацию, включая дампы потоков, которые основаны только на весеннем amqp 1.3.6.
Вот конфигурация нашего контейнера-слушателя, где зависают программы, так как вы можете видеть, что у нас есть собственный SimpleMessageLinstenerContainer, который действует как оболочка для acutal spring SimpleMessageListenerContainer, я также привязал этот файл обертки для вашей справки.
<bean id="tlogOutOfCycleMessageListenerPrototype" class="com.myorg.ips.cnccommon.support.amqp.SimpleMessageListenerContainer" scope="prototype">
<property name="channelTransacted" value="true" />
<property name="transactionManager" ref="transactionManager" />
<property name="concurrentConsumers" value="1" />
<property name="taskExecutor" ref="tlogOutOfCycleMessageListenerPool" />
<property name="messageListener" ref="tlogMLAOutOfCycle" />
<property name="errorHandler" ref="tlogOutOfCycleMessageHandler" />
<property name="autoStartup" value="false" />
<property name="instanceNameForLogging" value="site1TlogOutOfCycleMessageListener"/>
<!-- A dummy connection factory which will never be used -->
<property name="connectionFactory" ref="switchCompositeConnectionFactoryPrototype"/>
</bean>
Наш класс оболочки SimpleMessageListenerContainer.java
package com.myorg.ips.cnccommon.support.amqp;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.slf4j.cal10n.LocLogger;
import org.springframework.util.ErrorHandler;
import com.myorg.ips.amqp.SwitchSiteSupport;
import com.myorg.ips.logging.LoggerFactory;
import com.myorg.ips.system.config.InitialisableSiteAware;
import static com.myorg.ips.logging.SystemWideLogMessages.ERROR_AMQP_FAILED_TO_START_LISTENER;
import static com.myorg.ips.logging.SystemWideLogMessages.INFO_AMQP_STOPPING_LISTENER;
/**
*
* Wrapper for the Spring SimpleMessageListenerContainer which simply allows us to delay (or prevent startup). Can also restart on command.
*
*/
public class SimpleMessageListenerContainer extends org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer implements InitialisableSiteAware {
private static final LocLogger logger = LoggerFactory.getLogger(SimpleMessageListenerContainer.class);
private boolean autoStart = true;
private ErrorHandler exposedErrorHandler;
private boolean springBeanInitialisationAttempted = false;
private boolean springBeanInitialised = false;
private String instanceNameForLogging;
@Override
public void initialize() {
// Do nothing -- we will instead perform the Spring bean initialisation later on via the factory bean, after the connection factory has been set
springBeanInitialisationAttempted = true;
springBeanInitialised = false;
}
@Override
public void initialise() {
SwitchSiteSupport.initialiseIfSiteAware(getMessageListener());
SwitchSiteSupport.initialiseIfSiteAware(getErrorHandler());
// If this object is a Spring bean, we should now complete the initialisation that the Spring framework attempted earlier
if (springBeanInitialisationAttempted && !springBeanInitialised) {
springBeanInitialised = true;
super.initialize();
if (isAutoStartup()) {
start();
}
}
}
@Override
public void configureForSite(final MultiHostConnectionFactory configuredConnectionFactory) {
setConnectionFactory(configuredConnectionFactory);
SwitchSiteSupport.configureIfSiteAware(getMessageListener(), configuredConnectionFactory);
SwitchSiteSupport.configureIfSiteAware(getErrorHandler(), configuredConnectionFactory);
setInstanceNameForLogging(SwitchSiteSupport.replaceWithSiteAlias(instanceNameForLogging, configuredConnectionFactory));
}
@Override
//CHECKSTYLE:OFF Unfortunately the parent springframework class throws and exception, so so do we
protected void doStart() throws Exception {
//CHECKSTYLE:ON
if (autoStart) {
logger.debug("Starting message listener " + instanceNameForLogging);
super.doStart();
logger.debug("Started message listener " + instanceNameForLogging);
}
}
/**
* Start this listener
*/
public void start() {
autoStart = true;
try {
doStart();
//CHECKSTYLE:OFF Unfortunately the parent springframework class throws and exception, so that is what we catch
} catch (Exception e) {
//CHECKSTYLE:ON
logger.error(ERROR_AMQP_FAILED_TO_START_LISTENER, e);
}
}
/**
* Stop listener
*/
public void stop() {
logger.info(INFO_AMQP_STOPPING_LISTENER, getBeanName());
autoStart = false;
doStop();
}
/**
* Stop and start this listener
*/
public void restart() {
stop();
start();
}
/**
* Store the errorHandler in a subclass-specific property so that we can retrieve it later
* @param errorHandler errorHandler
*/
@Override
public void setErrorHandler(final ErrorHandler errorHandler) {
this.exposedErrorHandler = errorHandler;
super.setErrorHandler(errorHandler);
}
/**
* Return the exposed errorHandler
* @return errorHandler
*/
public ErrorHandler getErrorHandler() {
return exposedErrorHandler;
}
public void setInstanceNameForLogging(final String instanceNameForLogging) {
this.instanceNameForLogging = instanceNameForLogging;
}
@Override
public String toString(){
return ToStringBuilder.reflectionToString(this);
}
}
Хорошая добыча!
Я просто сейчас работаю над этим кодом Spring AMQP 1.4.
Не могли бы вы поделиться:
ListenerContainer
на которой вы вешаетеredeclareElementsIfNecessary()
На самом деле теперь этот код выглядит так:
if (queueNames.contains(queue.getName()) && queue.isAutoDelete()
&& this.rabbitAdmin.getQueueProperties(queue.getName()) == null) {
if (logger.isDebugEnabled()) {
logger.debug("At least one auto-delete queue is missing: " + queue.getName()
+ "; redeclaring context exchanges, queues, bindings.");
}
this.rabbitAdmin.initialize();
break;
}
Таким образом, он может использоваться только в очереди auto-delete
.
Или у вас есть другая фотография?..
ОБНОВИТЬ
Согласно вашему ThreadDump. Это незаконно:
at com.vocalink.ips.amqp.AmqpMessageListenerManager.initialise(AmqpMessageListenerManager.java:106)
at com.vocalink.ips.amqp.SwitchSiteSupport.initialiseIfSiteAware(SwitchSiteSupport.java:29)
at com.vocalink.ips.system.config.AbstractSiteAwareComponentCachingFactory.createAndConfigureSiteAwareComponent(AbstractSiteAwareComponentCachingFactory.java:51)
Вы не можете start
компонент на этапе инициализации. Или оставить его в контейнере или просто start
вручную где-нибудь во время выполнения, когда все ваши компоненты уже созданы.
Например, вы можете сделать это с помощью ApplicationListener<ContextRefreshedEvent>
.
start()
. Никогда не следует выполнять какие-либо реальные действия (например, запускать контейнеры) во время инициализации компонента (например, вFactoryBean.getObject()
). По определению контекст приложения все еще создается. Тебе просто повезло, что ты до сих пор не сталкивался с подобными проблемами.