Partager via


Guide de résolution des problèmes spring JMS

Cet article explique comment résoudre les problèmes connus et les erreurs courantes lors de l’utilisation de Spring JMS. L’article répond également à certaines questions fréquemment posées pour spring-cloud-azure-starter-servicebus-jms.

Problèmes de connectivité

MessageProducer a été fermé en raison d’une erreur irrécupérable

Description du problème

Lorsque vous utilisez JmsTemplate pour envoyer des messages, JmsTemplate devient indisponible pendant un intervalle d’inactivité compris entre 10 et 15 minutes. L’envoi de messages dans cet intervalle peut obtenir les exceptions indiquées dans l’exemple de sortie suivant :

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]
  ...

Analyse de la cause

Les exceptions se produisent pour Azure Service Bus lorsque la connexion AMQP et le lien sont actifs, mais qu’aucun appel ( par exemple, envoyer ou recevoir des appels) est effectué à l’aide du lien pendant 10 minutes. Dans ce cas, le lien est fermé. Et quand tous les liens de la connexion ont été fermés, car il n’y a pas eu d’activité (inactif) et qu’un nouveau lien n’a pas été créé en 5 minutes, la connexion est fermée.

Pour le démarrage JMS Service Bus, le CachingConnectionFactory est utilisé par défaut, qui met en cache la session, le producteur et le consommateur. Lorsque le JmsProducer est inactif pendant plus de 10 minutes, mais moins de 15, le lien que le producteur mis en cache occupe a été fermé. Les messages ne peuvent pas être envoyés pendant cet intervalle. Ensuite, après 5 minutes d’inactivité, toute la connexion est fermée. Ainsi, toute opération d’envoi après l’intervalle d’inactivité de 15 minutes entraîne l’envoi de la CachingConnectionFactory pour créer une connexion. L’opération d’envoi devient disponible après 15 minutes.

Solution de contournement

Actuellement, le démarrage fournit une solution de contournement pour le problème de détachement de lien en appliquant le JmsPoolConnectionFactory, les pools Connection, Sessionet MessageProducer, et gère le cycle de vie des instances mises en pool. Cette solution de contournement peut s’assurer qu’un producteur est supprimé après avoir été indisponible et, par conséquent, toutes les opérations d’envoi sont effectuées sur les producteurs actifs.

Pour utiliser cette solution de contournement, ajoutez la configuration suivante :

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

Utilisation de spring.jms.servicebus.idle-timeout

Les propriétés de délai d’inactivité configurent le délai d’inactivité d’une connexion AMQP. La spécification AMQP fournit la description suivante :

Les connexions sont soumises à un seuil de délai d’inactivité. Le délai d’expiration est déclenché par un homologue local lorsqu’aucune trame n’est reçue après qu’une valeur de seuil est dépassée. Le délai d’inactivité est mesuré en millisecondes et commence à partir du moment où la dernière image est reçue. Si le seuil est dépassé, un homologue DOIT essayer de fermer correctement la connexion à l’aide d’une trame de fermeture avec une erreur expliquant pourquoi. Si l’homologue distant ne répond pas correctement dans un seuil, l’homologue PEUT fermer le socket TCP.

Pour un client JMS, lorsque vous configurez cette propriété, vous contrôlez côté serveur combien de temps vous attendez que le serveur envoie une trame vide pour conserver une connexion active quand aucun message n’est remis. Cette propriété contrôle le comportement de l’homologue distant, et chaque homologue peut avoir sa propre valeur isolée.

Problèmes JmsTemplate

Messages planifiés

Azure Service Bus prend en charge le traitement différé des messages. Pour plus d’informations, consultez la section Messages planifiés de séquencement de messages et horodatages. Pour JMS, pour planifier un message, définissez la propriété ScheduledEnqueueTimeUtc à l’aide de l’en-tête d’annotation de message x-opt-scheduled-enqueue-time.

Problèmes JmsListener

Trop de demandes sont envoyées à Service Bus même s’il n’y a pas de messages sur le serveur

Description du problème

Lorsque vous utilisez l’API @JmsListener, dans certains cas, vous pouvez voir dans le portail Azure qu’il existe des valeurs en cours pour les demandes entrantes envoyées à leur file d’attente ou rubriques, même s’il n’y a aucun message sur le serveur à recevoir.

Analyse de la cause

@JmsListener est un écouteur d’interrogation, qui est généré pour les tentatives d’interrogation répétées.

L’écouteur se trouve sur une boucle d’interrogation en cours. Chaque boucle appelle la méthode JMS MessageConsumer.receive() pour interroger le consommateur local pour les messages à consommer. Par défaut, pour chaque opération de sondage, le consommateur local envoie des demandes de tirage au répartiteur de messages pour demander des messages, puis bloque pendant une certaine période de temps. Le processus d’interrogation concret est décidé par plusieurs propriétés, notamment receiveTimeout, prefetchSizeet receiveLocalOnly ou receiveNoWaitLocalOnly. La méthode receiveNoWaitLocalOnly est utilisée uniquement lorsque vous définissez receiveTimeout sur une valeur négative.

Lorsque ce problème se produit à votre application, vérifiez les paramètres de configuration suivants :

  • Déterminez si votre stratégie de prérécupération est 0, qui est également l’option par défaut. La prérécupération 0 signifie qu’un consommateur de tirage envoie des demandes de tirage à Service Bus pour chaque sondage.

  • Si vous avez configuré un prérécupération autre que zéro, déterminez si votre propriété receiveLocalOnly ou receiveNoWaitLocalOnly est définie sur false, qui est l’option par défaut. Une valeur false ici entraîne toujours l’envoi de demandes de tirage au serveur, car elle n’interroge pas uniquement le consommateur local.

  • La configuration receiveTimeout détermine la durée pendant laquelle elle bloque chaque demande de tirage( pull request) afin qu’elle puisse affecter la fréquence des demandes de tirage envoyées au serveur. La valeur par défaut est 1 seconde.

Pour une analyse complète, consultez la discussion dans le problème GitHub.

Solutions

Les sections suivantes décrivent deux solutions pour traiter ce problème

Solution 1. Modification pour envoyer (push) le consommateur et la vérification locale uniquement

En changeant le mode en push, le consommateur devient un notification asynchrone consommateur qui n’extrait pas les messages du répartiteur, mais conserve un montant cible de crédit de lien. Le montant est décidé par une propriété de prérécupération. À mesure que service Bus (expéditeur) envoie des messages, le crédit de lien de l’expéditeur diminue et lorsque le crédit de lien de l’expéditeur tombe en dessous d’un seuil, le client (destinataire) envoie une demande au serveur pour augmenter le crédit de lien de l’expéditeur au montant cible souhaité.

Pour accomplir cette solution, ajoutez la configuration suivante :

Tout d’abord, configurez le nombre de prefetch comme non zéro, ce qui configure le consommateur comme non-pull. Le tableau suivant présente plusieurs propriétés de prérécupération, chacune qui contrôle différentes entités Service Bus. Définissez les propriétés qui s’appliquent à votre cas.

Propriété Description
spring.jms.servicebus.prefetch.all Valeur de secours de l’option de prérécupération dans cet espace de noms Service Bus
spring.jms.servicebus.prefetch.queue-prefetch Numéro de prérécupération de la file d’attente.
spring.jms.servicebus.prefetch.queue-browser-prefetch Numéro de prérécupération du navigateur de file d’attente.
spring.jms.servicebus.prefetch.topic-prefetch Numéro de prérécupération de la rubrique.
spring.jms.servicebus.prefetch.durable-topic-prefetch Numéro de prérécupération de la rubrique durable.

Ensuite, configurez l'non-local-check en ajoutant une classe de configuration pour le personnalisateur d’usine, comme illustré dans l’exemple suivant :

@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);
        };
    }
}

La valeur de prérécupération peut affecter la vitesse de distribution des messages vers la mémoire tampon locale du consommateur. Vous devez ajuster la valeur en fonction de vos volumes de performances et de messages consommants. Une valeur appropriée peut accélérer le processus de consommation, tandis qu’une valeur trop importante peut entraîner la mise en mémoire tampon locale des messages obsolètes et distribués à nouveau. Pour les volumes de messages faibles, où chaque message prend beaucoup de temps à traiter, définissez la prérécupération sur 1. Cette valeur garantit qu’un consommateur ne traite qu’un seul message à la fois.

Solution 2. Augmenter le délai d’expiration de réception pour diminuer la fréquence d’extraction

La propriété de délai d’attente de réception détermine la stratégie pour la durée pendant laquelle le consommateur bloque l’attente d’un résultat de tirage. Par conséquent, en étendant le délai d’expiration, vous pouvez réduire la fréquence d’extraction, puis réduire le nombre de demandes de tirage lorsque vous choisissez le mode pull. Dans les cas extrêmes, vous pouvez définir la stratégie d’attente indéfiniment jusqu’à ce qu’un message arrive, ce qui signifie que le consommateur tire uniquement après avoir consommé un message. Dans ce cas, lorsqu’aucun message n’est présent sur le serveur, il bloque l’attente.

Pour accomplir cette solution, configurez la propriété spring.jms.listener.receive-timeout. Cette propriété est de type java.time.Duration et a une valeur par défaut de 1 seconde. La liste suivante explique l’effet de différentes valeurs :

  • La définition du délai d’expiration de réception sur 0 signifie que les blocs de tirage sont indéfinis jusqu’à ce qu’un message soit distribué.
  • La définition du délai d’expiration de réception sur une valeur positive signifie que l’extraction bloque jusqu’au délai d’expiration.
  • La définition du délai de réception sur une valeur négative signifie que l’extraction est une réception sans attente, ce qui signifie qu’elle retourne immédiatement un message ou null si aucun message n’est disponible.

Note

Une valeur de délai d’expiration élevée peut apporter des effets secondaires. Par exemple, une valeur de délai d’attente élevée étend également la durée pendant laquelle le thread principal est dans un état de bloc. Cet état signifie que le conteneur sera moins réactif aux appels stop() et ne peut s’arrêter qu’entre les appels receive().

En outre, le conteneur ne peut envoyer des requêtes qu’une fois l’intervalle de receive-timeout passé. Si l’intervalle est supérieur à 10 minutes, Service Bus ferme le lien et empêche l’écouteur d’envoyer ou de recevoir. Pour plus d’informations, consultez la section Link est fermée section erreurs AMQP dans Azure Service Bus. Par défaut, l’écouteur utilise une CachingConnectionFactory.

Si vous avez besoin d’un délai d’attente élevé, veillez à utiliser le JmsPoolConnectionFactory.

Pour plus d’informations sur le problème de fermeture de lien et sur l’utilisation de JmsPoolConnectionFactory, consultez le MessageProducer a été fermé en raison d’une erreur irrécupérable section.

Problème de prérécupération

Description du problème

Une stratégie de prérécupération inappropriée peut entraîner les problèmes suivants :

  • Les mêmes messages sont consommés à plusieurs reprises.
  • Les messages sont placés dans la file d’attente de lettres mortes après MaxDeliveryCountExceeded, même lorsque les messages sont traités sans erreur ou exception.

Analyse de la cause

Ce problème se produit généralement lorsque la valeur de de prérécupération est supérieure à la capacité de consommation réelle, avec l’effet que trop de messages sont prérécupérés vers la mémoire tampon locale en attente d’être consommés. Toutefois, les messages prérécupérés sont affichés comme distribués dans un mode de verrouillage aperçu côté Service Bus. Chaque message distribué a un nombre maximal de remises et des attributs de durée de verrouillage. Dans le mode de réception du verrou d’aperçu, les messages récupérés dans la mémoire tampon de prérécupération sont acquis dans la mémoire tampon dans un état verrouillé, avec l’horloge du délai d’expiration pour la graduation du verrou. Si la mémoire tampon de prérécupération est volumineuse et que le traitement prend tellement de temps que les verrous de message expirent tout en restant dans la mémoire tampon de prérécupération, le message est traité comme abandonné et est à nouveau mis à disposition pour la récupération à partir de la file d’attente.

Ce problème peut entraîner l’extraction du message dans la mémoire tampon de prérécupération et placée à la fin. Si la mémoire tampon de prérécupération n’est pas traitée avant l’expiration du message, les messages sont prérécupérés à plusieurs reprises, mais jamais remis efficacement dans un état utilisable (verrouillé validement). Ensuite, lorsque ces copies obsolètes sont mises en file d’attente, l’application consomme ensuite le même message à plusieurs reprises et ne peut pas les terminer. Dans un autre cas, les messages répétés ont expiré dans la mémoire tampon avant d’être consommés. Dans ce cas, le message dans Service Bus sera finalement déplacé vers la file d’attente de lettres mortes une fois le nombre maximal de remises dépassé.

Pour plus d’informations, consultez la Pourquoi la prérécupération n’est-elle pas l’option par défaut ? section de prérécupération des messages Azure Service Bus.

Solution

Veillez à la configuration du prérécupération pour vous assurer qu’elle correspond à la fonctionnalité consommatrice. Vous devez équilibrer le nombre maximal de prérécupérations et la durée de verrouillage configurée sur la file d’attente ou l’abonnement afin que le délai d’expiration du verrouillage dépasse au moins le temps de traitement des messages attendu cumulé pour la taille maximale de la mémoire tampon de prérécupération, plus un message. En même temps, le délai d’expiration du verrouillage ne doit pas être si long que les messages peuvent dépasser leur durée de vie maximale lorsqu’ils sont accidentellement supprimés, ce qui oblige leur verrouillage à expirer avant d’être redélisés.

Pour configurer l’attribut de prérécupération, qui a la valeur par défaut zéro, utilisez l’une des propriétés suivantes :

Propriété Description
spring.jms.servicebus.prefetch.all Valeur de secours de l’option de prérécupération dans cet espace de noms Service Bus.
spring.jms.servicebus.prefetch.queue-prefetch Numéro de prérécupération de la file d’attente.
spring.jms.servicebus.prefetch.queue-browser-prefetch Numéro de prérécupération du navigateur de file d’attente.
spring.jms.servicebus.prefetch.topic-prefetch Numéro de prérécupération de la rubrique.
spring.jms.servicebus.prefetch.durable-topic-prefetch Numéro de prérécupération de la rubrique durable.

Comment effectuer une disposition AMQP vers Service Bus ?

JMS prend en charge cinq types de destruction AMQP lors de l’accusé de réception des messages au répartiteur de messagerie. Les valeurs prises en charge sont ACCEPTED, REJECTED, RELEASED, MODIFIED_FAILEDet MODIFIED_FAILED_UNDELIVERABLE. Pour plus d’informations, consultez la section la disposition AMQP et le mappage des opérations Service Bus de Utiliser Java Message Service 1.1 avec azure Service Bus standard et AMQP 1.0.

Par conséquent, pour terminer manuellement, abandonner, reporter ou libérer un message à l’aide de JmsListener, procédez comme suit :

  1. Désactivez les transactions de session et utilisez le mode client ack.

    Pour accomplir cette tâche, déclarez votre propre JmsListenerContainerFactory bean, puis définissez les propriétés, ou publiez le processus de défini dans lede démarrage . L’exemple suivant utilise l’approche de déclaration d’un autre haricot :

    @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. Dans votre gestionnaire de messages, complétez ou abandonnez explicitement les messages.

    @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èmes de configuration

Désactiver la configuration automatique de Service Bus JMS

Description du problème

Certains utilisateurs importent certains Spring Cloud Azure Starter pour la configuration automatique d’un service Azure autre que Service Bus JMS. Ils utilisent également l’infrastructure Spring JMS sans avoir besoin de JMS Service Bus. Ensuite, lorsque l’application tente de démarrer, les exceptions suivantes sont levées :

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

Analyse de la cause

Ce problème se produit parce que toutes les classes de configuration automatique Spring Cloud Azure sont placées dans le même module, de sorte que n’importe quel Spring Cloud Azure Starter importe réellement toutes ces configurations automatiques, qui incluent également Service Bus JMS. Ensuite, lorsque l’application utilise l’API Spring JMS, elle répond à la condition de configuration automatique JMS Service Bus et la déclenche. Ensuite, pour les utilisateurs qui n’ont pas l’intention d’utiliser spring-cloud-azure-starter-servicebus-jms, les conditions de propriété ne sont pas remplies, car il n’y a aucune raison pour eux de configurer Service Bus pour JMS. Cette situation entraîne la levée des exceptions.

Solution

Spring Cloud Azure pour Service Bus JMS fournit une propriété pour activer ou désactiver sa configuration automatique. Vous pouvez choisir de désactiver cette fonctionnalité si nécessaire à l’aide du paramètre de propriété suivant :

spring.jms.servicebus.enabled=false

Configurer les attributs de message

Comment définir le type de contenu des messages sortants ?

Pour configurer le type de contenu, personnalisez le convertisseur de messages pour modifier l’attribut de type de contenu lors de la conversion de messages. Le code suivant prend des messages d’octets comme exemple.

Tout d’abord, personnalisez le convertisseur de messages à utiliser dans le JmsTemplate, comme illustré dans l’exemple suivant :

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;
    }
}

Ensuite, déclarez votre bean de convertisseur de message personnalisé, comme illustré dans cet exemple :

@Configuration(proxyBeanMethods = false)
public class CustomJmsConfiguration {

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

Comment définir le nom de propriété id de type pour MappingJackson2MessageConverter ?

L’attribut type-id-property-name permet au MappingJackson2MessageConverter de déterminer la classe à utiliser pour désérialiser la charge utile du message. Lors de la sérialisation de chaque objet Java vers une charge utile Spring Message, le convertisseur stocke le type de charge utile dans une propriété de message avec le nom de propriété enregistré par type-id-property-name. Ensuite, lors de la désérialisation du message, le convertisseur lit l’ID de type du message et effectue la désérialisation.

Pour définir le type-id-property-name, déclarez votre propre bean MappingJackson2MessageConverter et configurez cette propriété, comme illustré dans l’exemple suivant :

@Configuration(proxyBeanMethods = false)
public class CustomJmsConfiguration {

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

Détection dupliquée

Azure Service Bus prend en charge détection en double, qui applique la propriété MessageId pour identifier de manière unique les messages et ignorer les doublons envoyés à Service Bus.

Toutefois, pour l’API JMS, vous ne devez pas définir l’ID de message JMS, qui est considéré comme illégal dans les spécifications JMS. Par conséquent, cette fonctionnalité n’est actuellement pas prise en charge pour Spring Cloud Azure Service Bus JMS Starter.

Pour obtenir d’autres mises à jour de cette fonctionnalité, consultez le problème GitHub.

Activer la journalisation des transports AMQP

Pour plus d’informations, consultez la activer la journalisation des transports AMQP section Résolution des problèmes service Bus.

Obtenir de l’aide supplémentaire

Pour plus d’informations sur les moyens de contacter le support, consultez support à la racine du dépôt.

Ressources pour le démarrage JMS Azure Service Bus Spring Cloud

Classement des problèmes GitHub

Lorsque vous déposez des problèmes GitHub, les détails suivants sont demandés :

  • Configuration service Bus / Environnement d’espace de noms
    • Quel niveau est l’espace de noms (standard ou Premium) ?
    • Quel type d’entité de messagerie est utilisé (file d’attente ou rubrique) ? et sa configuration.
    • Quelle est la taille moyenne de chaque message ?
  • Quel est le modèle de trafic comme ? (autrement dit, le nombre de messages par minute et si le client est toujours occupé ou a des périodes de trafic lentes.)
  • Repro code et étapes
    • Cela est important, car nous ne pouvons souvent pas reproduire le problème dans notre environnement.
  • Journaux