Průvodce odstraňováním potíží s Spring JMS

Tento článek popisuje, jak řešit známé problémy a běžné chyby při používání Spring JMS. Článek také odpovídá na některé časté otázky k spring-cloud-azure-starter-servicebus-jms.

Problémy s připojením

MessageProducer byl zavřen kvůli neopravitelné chybě.

Popis problému

Při použití JmsTemplate k odesílání zpráv se JmsTemplate během intervalu nečinnosti mezi 10 až 15 minutou stane nedostupný. Odesílání zpráv v tomto intervalu může získat výjimky uvedené v následujícím příkladu výstupu:

2022-11-06 11:12:05.762  INFO 25944 --- [   scheduling-1] c.e.demo.ServiceBusJMSMessageProducer    : Sending message: 2022-11-06T11:12:05.762072 message 1
2022-11-06 11:12:05.772 ERROR 25944 --- [   scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler    : Unexpected error occurred in scheduled task

org.springframework.jms.IllegalStateException: The MessageProducer was closed due to an unrecoverable error.; nested exception is javax.jms.IllegalStateException: The MessageProducer was closed due to an unrecoverable error.
    at org.springframework.jms.support.JmsUtils.convertJmsAccessException(JmsUtils.java:274) ~[spring-jms-5.3.23.jar:5.3.23]
  ...
Caused by: org.apache.qpid.jms.provider.ProviderException: The link 'G0:36906660:qpid-jms:sender:azure:5caf3ef4-9602-413c-964d-cf1292d6e1f5:1:1:1:t4' is force detached. Code: publisher(link376). Details: AmqpMessagePublisher.IdleTimerExpired: Idle timeout: 00:10:00. [condition = amqp:link:detach-forced]
    at org.apache.qpid.jms.provider.amqp.AmqpSupport.convertToNonFatalException(AmqpSupport.java:181) ~[qpid-jms-client-0.53.0.jar:na]
  ...

Analýza příčin

K výjimkám dochází u služby Azure Service Bus, když jsou připojení a propojení AMQP aktivní, ale nejsou k dispozici žádná volání – například odesílání nebo přijímání volání – pomocí odkazu po dobu 10 minut. V tomto případě je propojení uzavřeno. A když byly všechny odkazy v připojení uzavřeny, protože nedošlo k žádné aktivitě (nečinné) a za 5 minut se nevytvořil nový odkaz, připojení se zavře.

Pro úvodní sadu Service Bus JMS se ve výchozím nastavení používá CachingConnectionFactory, která ukládá relaci, producenta a příjemce do mezipaměti. Pokud je JmsProducer nečinný déle než 10 minut, ale méně než 15, propojení, které producent v mezipaměti zabírá, bylo uzavřeno. Během tohoto intervalu nelze odesílat zprávy. Po dalších 5 minutách nečinnosti se celé připojení zavře. Proto jakákoli operace odesílání po 15minutovém intervalu nečinnosti způsobí, že CachingConnectionFactory vytvoří nové připojení k odeslání. Operace odesílání bude k dispozici po 15 minutách.

Alternativní řešení

V současné době starter nabízí alternativní řešení problému s odpojením propojení použitím JmsPoolConnectionFactory, který fondy Connection, Sessiona MessageProducera spravuje životní cyklus instancí ve fondu. Toto alternativní řešení může zajistit vyřazení producenta po nedostupnosti, a proto se všechny odesílající operace provádějí u aktivních výrobců.

Pokud chcete použít toto alternativní řešení, přidejte následující konfiguraci:

spring:
  jms:
    servicebus:
      pool:
        enabled: true
        max-connections: ${your-expected-max-connection-value}

Využití spring.jms.servicebus.idle-timeout

Vlastnosti časového limitu nečinnosti konfiguruje časový limit nečinnosti připojení AMQP. Specifikace AMQP obsahuje následující popis:

Připojení podléhají prahové hodnotě časového limitu nečinnosti. Časový limit aktivuje místní partnerský vztah, pokud po překročení prahové hodnoty nejsou přijaty žádné rámce. Časový limit nečinnosti se měří v milisekundách a začíná od okamžiku přijetí posledního rámce. Pokud dojde k překročení prahové hodnoty, partnerský vztah by se měl pokusit připojení elegantně zavřít pomocí zavření rámce s chybou vysvětlující proč. Pokud vzdálený partner nereaguje elegantně v rámci prahové hodnoty, pak partnerský vztah MŮŽE zavřít soket TCP.

Pokud pro klienta JMS nakonfigurujete tuto vlastnost, řídíte na straně serveru, jak dlouho očekáváte, že server odešle prázdný rámec, aby zůstal připojení aktivní, když nejsou doručeny žádné zprávy. Tato vlastnost řídí chování vzdáleného partnerského uzlu a každý partnerský vztah může mít svou vlastní izolovanou hodnotu.

Problémy s JmsTemplate

Naplánované zprávy

Azure Service Bus podporuje zpožděné zpracování zpráv. Další informace naleznete v části Naplánované zprávypořadí zpráv a časových razítek. V případě JMS nastavte vlastnost ScheduledEnqueueTimeUtc pomocí záhlaví poznámek zprávy x-opt-scheduled-enqueue-time.

Problémy s JmsListener

Do služby Service Bus se odesílá příliš mnoho požadavků, i když na serveru nejsou žádné zprávy.

Popis problému

Při použití rozhraní API @JmsListener se v některých případech můžete na webu Azure Portal podívat, že příchozí požadavky odeslané do fronty nebo témat stále probíhají, i když na serveru nejsou žádné zprávy, které by bylo možné přijímat.

Analýza příčin

@JmsListener je naslouchací proces dotazování, který je vytvořený pro opakované pokusy o dotazování.

Naslouchací proces se nachází na probíhající smyčce dotazování. Každá smyčka volá metodu JMS MessageConsumer.receive() k dotazování místního příjemce, aby zprávy spotřebovaly. Ve výchozím nastavení místní příjemce pro každou operaci dotazování odesílá žádosti o přijetí změn zprostředkovateli zpráv, aby požádal o zprávy a po určitou dobu blokuje. Konkrétní proces dotazování je určen několika vlastnostmi, včetně receiveTimeout, prefetchSizea receiveLocalOnly nebo receiveNoWaitLocalOnly. Metoda receiveNoWaitLocalOnly se používá pouze při nastavení receiveTimeout na zápornou hodnotu.

Pokud k tomuto problému dojde v aplikaci, zkontrolujte následující nastavení konfigurace:

  • Určete, jestli je vaše zásada předběžného načtení 0, což je také výchozí možnost. 0-prefetch znamená příjemce přijetí změn, který odesílá žádosti o přijetí změn do služby Service Bus pro každé hlasování.

  • Pokud jste nakonfigurovali nenulový prefetch, určete, jestli je vlastnost receiveLocalOnly nebo receiveNoWaitLocalOnly nastavená na false, což je výchozí možnost. Hodnota false tady stále vede k odesílání žádostí o přijetí změn na server, protože se necyklické dotazování pouze místního příjemce.

  • Konfigurace receiveTimeout určuje, jak dlouho blokuje pro každou žádost o přijetí změn, aby mohla ovlivnit frekvenci žádostí o přijetí změn odesílaných na server. Výchozí hodnota je 1 sekunda.

Úplnou analýzu najdete v diskuzi o problému GitHubu.

Řešení

Následující části popisují dvě řešení tohoto problému.

Řešení 1. Změna pouze pro nabízení příjemců a místní kontrolu

Změnou režimu na pushse příjemce stane asynchronním oznámením příjemce, který nepřetahuje zprávy od zprostředkovatele, ale udržuje cílovou částku kreditu propojení. Částka je určena předem načtenou vlastností. S tím, jak služba Service Bus (odesílatel) odesílá zprávy, sníží se kredit odkazu odesílatele a když odkazový kredit odesílatele klesne pod prahovou hodnotu, klient (příjemce) odešle požadavek na server, aby zvýšil odkaz-kredit odesílatele zpět na požadovanou cílovou částku.

Pokud chcete toto řešení provést, přidejte následující konfiguraci:

Nejprve nakonfigurujte číslo prefetch jako nenulu, které konfiguruje příjemce jako nenulu. Následující tabulka uvádí několik předem načtených vlastností, z nichž každá řídí různé entity služby Service Bus. Nastavte vlastnosti, které platí pro váš případ.

Vlastnost Popis
spring.jms.servicebus.prefetch.all Záložní hodnota pro možnost předběžného načtení v tomto oboru názvů služby Service Bus
spring.jms.servicebus.prefetch.queue-prefetch Číslo předběžného načtení fronty.
spring.jms.servicebus.prefetch.queue-browser-prefetch Číslo předběžného načtení pro prohlížeč front.
spring.jms.servicebus.prefetch.topic-prefetch Číslo předběžného načtení tématu.
spring.jms.servicebus.prefetch.durable-topic-prefetch Číslo předběžného načtení pro trvalé téma.

Za druhé nakonfigurujte non-local-check přidáním třídy konfigurace pro úpravce továrny, jak je znázorněno v následujícím příkladu:

@Configuration(proxyBeanMethods = false)
public class CustomJmsConfiguration {

    @Bean
    ServiceBusJmsConnectionFactoryCustomizer customizer() {
        return factory -> {
            factory.setReceiveLocalOnly(true);
            // Configure the below ReceiveNoWaitLocalOnly instead if you have specified the property 
            // spring.jms.listener.receive-timeout with negative value. Otherwise, configure the above `ReceiveLocalOnly`.
            //factory.setReceiveNoWaitLocalOnly(true);
        };
    }
}

Hodnota předběžného načtení může ovlivnit, jak rychlé zprávy se odesílají do místní vyrovnávací paměti příjemce. Hodnotu byste měli upravit podle vašeho náročného výkonu a svazků zpráv. Vhodná hodnota může urychlit proces využívání, zatímco hodnota, která je příliš velká, může způsobit, že zprávy v místní vyrovnávací paměti budou zastaralé a znovu odeslány. U malých objemů zpráv, kdy zpracování každé zprávy trvá dlouhou dobu, nastavte předběžné načtení na 1. Tato hodnota zajišťuje, že příjemce zpracovává pouze jednu zprávu najednou.

Řešení 2. Zvýšení časového limitu příjmu za účelem snížení frekvence přijetí změn

Vlastnost časového limitu příjmu určuje strategii, jak dlouho budou bloky příjemce čekat na výsledek přijetí změn. Prodloužením časového limitu tedy můžete snížit frekvenci přijetí změn a snížit počet žádostí o přijetí změn, když zvolíte režim přijetí změn. Vextrémních V takovém případě, pokud na serveru nejsou žádné zprávy, zablokuje čekání.

Chcete-li toto řešení provést, nakonfigurujte vlastnost spring.jms.listener.receive-timeout. Tato vlastnost je typu java.time.Duration a má výchozí hodnotu 1 sekundu. Následující seznam vysvětluje účinek různých hodnot:

  • Nastavení časového limitu příjmu na 0 znamená, že bloky vyžádané replikace po neomezenou dobu, dokud nebude odeslána zpráva.
  • Nastavení časového limitu příjmu na kladnou hodnotu znamená, že pull blokuje až do časového limitu.
  • Nastavení časového limitu příjmu na zápornou hodnotu znamená, že přijetí bez čekání je přijetí bez čekání, což znamená, že okamžitě vrátí zprávu nebo null, pokud nejsou k dispozici žádné zprávy.

Poznámka

Vysoká hodnota časového limitu může přinést některé vedlejší účinky. Například vysoká hodnota časového limitu také prodlouží dobu, po kterou je hlavní vlákno ve stavu bloku. Tento stav znamená, že kontejner bude méně reagovat na stop() volání a může se zastavit pouze mezi receive() voláními.

Kontejner může také odesílat požadavky pouze po uplynutí receive-timeout intervalu. Pokud je interval delší než 10 minut, Service Bus propojení zavře a zabrání naslouchacímu procesu v odesílání nebo přijímání. Další informace najdete v části o chybách AMQP ve službě Azure Service Bus. Ve výchozím nastavení naslouchací proces používá CacheConnectionFactory.

Pokud potřebujete vysoký časový limit příjmu, nezapomeňte použít JmsPoolConnectionFactory.

Další informace o problému zavření odkazu a o tom, jak používat JmsPoolConnectionFactory, najdete v MessageProducer byl zavřen kvůli neopravitelné chybě oddílu.

Problém s předběžným načtením

Popis problému

Nevhodná zásada předběžného načtení může způsobit následující problémy:

  • Stejné zprávy se opakovaně spotřebovávají.
  • Zprávy se za MaxDeliveryCountExceededzařadí do fronty nedoručených zpráv, a to i v případě, že se zprávy zpracovávají bez chyby nebo výjimky.

Analýza příčin

K tomuto problému obvykle dochází v případě prefetch , že je hodnota vyšší než skutečná spotřeba kapacity, což má za následek, že do místní vyrovnávací paměti se předčítá příliš mnoho zpráv, které čekají na využití. Předem načtené zprávy se ale zobrazují jako odeslané v peek-lock režimu ze strany služby Service Bus. Každá odesílaná zpráva má atributy max-delivery-count a lock-duration atributy. V režimu příjmu peek-lock se zprávy načtené do vyrovnávací paměti předběžného načtení získávají do vyrovnávací paměti v uzamčeném stavu s hodinami časového limitu pro uzamčení. Pokud je vyrovnávací paměť předběžného načtení velká a zpracování trvá tak dlouho, že vyprší platnost uzamčení zpráv při zachování vyrovnávací paměti před načtením, bude zpráva považována za opuštěná a opět je k dispozici pro načtení z fronty.

Tento problém může způsobit, že se zpráva načte do vyrovnávací paměti předběžného načtení a umístí se na konec. Pokud se před vypršením platnosti zprávy nezpracuje vyrovnávací paměť předběžného načtení, zprávy se opakovaně načítají, ale nikdy se nedoručují v použitelném (platném uzamčeném) stavu. Když jsou tyto zastaralé kopie vyřazené z fronty, aplikace pak opakovaně spotřebovává stejnou zprávu a nemůže je dokončit. V jiném případě jsou všechny opakované zprávy před spotřebou ve vyrovnávací paměti vypršeny. V takovém případě se zpráva ve službě Service Bus nakonec přesune do fronty nedoručených zpráv po překročení maximálního počtu doručení.

Další informace najdete v tématu Proč není výchozí možnost Prefetch? oddílu Předběžné načtení zpráv služby Azure Service Bus.

Řešení

Při konfiguraci předběžného načtení buďte opatrní, abyste měli jistotu, že odpovídá spotřebě funkcí. Musíte vyvážit maximální počet předběžného načtení a dobu trvání uzamčení nakonfigurovanou ve frontě nebo předplatném tak, aby časový limit uzamčení alespoň překročil kumulativní očekávanou dobu zpracování zpráv pro maximální velikost vyrovnávací paměti předběžného načtení a jednu zprávu. Současně by časový limit uzamčení neměl být tak dlouhý, aby zprávy mohly překročit maximální dobu, po které se omylem vyřadí, a tím by před opětovným nasazením vyžadoval vypršení platnosti zámku.

Chcete-li nakonfigurovat atribut předběžného načtení, který má výchozí hodnotu nula, použijte jednu z následujících vlastností:

Vlastnost Popis
spring.jms.servicebus.prefetch.all Záložní hodnota pro možnost předběžného načtení v tomto oboru názvů služby Service Bus.
spring.jms.servicebus.prefetch.queue-prefetch Číslo předběžného načtení fronty.
spring.jms.servicebus.prefetch.queue-browser-prefetch Číslo předběžného načtení pro prohlížeč front.
spring.jms.servicebus.prefetch.topic-prefetch Číslo předběžného načtení tématu.
spring.jms.servicebus.prefetch.durable-topic-prefetch Číslo předběžného načtení pro trvalé téma.

Jak provést dispoziční charakter AMQP pro Service Bus?

JMS podporuje pět typů dispozičních typů AMQP při potvrzení zpráv zprostředkovateli zasílání zpráv. Podporované hodnoty jsou ACCEPTED, REJECTED, RELEASED, MODIFIED_FAILEDa MODIFIED_FAILED_UNDELIVERABLE. Další informace najdete v a mapování operací AMQP a mapování operací služby Service Bus části Použití služby Java Message Service 1.1 se standardem Azure Service Bus aAMQP 1.0 .

Pokud tedy chcete zprávu ručně dokončit, opustit, odložit, odložit nebo uvolnit zprávu pomocí JmsListener, postupujte následovně:

  1. Zakažte transakce relace a použijte režim KLIENT ack.

    Chcete-li provést tuto úlohu, deklarujte vlastní JmsListenerContainerFactory bean a potom nastavte vlastnosti, nebo proveďte zpracování definované v počátečním. Následující příklad používá přístup k deklarování jiného beanu:

    @Configuration(proxyBeanMethods = false)
    public class CustomJmsConfiguration {
    
        @Bean
        public JmsListenerContainerFactory<?> customQueueJmsListenerContainerFactory(
                DefaultJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
            DefaultJmsListenerContainerFactory jmsListenerContainerFactory = new DefaultJmsListenerContainerFactory();
            configurer.configure(jmsListenerContainerFactory, connectionFactory);
            jmsListenerContainerFactory.setPubSubDomain(Boolean.FALSE);
            jmsListenerContainerFactory.setSessionTransacted(Boolean.FALSE);
            jmsListenerContainerFactory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
            return jmsListenerContainerFactory;
        }
    }
    
  2. V obslužné rutině zprávy explicitně dokončete nebo opusťte zprávy.

    @JmsListener(destination = "QUEUE_NAME", containerFactory = "customQueueJmsListenerContainerFactory")
    public void receiveMessage(JmsTextMessage message) throws Exception {
        String event = message.getBody(String.class);
        try {
            logger.info("Received event: {}", event);
            logger.info("Received message: {}", message);
            // by default complete the message
            message.acknowledge();
        } catch (Exception e) {
            logger.error("Exception while processing re-source event: " + event, e);
            JmsAcknowledgeCallback acknowledgeCallback = message.getAcknowledgeCallback();
            // explicitly abandon the message
            acknowledgeCallback.setAckType(MODIFIED_FAILED);
            message.setAcknowledgeCallback(acknowledgeCallback);
            message.acknowledge();
            throw e;
        }
    }
    

Problémy s konfigurací

Zakázání automatické konfigurace JMS služby Service Bus

Popis problému

Někteří uživatelé naimportují sadu Spring Cloud Azure Starter pro automatickou konfiguraci jiné služby Azure než Service Bus JMS. Používají také architekturu Spring JMS bez nutnosti použití JMS služby Service Bus. Když se aplikace pokusí spustit, zobrazí se následující výjimky:

Caused by: java.lang.IllegalArgumentException: 'spring.jms.servicebus.connection-string' should be provided
    at com.azure.spring.cloud.autoconfigure.jms.properties.AzureServiceBusJmsProperties.afterPropertiesSet(AzureServiceBusJmsProperties.java:210)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800)
    ... 98 more

Analýza příčin

K tomuto problému dochází, protože všechny třídy automatické konfigurace Azure Spring Cloudu jsou umístěny do stejného modulu, takže všechny Spring Cloud Azure Starter ve skutečnosti importují všechny tyto automatické konfigurace, což zahrnuje také Service Bus JMS. Když pak aplikace používá rozhraní Spring JMS API, splňuje podmínku automatické konfigurace JMS služby Service Bus a aktivuje ji. Potom pro uživatele, kteří nemají v úmyslu používat spring-cloud-azure-starter-servicebus-jms, podmínky vlastností nejsou splněny, protože neexistuje důvod, proč by mohli nakonfigurovat Service Bus pro JMS. Tato situace způsobí vyvolání výjimek.

Řešení

Spring Cloud Azure for Service Bus JMS poskytuje vlastnost pro zapnutí nebo vypnutí jeho automatické konfigurace. Tuto funkci můžete podle potřeby zakázat pomocí následujícího nastavení vlastnosti:

spring.jms.servicebus.enabled=false

Konfigurace atributů zpráv

Jak nastavit typ obsahu odchozích zpráv?

Chcete-li nakonfigurovat typ obsahu, přizpůsobte převaděč zpráv úpravě atributu typu obsahu při převodu zpráv. Následující kód přebírá jako příklad bajtové zprávy.

Nejprve upravte převaděč zpráv, který se má použít v JmsTemplate, jak je znázorněno v následujícím příkladu:

public class CustomMappingJackson2MessageConverter extends MappingJackson2MessageConverter {

    public static final String CONTENT_TYPE = "application/json";

    public CustomMappingJackson2MessageConverter() {
        this.setTargetType(MessageType.BYTES);
    }

    @Override
    protected BytesMessage mapToBytesMessage(Object object, Session session, ObjectWriter objectWriter)
            throws JMSException, IOException {
        final BytesMessage message = super.mapToBytesMessage(object, session, objectWriter);
        JmsBytesMessage msg = (JmsBytesMessage) message;
        AmqpJmsMessageFacade facade = (AmqpJmsMessageFacade) msg.getFacade();
        facade.setContentType(Symbol.valueOf(CONTENT_TYPE));
        return msg;
    }
}

Potom deklarujte vlastní převaděč zpráv bean, jak je znázorněno v tomto příkladu:

@Configuration(proxyBeanMethods = false)
public class CustomJmsConfiguration {

    @Bean
    public MessageConverter messageConverter() {
        return new CustomMappingJackson2MessageConverter();
    }
}

Jak nastavit název vlastnosti ID typu pro MappingJackson2MessageConverter?

Atribut type-id-property-name umožňuje MappingJackson2MessageConverter určit, kterou třídu použít k deserializaci datové části zprávy. Při serializaci každého objektu Java do datové části Spring Message převaděč ukládá datový typ datové části do vlastnosti zprávy s názvem vlastnosti zaznamenané type-id-property-name. Při deserializaci zprávy pak převaděč přečte ID typu ze zprávy a provede deserializaci.

Chcete-li nastavit type-id-property-name, deklarujte vlastní MappingJackson2MessageConverter bean a nakonfigurujte tuto vlastnost, jak je znázorněno v následujícím příkladu:

@Configuration(proxyBeanMethods = false)
public class CustomJmsConfiguration {

    @Bean
    public MessageConverter jacksonJmsMessageConverter()
    {
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        converter.setTypeIdPropertyName("your-custom-type-id-property-name");
        return converter;
    }
}

Detekce duplicit

Azure Service Bus podporuje detekce duplicit , která používá vlastnost MessageId k jedinečné identifikaci zpráv a zahození duplicit odeslaných do služby Service Bus.

Pro rozhraní API JMS byste ale neměli nastavit ID zprávy JMS, které se považuje za nelegální ve specifikacích JMS. Tato funkce se v současné době nepodporuje pro úvodní sadu Spring Cloud Azure Service Bus JMS.

Další aktualizace této funkce najdete vproblému GitHubu.

Povolení protokolování přenosu AMQP

Další informace najdete v povolení protokolování přenosu AMQP části Řešení potíží se službou Service Bus.

Získání další nápovědy

Další informace o způsobech, jak kontaktovat podporu, najdete v tématu podpora v kořenovém adresáři úložiště.

Zdroje informací pro úvodní sadu Spring Cloud Azure Service Bus JMS

Problémy s vytvářením GitHubu

Při vyplňování problémů GitHubu se požadují následující podrobnosti:

  • Konfigurace služby Service Bus / prostředí oboru názvů
    • Jaká úroveň je obor názvů (Standard nebo Premium)?
    • Jaký typ entity zasílání zpráv se používá (fronta nebo téma)? a jeho konfiguraci.
    • Jaká je průměrná velikost každé zprávy?
  • Jaký je vzor provozu? (to znamená počet zpráv za minutu a to, jestli je klient vždy zaneprázdněný nebo má pomalou dobu provozu.)
  • Repropro code and steps
    • To je důležité, protože často nemůžeme problém reprodukovat v našem prostředí.
  • Polena