Partager via


Dépanner 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 Java du client Azure Service Bus, et des mesures d'atténuation pour résoudre ces erreurs.

Activez et configurez la journalisation

Azure SDK for Java offre une histoire de journal cohérente pour faciliter le dépannage des erreurs d'application et accélérer leur résolution. Les journaux produits capturent le flux d’une application avant d’atteindre l’état terminal pour faciliter la localisation du problème racine. Pour obtenir des conseils sur la journalisation, voir Configurer la journalisation dans Azure SDK pour Java et Aperçu du dépannage.

Outre l'activation de la journalisation, le réglage du niveau de journal sur VERBOSE ou DEBUG permet d'avoir un aperçu de l'état de la bibliothèque. Les sections suivantes présentent des exemples de configurations de log4j2 et de logback pour réduire les messages excessifs lorsque la journalisation détaillée est activée.

Configurer Log4J 2

Suivez les étapes suivantes pour configurer Log4J 2 :

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

Configurer logback

Suivez les étapes suivantes pour configurer logback :

  1. Ajoutez les dépendances dans votre pom.xml en utilisant celles de l'exemple pom.xml de logback, 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 que vous souhaitez pour la mise en œuvre de java.util.logging.Handler. Pour connaître les classes d'implémentation et leurs options, consultez le package java.util.logging dans la documentation du 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 de niveau TRACE de Proton-J dans le 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 la journalisation est de modifier le niveau de détail. 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 des exemples, voir les fichiers XML dans les sections Configurer Log4J 2 et Configurer logback.

Lorsque vous soumettez un bogue, les messages de journal 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 simultanés au gestionnaire de messages. Cette configuration permet de traiter plusieurs messages en parallèle. Pour un ServiceBusProcessorClient consommant des messages d'une entité hors session, l'application peut configurer la simultanéité souhaitée à l'aide de l'API maxConcurrentCalls. Pour une entité activée par session, la simultanéité souhaitée est de maxConcurrentSessions fois maxConcurrentCalls.

Si l'application observe moins d'appels simultanés au gestionnaire de messages que la simultanéité configurée, cela peut être dû au fait que le pool de threads n'est pas dimensionné de manière appropriée.

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 un plafond. Par défaut, ce plafond est égal à dix fois le nombre de cœurs de processeurs 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 le plafond par défaut en définissant la propriété système reactor.schedulers.defaultBoundedElasticSize.

Vous devez ajuster 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 ServiceBusProcessorClient à environ 20-30. Profilez et mesurez votre cas d'utilisation spécifique et réglez les aspects de concurrence en conséquence. Pour les scénarios à forte charge, envisagez d'exécuter plusieurs instances ServiceBusProcessorClient où chaque instance est construite à 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 le temps d'arrêt d'un hôte n'ait pas d'impact sur le traitement global des messages.

Gardez à l'esprit que la définition d'une valeur élevée pour le plafond du pool sur un hôte disposant de peu de cœurs de processeur aurait des effets néfastes. Les signes d'un manque de ressources CPU ou d'un pool avec trop de threads sur un nombre réduit de CPU sont les suivants : dépassements fréquents de délais, perte de verrou, blocage ou baisse du débit. Si vous exécutez l'application Java dans un conteneur, nous vous recommandons d'utiliser au moins deux cœurs vCPU. Nous vous déconseillons de choisir moins d'un cœur vCPU lorsque vous exécutez une application Java dans un environnement conteneurisé. Pour des recommandations approfondies sur la ressource, voir Conteneurisez vos applications Java.

Goulot d'étranglement du partage de connexion

Tous les clients créés à partir d'une instance ServiceBusClientBuilder partagée partagent la même connexion à l'espace de noms du bus de service.

L'utilisation d'une connexion partagée permet de multiplexer les opérations entre les clients sur une même connexion, mais le partage peut également devenir un goulot d'étranglement s'il y a beaucoup de clients ou si les clients génèrent ensemble une charge élevée. Chaque connexion est associée à un thread d'E/S. Lors du partage de la connexion, les clients placent leur travail dans la file d'attente de ce thread d'E/S partagé et la progression de chaque client dépend de l'achèvement 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. En d'autres termes, si la file d'attente du thread d'E/S d'une connexion partagée se retrouve avec un grand nombre de travaux en attente à traiter, les symptômes sont similaires à ceux d'un processeur faible. 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 Service Bus SDK utilise le modèle de dénomination 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 travaux dans l'instantané de la mémoire pendant la période de goulot d'étranglement peut indiquer que le thread d'E/S partagé est surchargé par des travaux en attente.

Par conséquent, si la charge de l'application vers un point de terminaison du bus de service est raisonnablement élevée en termes de nombre global de messages envoyés-reçus ou de taille de la charge utile, vous devez utiliser une instance de constructeur distincte pour chaque client que vous construisez. Par exemple, pour chaque entité - file d'attente ou rubrique - vous pouvez créer un nouveau ServiceBusClientBuilder et construire un client à partir de celui-ci. En cas de charge extrêmement élevée vers une entité spécifique, vous pourriez vouloir soit créer plusieurs instances de client pour cette entité, soit exécuter les 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é de la passerelle d'applications.

L'adresse du point de terminaison personnalisé fait référence à une adresse de point de terminaison HTTPS fournie par l'application et pouvant être résolue vers Service Bus ou configurée pour acheminer le trafic vers Service Bus. Azure Application Gateway facilite la création d'un front-end HTTPS qui redirige le trafic vers Service Bus. Vous pouvez configurer Service Bus SDK pour une application afin qu'elle utilise une adresse IP front-end d'Application Gateway comme point de terminaison personnalisé pour se connecter à Service Bus.

Application Gateway propose plusieurs stratégies de sécurité prenant en charge différentes versions du protocole TLS. Il existe des stratégies prédéfinies imposant TLSv1.2 comme version minimale, ainsi que d'anciennes stratégies imposant TLSv1.0 comme version minimale. Une stratégie TLS sera appliquée à l'interface HTTPS.

À l'heure actuelle, le Service Bus SDK ne reconnaît pas certaines terminaisons TCP à distance effectuées par l'Application Gateway frontale, qui utilise TLSv1.0 comme version minimale. Par exemple, si le frontal envoie des paquets TCP FIN, ACK pour fermer la connexion lorsque ses propriétés sont mises à jour, le SDK ne peut pas le détecter, il ne se reconnectera donc pas et les clients ne pourront plus envoyer ou recevoir de messages. Un tel arrêt ne se produit que lorsque vous utilisez TLSv1.0 comme version minimale. Pour y remédier, utilisez une stratégie de sécurité avec TLSv1.2 ou une version supérieure comme version minimale pour le front-end de l'Application Gateway.

La fin de la prise en charge de TLSv1.0 et 1.1 par tous les services Azure est déjà annoncée pour le 31 octobre 2024, il est donc fortement recommandé de passer à TLSv1.2.

Perte du verrouillage du message ou de la session

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 tire un message de la ressource, le Service Bus broker applique un verrou initial au message. Le verrou initial dure pendant la durée du verrou définie au niveau de la ressource. Si le verrouillage du message n'est pas renouvelé avant son expiration, le Service Bus Broker 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 à l'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 le verrou du message en continu chaque fois avant son expiration. Par défaut, la tâche de renouvellement du verrou s'exécute pendant 5 minutes. Vous pouvez régler la durée du renouvellement du verrouillage en utilisant ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration). Si vous indiquez la valeur Duration.ZERO, la tâche de renouvellement du verrou est désactivée.

Les listes suivantes décrivent quelques-uns des modèles d'utilisation ou des environnements hôtes qui peuvent entraîner l'erreur de perte de verrou :

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

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

  • L'application a activé la fonctionnalité Prefetch en réglant la valeur prefetch sur un nombre entier positif à l'aide de ServiceBusReceiverClientBuilder.prefetchCount(prefetch). Lorsque la fonctionnalité Prefetch est activée, le client récupère le nombre de messages correspondant au prefetch à partir de l'entité Service Bus - file d'attente ou rubrique - et les stocke dans la mémoire tampon prefetch. Les messages restent dans la mémoire tampon jusqu'à ce qu'ils soient reçus par l'application. Le client ne prolonge pas le verrouillage des messages pendant qu'ils sont dans la mémoire tampon. 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 connaît des problèmes de réseau occasionnels - par exemple, une panne ou une interruption transitoire du réseau - qui empêchent la tâche de renouvellement du verrou de renouveler le verrou à temps.

  • L'environnement hôte ne dispose pas de suffisamment de processeurs ou connaît des pénuries de cycles de processeur par intermittence qui retardent l'exécution de la tâche de renouvellement du verrou en temps voulu.

  • L'heure du système hôte n'est pas exacte - par exemple, l'horloge est faussée - ce qui retarde la tâche de renouvellement du verrou et l'empêche de s'exécuter à temps.

  • Le thread d'E/S de la connexion est surchargé, ce qui a un impact sur sa capacité à exécuter à temps les appels réseau de renouvellement des verrous. Les deux scénarios suivants peuvent être à l'origine de ce problème :

    • L'application exécute un trop grand nombre 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 maxMessages ou maxConcurrentCalls importantes. 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 des paramètres maxMessages ou maxConcurrentCalls définies pour ServiceBusProcessorClient ou ServiceBusReceiverClient.receiveMessages. Un nombre élevé de tâches de renouvellement de verrou effectuant plusieurs appels réseau peut également avoir un effet négatif sur la limitation de l'espace de noms du bus de service.

Si l'hôte ne dispose pas de ressources suffisantes, le verrou peut être perdu même si seules quelques tâches de renouvellement de verrou sont en cours d'exécution. Si vous exécutez l'application Java dans un conteneur, nous vous recommandons d'utiliser au moins deux cœurs vCPU. Nous ne recommandons pas de choisir moins d'un cœur vCPU lorsque vous exécutez des applications Java dans des environnements conteneurisés. Pour des recommandations approfondies sur la ressource, voir 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 courtier 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 la session avant qu'il n'expire. Pour une ressource activée par session, les partitions sous-jacentes se déplacent parfois pour équilibrer la charge entre les nœuds du bus de service - par exemple, lorsque de nouveaux nœuds sont ajoutés pour partager la charge. Dans ce cas, 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 à l'API échoue avec l'erreur com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver.

Mettez à jour vers la version 7.15.x ou la plus récente

Si vous rencontrez des problèmes, vous devez d'abord essayer de les résoudre en passant à la dernière version du Service Bus SDK. La version 7.15.x est une refonte majeure qui résout les problèmes de performance et de fiabilité rencontrés depuis longtemps.

La version 7.15.x et les versions ultérieures réduisent le threading, suppriment les verrous, optimisent le code dans les chemins chauds et réduisent les allocations de mémoire. Ces changements se traduisent par un débit jusqu'à 45-50 fois plus élevé sur le ServiceBusProcessorClient.

La version 7.15.x et les versions ultérieures apportent également diverses améliorations en matière 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 changements se traduisent par une meilleure fiabilité en présence de problèmes transitoires sur divers types de clients.

Utilisation des derniers clients

Le nouveau cadre sous-jacent qui comporte ces améliorations - à partir de la version 7.15.x - est appelé V2-Stack. Cette gamme de versions comprend à la fois la génération précédente de la pile sous-jacente (celle utilisée par la version 7.14.x) et la nouvelle pile V2.

Par défaut, certains types de clients utilisent la pile V2, tandis que d'autres requièrent l'activation de la pile V2. Vous pouvez choisir d'accepter ou de refuser une pile 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 créez 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 autorisé par défaut à utiliser la pile V2 dans la dernière version 7.17.0. Pour un client qui n'est pas sur la V2-Stack par défaut, vous pouvez utiliser l'exemple qui vient d'être montré pour l'opt-in.

Type de client Nom de la configuration Est-il sur V2-Stack par défaut ?
Emetteur et client 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 hors session com.azure.messaging.servicebus.nonSession.syncReceive.v2 non
Client récepteur synchrone de session com.azure.messaging.servicebus.session.syncReceive.v2 non

Au lieu d'utiliser com.azure.core.util.Configuration, vous pouvez procéder à l'opt-in ou à l'opt-out en définissant les mêmes noms de configuration à l'aide de variables d'environnement ou de propriétés du 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.