Partager via


Résoudre les problèmes liés à Azure Service Bus

Cet article traite des techniques d’investigation des défaillances, de la concurrence, des erreurs courantes pour les types d’informations d’identification dans la bibliothèque cliente Java Azure Service Bus et des étapes d’atténuation pour résoudre ces erreurs.

Activer et configurer la journalisation

Le Kit de développement logiciel (SDK) Azure pour Java offre un article de journalisation cohérent pour vous aider à résoudre les erreurs d’application et à accélérer leur résolution. Les journaux générés capturent le flux d’une application avant d’atteindre l’état du terminal pour aider à localiser le problème racine. Pour obtenir des conseils sur la journalisation, consultez Configurer la journalisation dans le Kit de développement logiciel (SDK) Azure pour Java et Vue d’ensemble de la résolution des problèmes.

Outre l’activation de la journalisation, la définition du niveau de journalisation sur VERBOSE ou DEBUG fournit des insights sur l’état de la bibliothèque. Les sections suivantes montrent des exemples de configurations log4j2 et logback pour réduire les messages excessifs lorsque la journalisation détaillée est activée.

Configurer Log4J 2

Procédez comme suit pour configurer Log4J 2 :

  1. Ajoutez les dépendances dans votre pom.xml en utilisant celles de l’exemple de journalisation pom.xml, dans la section « Dépendances requises pour Log4j2 ».
  2. Ajoutez log4j2.xml à votre dossier src/main/resources.

Configurer logback

Pour configurer logback, procédez comme suit :

  1. Ajoutez les dépendances dans votre pom.xml en utilisant celles de l'exemple de journalisation pom.xml, dans la section « Dépendances requises pour logback ».
  2. Ajoutez logback.xml à votre dossier src/main/resources.

Activer le journal de transport AMQP

Si l'activation du journal client n'est pas suffisante pour diagnostiquer vos problèmes, vous pouvez activer la journalisation vers un fichier dans la bibliothèque AMQP sous-jacente, Qpid Proton-J. Qpid Proton-J utilise java.util.logging. Vous pouvez activer la journalisation en créant un fichier de configuration avec le contenu indiqué dans la section suivante. Vous pouvez également définir proton.trace.level=ALL et les options de configuration souhaitées pour l’implémentation java.util.logging.Handler. Pour connaître les classes d’implémentation et leurs options, consultez package java.util.logging dans la documentation du Kit de développement logiciel (SDK) Java 8.

Pour tracer les trames de transport AMQP, définissez la variable d’environnement PN_TRACE_FRM=1.

Exemple de fichier logging.properties

Le fichier de configuration suivant enregistre la sortie du niveau TRACE de Proton-J au fichier proton-trace.log:

handlers=java.util.logging.FileHandler
.level=OFF
proton.trace.level=ALL
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.pattern=proton-trace.log
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=[%1$tF %1$tr] %3$s %4$s: %5$s %n

Réduire le journal

Une façon de réduire les logs consiste à modifier la verbosité. Une autre méthode consiste à ajouter des filtres qui excluent les journaux provenant de packages de noms de journaux tels que com.azure.messaging.servicebus ou com.azure.core.amqp. Pour obtenir des exemples, consultez les fichiers XML dans les sections Configurer log4J 2 et Configurer la journalisation.

Lorsque vous soumettez une anomalie, les messages de journal provenant des classes des packages suivants sont intéressants :

  • com.azure.core.amqp.implementation
  • com.azure.core.amqp.implementation.handler
    • L’exception est que vous pouvez ignorer le message onDelivery dans ReceiveLinkHandler.
  • com.azure.messaging.servicebus.implementation

Concurrence dans ServiceBusProcessorClient

ServiceBusProcessorClient permet à l’application de configurer le nombre d’appels au gestionnaire de messages qui doivent se produire simultanément. Cette configuration permet de traiter plusieurs messages en parallèle. Pour qu'un ServiceBusProcessorClient consomme des messages à partir d'une entité non-session, l'application peut configurer la simultanéité souhaitée en utilisant l'API maxConcurrentCalls. Pour une entité activée par session, la simultanéité souhaitée est maxConcurrentSessions fois maxConcurrentCalls.

Si l’application observe moins d’appels simultanés au gestionnaire de messages que la concurrence configurée, cela peut être dû au fait que le pool de threads n’est pas dimensionné correctement.

ServiceBusProcessorClient utilise des threads démons du pool de threads boundedElastic global de Reactor pour invoquer le gestionnaire de messages. Le nombre maximal de threads simultanés dans ce pool est limité par une limite. Par défaut, cette limite est de dix fois le nombre de cœurs processeur disponibles. Pour que le ServiceBusProcessorClient prenne effectivement en charge la concurrence souhaitée par l'application (maxConcurrentCalls ou maxConcurrentSessions fois maxConcurrentCalls), vous devez avoir une valeur de plafond de pool boundedElastic supérieure à la concurrence souhaitée. Vous pouvez remplacer la limite par défaut en définissant la propriété système reactor.schedulers.defaultBoundedElasticSize.

Vous devez paramétrer le pool de threads et l’allocation du processeur au cas par cas. Toutefois, lorsque vous remplacez le plafond du pool, commencez par limiter le nombre de threads simultanés à environ 20-30 par cœur de processeur. Nous vous recommandons de limiter la concurrence souhaitée par instance de ServiceBusProcessorClient à environ 20 à 30. Profilez et mesurez votre cas d’usage spécifique et ajustez les aspects concurrentiels en conséquence. Pour les scénarios à charge élevée, envisagez d’exécuter plusieurs instances ServiceBusProcessorClient où chaque instance est générée à partir d’une nouvelle instance ServiceBusClientBuilder. En outre, envisagez d’exécuter chaque ServiceBusProcessorClient dans un hôte dédié, tel qu’un conteneur ou une machine virtuelle, afin que les temps d’arrêt dans un hôte n’affectent pas le traitement global des messages.

N’oubliez pas que la définition d’une valeur élevée pour la limite du pool sur un hôte avec quelques cœurs de processeur aurait des effets néfastes. Certains signes de ressources processeur faibles ou d’un pool avec trop de threads sur moins de processeurs sont : les délais d’expiration fréquents, les verrous perdus, les interblocages ou le débit inférieur. Si vous exécutez l’application Java sur un conteneur, nous vous recommandons d’utiliser deux cœurs de processeurs virtuels ou plus. Nous vous déconseillons de choisir moins d'un cœur de processeur virtuel pour exécuter une application Java sur des environnements conteneurisés. Pour obtenir des recommandations détaillées sur le dimensionnement des ressources, consultez Conteneuriser ses applications Java.

Goulot d’étranglement du partage de connexion

Tous les clients créés à partir d’une instance de ServiceBusClientBuilder partagée partagent la même connexion à l’espace de noms Service Bus.

L’utilisation d’une connexion partagée permet d’effectuer des opérations de multiplexage entre les clients sur une connexion, mais le partage peut également devenir un goulot d’étranglement s’il existe de nombreux clients, ou les clients génèrent ensemble une charge élevée. Chaque connexion a un thread d’E/S associé. Lors du partage de la connexion, les clients placent leur travail dans la file d’attente de travail de ce thread d’E/S partagé et la progression de chaque client dépend de la fin en temps voulu de son travail dans la file d’attente. Le thread d'E/S traite les travaux mis en file d'attente en série. Autrement dit, si la file d'attente de travail du thread d'E/S d'une connexion partagée accumule une grande quantité de travail en attente à traiter, les symptômes sont similaires à ceux d'une faible puissance de CPU. Cette situation est décrite dans la section précédente sur la concurrence - par exemple, blocage des clients, dépassement de délai, perte de verrou ou ralentissement du chemin de récupération.

Le Kit de développement logiciel (SDK) Service Bus utilise le modèle d’affectation de noms reactor-executor-* pour le thread d’E/S de connexion. Lorsque l'application est confrontée à un goulot d'étranglement au niveau de la connexion partagée, cela peut se refléter dans l'utilisation du processeur du threading d'E/S. Une longue file d'attente de travail dans le threading d'E/S peut entraîner une perte de temps. De même, dans le vidage du tas ou dans la mémoire vive, l'objet ReactorDispatcher$workQueue est la file d'attente du threading d'entrée/sortie. Une longue file d'attente de tâches dans la capture de mémoire pendant la période de congestion peut indiquer que le thread d'E/S partagé est surchargé de tâches en attente.

Par conséquent, si la charge de l’application sur un point de terminaison Service Bus est raisonnablement élevée en termes de nombre global de messages reçus envoyés ou de taille de charge utile, vous devez utiliser une instance de générateur distincte pour chaque client que vous générez. Par exemple, pour chaque entité - file d’attente ou rubrique - vous pouvez créer une nouvelle ServiceBusClientBuilder et générer un client à partir de celui-ci. En cas de charge extrêmement élevée sur une entité spécifique, vous pouvez créer plusieurs instances clientes pour cette entité ou exécuter des clients dans plusieurs hôtes ( par exemple, des conteneurs ou des machines virtuelles) pour équilibrer la charge.

Les clients s’arrêtent lors de l’utilisation du point de terminaison personnalisé Application Gateway

L’adresse de point de terminaison personnalisée fait référence à une adresse de point de terminaison HTTPS fournie par l’application pouvant être résolue dans Service Bus ou configurée pour acheminer le trafic vers Service Bus. Azure Application Gateway facilite la création d’un serveur frontal HTTPS qui transfère le trafic vers Service Bus. Vous pouvez configurer le Kit de développement logiciel (SDK) Service Bus pour qu’une application utilise une adresse IP frontale Application Gateway comme point de terminaison personnalisé pour se connecter à Service Bus.

Application Gateway offre plusieurs stratégies de sécurité prenant en charge différentes versions de protocole TLS. Il existe des stratégies prédéfinies appliquant TLSv1.2 comme version minimale, il existe également d’anciennes stratégies avec TLSv1.0 comme version minimale. Le serveur frontal HTTPS aura une stratégie TLS appliquée.

À l’heure actuelle, le Kit de développement logiciel (SDK) Service Bus ne reconnaît pas certaines terminaisons TCP distantes par le serveur frontal Application Gateway, qui utilise TLSv1.0 comme version minimale. Par exemple, si le serveur frontal envoie TCP FIN, des paquets ACK pour fermer la connexion lorsque ses propriétés sont mises à jour, le SDK ne peut pas le détecter, de sorte qu’il ne se reconnecte pas, et les clients ne peuvent plus envoyer ni recevoir de messages. Une telle interruption se produit uniquement lors de l’utilisation de TLSv1.0 comme version minimale. Pour atténuer les problèmes, utilisez une stratégie de sécurité avec TLSv1.2 ou une version ultérieure comme version minimale pour le serveur frontal Application Gateway.

La prise en charge de TLSv1.0 et 1.1 pour tous les services Azure est déjà annoncée pour se terminer d'ici le 31 octobre 2024, donc la transition vers TLSv1.2 est vivement recommandée.

Le verrouillage de message ou de session est perdu

Une file d'attente ou un abonnement à une rubrique du Service Bus a une durée de verrouillage définie au niveau de la ressource. Lorsque le client récepteur extrait un message de la ressource, le répartiteur Service Bus applique un verrou initial au message. Le verrou initial dure pour la durée de verrouillage définie au niveau de la ressource. Si le verrou de message n’est pas renouvelé avant son expiration, le répartiteur Service Bus libère le message pour le rendre disponible pour d’autres récepteurs. Si l’application tente de terminer ou d’abandonner un message après l’expiration du verrou, l’appel d’API échoue avec l’erreur com.azure.messaging.servicebus.ServiceBusException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue.

Le client Service Bus prend en charge l’exécution d’une tâche de renouvellement de verrou en arrière-plan, qui renouvelle continuellement le verrou de message chaque fois avant son expiration. Par défaut, la tâche de renouvellement de verrou s’exécute pendant 5 minutes. Vous pouvez ajuster la durée de renouvellement du verrou à l’aide de ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration). Si vous passez la valeur Duration.ZERO, la tâche de renouvellement de verrou est désactivée.

La liste suivante décrit certains des modèles d’utilisation ou des environnements hôtes qui peuvent entraîner l’erreur de verrouillage perdu :

  • La tâche de renouvellement de verrou est désactivée et le temps de traitement des messages de l’application dépasse la durée de verrouillage définie au niveau de la ressource.

  • Le temps de traitement des messages de l’application dépasse la durée de renouvellement de la tâche de renouvellement de verrou configurée. Notez que, si la durée de renouvellement du verrou n’est pas définie explicitement, elle est définie par défaut sur 5 minutes.

  • L’application a activé la fonctionnalité de prérécupération en définissant la valeur de prérécupération sur un entier positif à l’aide de ServiceBusReceiverClientBuilder.prefetchCount(prefetch). Lorsque la fonctionnalité de prérécupération est activée, le client récupère le nombre de messages égaux au prérécupération de l’entité Service Bus ( file d’attente ou rubrique) et les stocke dans la mémoire tampon de prérécupération en mémoire. Les messages restent dans la mémoire tampon de prérécupération jusqu’à ce qu’ils soient reçus dans l’application. Le client n’étend pas le verrou des messages pendant qu’ils se trouvent dans la mémoire tampon de prérécupération. Si le traitement de l'application prend tellement de temps que les verrous des messages expirent pendant qu'ils restent dans la mémoire tampon de préemption, l'application peut acquérir les messages avec un verrou expiré. Pour plus d'informations, voir Pourquoi l'option Prefetch n'est-elle pas l'option par défaut ?

  • L’environnement hôte présente des problèmes réseau occasionnels ( par exemple, une défaillance ou une panne réseau temporaires) qui empêchent la tâche de renouvellement du verrou de renouveler le verrou à l’heure.

  • L’environnement hôte ne dispose pas de suffisamment de processeurs ou a des pénuries de cycles processeur intermittents qui retardent l’exécution de la tâche de renouvellement du verrou.

  • L’heure du système hôte n’est pas exacte ( par exemple, l’horloge est asymétrique) en retardant la tâche de renouvellement du verrou et en la empêchant de s’exécuter à temps.

  • Le thread d’E/S de connexion est surchargé, ce qui a un impact sur sa capacité à exécuter les appels réseau de renouvellement de verrous à temps. Les deux scénarios suivants peuvent entraîner ce problème :

    • L’application exécute trop de clients récepteurs partageant la même connexion. Pour plus d'informations, consultez la section Goulot d'étranglement du partage de connexion.
    • L’application a configuré ServiceBusReceiverClient.receiveMessages ou ServiceBusProcessorClient pour avoir des valeurs de maxMessages ou de maxConcurrentCalls volumineuses. Pour plus d'informations, consultez la section Concurrence dans ServiceBusProcessorClient.

Le nombre de tâches de renouvellement de verrou dans le client est égal aux valeurs de paramètres maxMessages ou maxConcurrentCalls définies pour ServiceBusProcessorClient ou ServiceBusReceiverClient.receiveMessages. Un grand nombre de tâches de renouvellement de verrou effectuant plusieurs appels réseau peuvent également avoir un effet négatif dans la limitation de l’espace de noms Service Bus.

Si l’hôte n’est pas suffisamment doté de ressources, le verrou peut toujours être perdu même si seules quelques tâches de renouvellement du verrou sont en cours d’exécution. Si vous exécutez l’application Java sur un conteneur, nous vous recommandons d’utiliser deux cœurs de processeurs virtuels ou plus. Nous vous déconseillons de sélectionner moins de 1 vCPU lors de l’exécution d’applications Java sur des environnements conteneurisés. Pour obtenir des recommandations détaillées sur l'allocation des ressources, consultez Conteneurisez vos applications Java.

Les mêmes remarques concernant les verrous s'appliquent également à une file d'attente du bus de service ou à un abonnement à une rubrique dont la session est activée. Lorsque le client récepteur se connecte à une session dans la ressource, le répartiteur applique un verrou initial à la session. Pour maintenir le verrou sur la session, la tâche de renouvellement du verrou dans le client doit continuer à renouveler le verrou de session avant son expiration. Pour une ressource activée par session, les partitions sous-jacentes se déplacent parfois pour atteindre l’équilibrage de charge entre les nœuds Service Bus, par exemple lorsque de nouveaux nœuds sont ajoutés pour partager la charge. Lorsque cela se produit, les verrous de session peuvent être perdus. Si l’application tente de terminer ou d’abandonner un message après la perte du verrou de session, l’appel d’API échoue avec l’erreur com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver.

Mise à niveau vers la version 7.15.x ou la plus récente

Si vous rencontrez des problèmes, vous devez d’abord tenter de les résoudre en effectuant une mise à niveau vers la dernière version du Kit de développement logiciel (SDK) Service Bus. La version 7.15.x est une refonte majeure, qui résout les problèmes de performances et de fiabilité de longue date.

La version 7.15.x et ultérieure réduit le changement de thread, supprime les verrous, optimise le code dans les chemins critiques et réduit les allocations de mémoire. Ces modifications entraînent jusqu’à 45 à 50 fois plus de débit sur le ServiceBusProcessorClient.

La version 7.15.x et ultérieure est également fournie avec diverses améliorations de fiabilité. Elles traitent plusieurs conditions de course (telles que les calculs de préchargement et de crédit) et améliorent la gestion des erreurs. Ces modifications entraînent une meilleure fiabilité en présence de problèmes temporaires entre différents types de clients.

Utilisation des clients les plus récents

La nouvelle infrastructure sous-jacente avec ces améliorations ( dans la version 7.15.x et ultérieure) est appelée V2-Stack. Cette ligne de mise en production inclut à la fois la génération précédente de la pile sous-jacente , la pile utilisée par la version 7.14.x et la nouvelle pile V2-Stack.

Par défaut, certains types de clients utilisent la V2-Stack, tandis que d’autres nécessitent une adhésion V2-Stack. Vous pouvez effectuer l’opt-in ou l’opt-out d’un stack spécifique (V2 ou la génération précédente) pour un type de client en fournissant des valeurs com.azure.core.util.Configuration lorsque vous construisez le client.

Par exemple, la réception d'une session basée sur V2-Stack avec ServiceBusSessionReceiverClient nécessite un opt-in comme le montre l'exemple suivant :

ServiceBusSessionReceiverClient sessionReceiver = new ServiceBusClientBuilder()
    .connectionString(Config.CONNECTION_STRING)
    .configuration(new com.azure.core.util.ConfigurationBuilder()
        .putProperty("com.azure.messaging.servicebus.session.syncReceive.v2", "true") // 'false' by default, opt-in for V2-Stack.
        .build())
    .sessionReceiver()
    .queueName(Config.QUEUE_NAME)
    .buildClient();

Le tableau suivant répertorie les types de clients et les noms de configuration correspondants, et indique si le client est actuellement activé par défaut pour utiliser le V2-Stack dans la dernière version 7.17.0. Pour un client qui n’est pas sur l'V2-Stack par défaut, vous pouvez utiliser l’exemple qui vient d’être affiché pour vous inscrire.

Type de client Nom de la configuration Est-il sur V2-Stack par défaut ?
Client d’expéditeur et de gestion com.azure.messaging.servicebus.sendAndManageRules.v2 oui
Client récepteur non processeur de session et réacteur com.azure.messaging.servicebus.nonSession.asyncReceive.v2 oui
Client récepteur processeur de session com.azure.messaging.servicebus.session.processor.asyncReceive.v2 oui
Client récepteur réacteur de session com.azure.messaging.servicebus.session.reactor.asyncReceive.v2 oui
Client récepteur synchrone sans session com.azure.messaging.servicebus.nonSession.syncReceive.v2 Non
Client récepteur synchrone de session com.azure.messaging.servicebus.session.syncReceive.v2 Non

En guise d’alternative à l’utilisation de com.azure.core.util.Configuration, vous pouvez effectuer l’opt-in ou le désactiver en définissant les mêmes noms de configuration à l’aide de variables d’environnement ou de propriétés système.

Étapes suivantes

Si les conseils de dépannage de cet article ne permettent pas de résoudre les problèmes liés à l'utilisation des bibliothèques clientes Azure SDK for Java, nous vous recommandons de déposer un problème dans le référentiel GitHub Azure SDK for Java.