Ошибка при обновлении Spring-AMQP с 1.2.0 до 1.3.0

1

Наше приложение построено и работает: -
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
rabbitmq

1 ответ

0
Лучший ответ

Хорошая добыча!

Я просто сейчас работаю над этим кодом Spring AMQP 1.4.

Не могли бы вы поделиться:

  1. Конфигурация для ListenerContainer на которой вы вешаете
  2. Анализ отладки для кода из этого 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>.

  • 0
    Пожалуйста, также сделайте дамп потока, когда он находится в состоянии «завис». Также, пожалуйста, не забудьте использовать последнюю версию в релизе - последняя версия 1.3.x - 1.3.6; Начиная с версии 1.3.0 было исправлено несколько ошибок. Меня интересует, что привело вас к версии 1.3.0, а не к текущей версии. Страница проекта четко показывает, что актуально.
  • 0
    Просто чтобы объяснить дальше; основной поток удерживает блокировку в контексте приложения, которая нужна контейнеру во время start() . Никогда не следует выполнять какие-либо реальные действия (например, запускать контейнеры) во время инициализации компонента (например, в FactoryBean.getObject() ). По определению контекст приложения все еще создается. Тебе просто повезло, что ты до сих пор не сталкивался с подобными проблемами.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню