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

Cet article traite des techniques d’investigation des défaillances, de la concurrence et des erreurs courantes dans la bibliothèque cliente Azure Service Bus Java. Utilisez ces conseils pour identifier les causes racines et appliquer des étapes d’atténuation pour résoudre les problèmes Azure Service Bus plus rapidement.

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 que vous produisez retracent le déroulement d’une application avant qu’elle n’atteigne son état final, afin de vous aider à localiser la cause première du problème. 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 la restauration automatique ».
  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 vous permet de configurer le nombre d’appels au gestionnaire de messages simultanément. Cette configuration permet de traiter plusieurs messages en parallèle. Pour un ServiceBusProcessorClient qui consomme des messages depuis une entité sans session, vous pouvez configurer le niveau de concurrence souhaité à l’aide de l’API maxConcurrentCalls. Pour une entité avec prise en charge des sessions, le niveau de concurrence souhaité est de maxConcurrentSessions fois maxConcurrentCalls.

Si vous observez 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 correctement dimensionné.

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.

Paramétrez 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. Limitez la concurrence souhaitée par ServiceBusProcessorClient instance à 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 a 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, utilisez deux cœurs de processeurs virtuels ou plus. Ne sélectionnez rien de moins de 1 cœur de processeur virtuel lors de l'exécution d'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 que vous créez à partir d’une instance partagée ServiceBusClientBuilder 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é. Lorsque vous partagez une 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 l’achèvement opportun 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 se retrouve avec un grand nombre de tâches en attente, les symptômes sont similaires à ceux d’une faible capacité 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 traduire par une utilisation CPU du thread d’E/S. En outre, dans le dump du tas ou dans la mémoire active, l’objet ReactorDispatcher$workQueue est la file de travail du thread d’E/S. Une longue file d’attente de tâches dans la capture mémoire pendant la période de saturation peut indiquer que le thread d’E/S partagé est surchargé par des 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, utilisez 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 que vous pouvez résoudre pour Service Bus ou configurer pour acheminer le trafic vers Service Bus. Azure Application Gateway facilite la création d’un front-end HTTPS qui transfère le trafic vers Service Bus. Vous pouvez configurer le sdk Service Bus pour qu’une application utilise une adresse IP frontale Application Gateway comme point de terminaison personnalisé pour vous connecter à Service Bus.

Application Gateway propose plusieurs stratégies de sécurité qui prennent en charge différentes versions de protocole TLS. Il existe des stratégies prédéfinies qui appliquent TLS 1.2 comme version minimale, et il existe des stratégies plus anciennes qui utilisent TLS 1.0 comme version minimale. Vous appliquez une stratégie TLS au serveur frontal HTTPS.

Actuellement, le sdk Service Bus ne reconnaît pas certaines terminaisons TCP distantes par le serveur frontal Application Gateway, qui utilise TLS 1.0 comme version minimale. Par exemple, si le serveur frontal envoie des paquets TCP FIN et ACK pour fermer la connexion lorsqu’il met à jour ses propriétés, le SDK ne peut pas détecter ces paquets. Par conséquent, il ne se reconnecte pas et les clients ne peuvent plus envoyer ni recevoir de messages. Cette interruption se produit uniquement lors de l’utilisation de TLS 1.0 comme version minimale. Pour atténuer ce problème, utilisez une stratégie de sécurité qui définit TLS 1.2 ou version ultérieure comme version minimale pour le serveur frontal Application Gateway.

La fin de la prise en charge de TLS 1.0 et 1.1 pour tous les services Azure a déjà été annoncée pour le 31 octobre 2024, alors effectuez la transition vers TLS 1.2.

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 perdue :

  • 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 active la fonctionnalité de prélecture en définissant la valeur de prélecture 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 à partir 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 par l’application prend tellement de temps que les verrous des messages expirent alors qu’ils se trouvent encore dans le tampon de prélecture, l’application peut récupérer 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 configure ServiceBusReceiverClient.receiveMessages ou ServiceBusProcessorClient pour avoir des valeurs élevées de maxMessages ou maxConcurrentCalls. Pour plus d'informations, consultez la section Concurrence dans ServiceBusProcessorClient.
  • Un modèle d'application courant qui augmente la probabilité d'une erreur de verrouillage perdu implique la planification de tâches de renouvellement de verrous à longue durée, comme les tâches dont la durée s'étend sur plusieurs heures. Comme mentionné précédemment, différents facteurs en dehors du contrôle d’un client Service Bus peuvent interférer avec le renouvellement de verrou réussi, de sorte que les conceptions d’applications doivent éviter d’assumer le renouvellement garanti sur des périodes prolongées. Pour éviter de devoir retraiter des opérations de longue durée, envisagez de diviser le travail en blocs plus petits ou d’implémenter une logique de point de contrôle idempotente.

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 ne dispose pas de ressources suffisantes, le verrou peut tout de même être perdu même s’il n’y a que quelques tâches de renouvellement du verrou en cours d’exécution. Si vous exécutez l'application Java sur un conteneur, utilisez deux cœurs de processeurs virtuels ou plus. Ne sélectionnez rien de moins de 1 cœur de processeur virtuel lors de l'exécution d'applications Java sur des environnements conteneurisés. Pour obtenir des recommandations détaillées sur le dimensionnement des ressources, consultez Conteneuriser ses 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.

Étapes suivantes

Si les conseils de dépannage de cet article ne permettent pas de résoudre les problèmes lorsque vous utilisez le Kit de développement logiciel (SDK) Azure pour Java bibliothèques clientes, placez un problème dans le Kit de développement logiciel (SDK) Azure pour Java GitHub référentiel.