Le tracing distribué est un outil de diagnostic puissant pour les environnements hybrides basés sur les microservices, car il vous permet d'examiner les problèmes de performance à partir d'un seul et même endroit. Une trace distribuée consolide les enregistrements d'événements qui se produisent sur plusieurs composants d'un système distribué. 

Dans cet article, vous découvrirez :

  • Ce qu'est le tracing distribué et comment l'utiliser
  • La structure des traces distribuées, y compris les spans et transactions, et des exemples dans New Relic
  • Comment faire passer le contexte des traces entre les services, y compris le standard W3C Trace Context
  • Le pour et le contre des échantillonnages en début et en fin de workflow

Qu'est-ce que le tracing distribué ?

Une trace distribuée consolide les enregistrements des événements qui se produisent sur tous les composants d'un système distribué. Ces événements sont déclenchés par une seule opération (en cliquant sur un bouton sur un site web, par exemple) et ils traversent les frontières entre les processus, réseau et sécurité. Afin de mieux comprendre ce qu'est le tracing distribué, nous vous proposons ci-dessous la définition de chaque terme :

  • Distribué se rapporte aux systèmes distribués, qui comprennent des composants indépendants communiquant via des requêtes pour former une application. 
  • Tracing fait référence aux traces qui font le suivi de bout en bout du parcours d'une requête à mesure que chaque trace passe d'un service à l'autre. 

Le tracing distribué est une partie essentielle d'une plateforme de monitoring des performances des applications (APM) unifiée et uniformisée. Il fournit une visibilité en temps réel de la santé et des performances de tout le stack d'applications lorsque vous l'intégrez avec d'autres outils d'observabilité, telles que les métriques, les logs et les alertes. Le tracing distribué fournit deux informations clés :

  • Le chemin suivi par une requête de service sur un système distribué
  • Le temps passé pour terminer chaque requête de service

Lorsque vous faites le monitoring des architectures basées sur les microservices, le tracing distribué aide à repérer les endroits où se produisent les échecs et ce qui entraîne des performances médiocres. Voici une illustration du fonctionnement du tracing distribué dans New Relic :

Vous réfléchissez à la façon d'utiliser le tracing distribué dans le monde réel ? Si les traces contiennent en elles-mêmes toutes les données pertinentes pour mener une analyse des causes profondes, les outils de tracing sont différents les uns des autres en fonction des éléments suivants :

  • Simplicité de déploiement et d'instrumentation
  • Visualisation et requête
  • Configuration et flexibilité

Structure des traces distribuées

Dans New Relic, les traces distribuées rassemblent trois types de données :

  • Un span est une opération nommée et chronométrée qui représente une partie du workflow. Des exemples d'opérations de span comprennent les requêtes du datastore, les interactions côté navigateur, le suivi du temps de réponse au niveau de la méthode, les appels à d'autres services et les fonctions Lambda. Par exemple, dans un service HTTP, vous pouvez vouloir un span créé au début d'une requête HTTP et terminé lorsque le serveur HTTP renvoie une réponse. Les attributs de span contiennent d'importantes informations sur l'opération telle que la durée et les données de l'hôte.
  • Une transaction est une unité logique de travail dans une application logicielle, telle que les requêtes HTTP, les requêtes SQL, les processus en arrière-plan, l'activité de la file d'attente des messages, etc. Dans New Relic, les événements de transaction comprennent les informations sur l'application, les appels sur la base de données, la durée de la transaction, et toute erreur qui a pu se produire.
  • Les métadonnées contextuelles affichent les calculs sur une trace et les relations entre ses spans. Elles montrent aussi la durée des traces, toutes les entités qui font partie d'une trace, le nombre d'entités qui font partie d'une trace, l'heure de début d'une trace en millisecondes, ainsi que les identifiants parent/enfant qui représentent toutes les relations au sein d'une trace.

En savoir plus sur les spans

Un span dans une trace distribuée représente l'unité de travail effectué individuelle et le temps pris par un service pour le traitement d'une requête. Les traces encapsulent les spans dans une structure en arborescence : plus d'un span enfant peut appartenir à un span parent

Pour comprendre les spans dans le tracing distribué, vous devez connaître les concepts suivants :

  • La durée de trace est la durée totale de la trace, déterminée par le temps pris du début du premier span jusqu'à la fin du dernier span.
  • Un span d'entrée de processus est le premier span dans l'exécution d'un bloc de code logique, tel qu'un service backend ou une fonction Lambda.
  • Un span de sortie de processus est un span qui est soit le parent d'un span d'entrée, soit un appel externe lorsqu'il a des attributs préfixés par http ou db.
  • Un span en cours représente un appel ou une fonction de méthode interne qui n'est ni un span de sortie ni d'entrée.
  • Un span client représente un appel à une autre entité ou dépendance externe. Actuellement, il y a deux types de span client. Tout d'abord, les spans client datastore ont des attributs avec db. comme préfixe alors que les spans client externes ont un préfixe http. ou un span enfant dans un autre processus.

Voici un exemple provenant de la documentation New Relic sur la façon dont les données de traces sont structurées :

En savoir plus sur les transactions

Une transaction est une unité logique de travail dans une application logicielle. Elle fait référence en particulier aux appels et méthodes de fonction qui composent cette unité de travail. Dans le contexte du monitoring des performances des applications (APM), elle fait souvent référence à une transaction web qui représente l'activité à partir du moment où l'application reçoit une requête web et jusqu'à l'envoi de la réponse.

Dans son article de blog expliquant le tracing distribué, Erika Arnold décrit les trois principales façons dont le tracing distribué utilise les transactions :

  • Analyse des transactions : le tracing suit les transactions qui se produisent dans tout le système pour obtenir les détails sur les performances. Chaque transaction joue un rôle au niveau des performances et les services non performants ont des répercussions sur les autres services. 
  • Enregistrement des transactions : le tracing aide à suivre de nombreuses transactions. Le contexte du tracing qui arrive dans un service avec une requête est propagé à d'autres processus et rattaché aux données de transaction. Avec ce contexte, vous pouvez assembler les transactions plus tard. Avec l'évolution du secteur qui passe des applications monolithiques aux microservices, il est de plus en plus important de suivre les transactions au-delà des frontières de processus où vous ne pouvez pas installer d'agents APM.
  • Description des transactions : le tracing aide à mesurer les transactions en fournissant des informations sur, par exemple, les transactions qui ont eu lieu et leur durée.

Passage du contexte des traces entre les services

Le contexte des traces fait référence à un ensemble d'en‑têtes HTTP dans New Relic qui propage les données d'un service à un autre pour composer les traces de bout en bout. Les agents de monitoring ajoutent ces en‑têtes HTTP aux requêtes de sortie d'un service. Les en‑têtes HTTP identifient les traces logicielles et portent des informations les identifiant lorsqu'elles passent dans les différents réseaux, processus et systèmes de sécurité. Ces en‑têtes incluent :

  • Chaque span de trace a un attribut guid. L'attribut guid du dernier span dans le processus est envoyé avec la requête sortante, afin que le premier segment de travail dans le service de réception puisse ajouter cet attribut guid à l'attribut parentId.
  • Le type de parent est la source de l'en‑tête de la trace (appareil mobile, navigateur ou application Ruby). Il devient l'attribut parent.type sur la transaction déclenchée par la requête.
  • timestamp est l'horodatage UNIX en millisecondes de la création de la charge.
  • traceId est l'identifiant unique utilisée pour identifier une seule requête au fur et à mesure qu'elle traverse les frontières (ou limites) entre processus et intraprocessus. Cet identifiant aide à relier les spans dans une trace distribuée. 
  • transactionId est l'identifiant unique pour l'événement de transaction.
  • priority est le numéro de classement par ordre de priorité généré de manière aléatoire qui aide à déterminer les données échantillonnées lorsque les limites d'échantillonnage sont atteintes. 
  • La valeur booléenne échantillonnée indique à l'agent si les données tracées doivent être collectées pour la requête. Ces transactions échantillonnées pour une trace complète reçoivent une valeur true pour l'attribut échantillonné, qui se propage en aval pour indiquer à tous les autres agents APM touchés par la trace de collecter les spans. Ces spans en aval reçoivent aussi une valeur true pour l'attribut échantillonné.

Utilisation du standard W3C Trace Context dans un environnement distribué

Que se passe-t-il si vous utilisez plusieurs outils dans votre environnement ? Lorsque le contexte de trace n'est pas standardisé, vos traces ne peuvent pas être corrélées ni propagées lorsqu'elles traversent les frontières entre les outils de différents fournisseurs. Si vous utilisez un environnement distribué avec plusieurs services middleware et plateformes cloud, ce problème est critique. Le standard W3C Trace Context définit un « format universellement accepté pour l'échange des données de propagation du contexte des traces ». 

Le standard améliore les problèmes d'interopérabilité en fournissant :

  • Un identifiant unique pour les traces et requêtes individuelles
  • Un mécanisme convenu pour acheminer les données de trace spécifiques au vendeur et éviter les traces cassées lorsque plusieurs outils de tracing participent à une transaction simple.
  • Un standard du secteur que les fournisseurs intermédiaires, de plateformes et matériels peuvent prendre en charge.

Pour adhérer à ce standard, les outils de tracing doivent interagir avec le contexte des traces en propageant les en‑têtes traceparent et tracestate pour garantir que les traces ne sont pas cassées. New Relic le met en œuvre en utilisant ses agents W3C, qui envoient et reçoivent les deux en‑têtes requis. L'agent envoie et reçoit également l'en‑tête de l'agent New Relic précédent. Le contexte de la trace pris en charge par New Relic comprend :

  • L'en‑tête W3C traceparent qui identifie toute la trace (identifiant de trace) et le service d'appel (identifiant de span). Il décrit la position de la requête entrante dans son graphique de trace dans un format portable, de taille fixe. Chaque outil de tracing doit correctement définir traceparent même lorsqu'il dépend d'informations spécifiques au fournisseur dans tracestate.
  • L'en‑tête W3C tracestate porte les informations spécifiques au fournisseur et fait le suivi du chemin de la trace. L'en‑tête tracestate continue traceparent en apportant des données spécifiques au fournisseur représentées par un ensemble de paires nom/valeur. Le stockage des informations dans tracestate est facultatif.
  • L'en‑tête exclusif de New Relic est l'en‑tête exclusif d'origine qui est utilisé pour garantir la rétrocompatibilité avec les agents New Relic précédents.

Voici un exemple de scénario provenant de la documentation New Relic How trace context is passed between applications (Comment est passé le contexte des traces entre les applications). Il montre le flux lorsqu'une requête touche un traceur OpenTelemetry, un agent New Relic qui utilise le standard W3C Trace Context et un agent New Relic plus ancien avant le standard W3C Trace Context.

Le diagramme de tracing distribué montrant le flux des en‑têtes quand une requête touche trois différents types d'agents

Échantillonnage des traces : en début et en fin de workflow

L'échantillonnage des traces est une technique utilisée dans le tracing distribué pour réduire la quantité de données de traces collectée et stockée. L'échantillonnage des données de trace réduit les frais généraux associés au tracing distribué et fournit un échantillon représentatif des performances du système. Il existe deux méthodes d'échantillonnage de trace : l'un en début et l'autre en fin de workflow.

Échantillonnage en début de workflow

L'échantillonnage en début de workflow décide de sélectionner des traces de manière aléatoire pour la collecte et le stockage au début de la trace. Utilisez-le pour capturer un échantillon représentatif de l'activité tout en évitant les problèmes de stockage et de performances. L'origine de la trace (le premier service monitoré dans une trace distribuée) choisit les requêtes à suivre aléatoirement et cette décision se propage aux services en aval touchés par cette requête, ce qui rend tous les spans dans la trace disponibles dans l'outil de tracing. 

Cela comprend aussi l'échantillonnage adaptatif, une technique appliquée à l'échantillonnage en début de workflow qui permet aux agents APM d'adapter la limite sur le nombre de transactions collectées en fonction des changements de débit des transactions. Si la limite est de 10 traces par minute, l'agent répartit la collecte de ces 10 traces sur une minute pour obtenir un échantillon représentatif dans le temps. Le rythme dépend du débit des transactions. Ainsi, si au cours de la minute précédente, il y a eu 100 transactions, l'agent anticipe un nombre similaire de transactions et en sélectionne une toutes les 10 transactions à tracer.

Échantillonnage en fin de workflow

À la différence de l'échantillonnage en début de workflow, les décisions de rétention de traces de l'échantillonnage en fin de workflow s'effectuent après que les spans dans une trace sont arrivés — à la toute fin. 

Le pour et le contre de l'échantillonnage en début ou en fin de workflow

 

Échantillonnage en début de workflow

Échantillonnage en fin de workflow

Pour

  • Fonctionne pour les applications avec un débit de transaction plus faible
  • Mise en route rapide et simple
  • Convient aux environnements monolithiques et microservices mixtes
  • Peu ou pas d'impact sur les performances des applications
  • Solution peu coûteuse pour l'envoi des données de trace à des outils tiers
  • L'échantillonnage statistique assure une transparence suffisante sur le système distribué
  • Observe et analyse 100 % des traces
  • Échantillonne les traces une fois qu'elles sont terminées
  • Visualise plus rapidement les traces avec des erreurs ou un ralentissement atypique 

Contre

  • Les traces sont échantillonnées de manière aléatoire
  • L'échantillonnage se produit avant qu'une trace ait terminé son parcours dans les différents services, et il n'y a donc aucun moyen de savoir à l'avance quelle trace a rencontré un problème
  • Dans les systèmes à haut débit, les traces avec des erreurs ou une latence anormale peuvent ne pas être échantillonnées et donc manquer
  • Peut exiger des passerelles, proxies et satellites supplémentaires pour exécuter le logiciel d'échantillonnage
  • Demande parfois un travail de gestion et d'évolution des logiciels tiers
  • Entraîne des frais supplémentaires pour la transmission et le stockage de plus de données.

 

Choosing a distributed tracing tool

Selecting the right distributed tracing tools is paramount for achieving clear visibility into application performance. A distributed tracing tool should offer more than just data collection; it should empower engineers by transforming raw data into actionable insights. When choosing such a tool, consider the following:

  • Transparency in performance claims: Ensure the tool provides accurate and honest insights about its performance capabilities.
  • Straightforward pricing: Look for clear, no-fluff pricing models that align with your usage needs and offer real value.
  • Actionable insights: Choose a tool that turns data into practical, empowering insights, facilitating informed decision-making.
  • Comprehensive language support: The tool should support a wide range of programming languages to accommodate diverse development environments.
  • Scalability: Opt for a solution that can efficiently scale with your infrastructure as it grows and evolves.
  • Real-time visualization: It's crucial to have a tool that offers real-time visualizations of your system’s health for immediate insights.
  • Alignment with team needs: The tool should resonate with your team's unique operational requirements and foster an environment conducive to learning and innovation.
  • Streamlined troubleshooting: Prioritize tools that simplify the process of identifying and resolving performance issues.
  • Continuous learning and optimization: Look for features that encourage ongoing system optimization and learning opportunities for the team.