Identifier les causes de la latence d'une application avec Cloud Monitoring et OpenCensus

Ce document vous aide à identifier les causes de la latence de queue, en faisant appel à OpenCensus et Cloud Monitoring afin de surveiller les métriques et le traçage distribué pour les développeurs d'applications.

Lorsque vous observez votre application qui répond aux utilisateurs, vous pouvez constater que certains utilisateurs subissent une latence plus élevée que d’autres. Les différences sont d'autant plus visibles lorsque les populations d'utilisateurs sont plus diversifiées et les ressources utilisées de manière plus intensive. Identifier et traiter les causes d'une latence élevée peut vous aider à étendre votre base d'utilisateurs à une population plus large tout en utilisant vos ressources de manière plus optimale. Si l'utilisation de vos ressources croît, la latence de queue est le premier signe à émerger en raison des conflits de ressources.

Bien que le nombre d'utilisateurs concerné par ces latences élevées puisse être faible, ils peuvent néanmoins être importants, par exemple s'ils représentent l'avant-garde d'un nouveau marché potentiel.

Ce document explique comment identifier et expliquer les sources d'une latence élevée subie par ces utilisateurs. En particulier, cet article répond aux questions suivantes :

  • Comment mesurer la latence de queue avec précision ?
  • Quelles sont les principales causes de latence des applications ?
  • Comment évaluer les stratégies visant à minimiser la latence de queue ?

Les principaux thèmes répondant à ces questions sont les suivants :

  • Identifier les sources d'imprécision dans les estimations des métriques de surveillance.
  • Identifier les causes d'une latence élevée.
  • Minimiser la latence de queue.

Le code source de l'application de test est mis à disposition dans un dépôt GitHub.

Dans ce document, nous partons du principe que vous maîtrisez les bases d'OpenCensus, de Cloud Monitoring, de Cloud Trace, de BigQuery et du développement d'applications en Java.

Terminologie

métrique
Une mesure des performances de l'application.
latence
Le délai nécessaire à l'application pour effectuer une tâche. Dans ce document, les valeurs de latence sont mesurées à partir du code de l'application.
latence de queue
Les valeurs de latence les plus élevées subies par un petit groupe d'utilisateurs. La latence de queue peut être définie en termes de centiles. Par exemple, le 99e centile correspond au 1 % des valeurs de latence les plus élevées pour les requêtes évaluées dans un intervalle de temps donné. Pour en savoir plus, consultez la section "Worrying about your Tail" (Prendre soin de votre latence de queue) au chapitre "Monitoring Distributed Systems" (Surveillance des systèmes distribués) du manuel d'Ingénierie en fiabilité des sites (SRE).
modèle d'agrégation
Une représentation des métriques de surveillance qui montre la distribution statistique des mesures collectées. Ce document présuppose un modèle de type histogramme où la plage de latences est divisée en buckets et les décomptes correspondant à chaque compartiment sont enregistrés pour chaque intervalle de temps. Cloud Monitoring utilise le terme de "métrique de distribution" pour les métriques utilisant ce type de représentation.

Contexte

En général, les propriétaires d'applications cloud surveillent une combinaison des valeurs de latence médiane et de latence de queue. La latence médiane est un bon indicateur de l'état général de votre application. Cependant, à mesure que celle-ci évolue, des problèmes subtils peuvent apparaître et ne sont pas facilement observables via la latence médiane.

Comprendre la latence de queue peut être utile à plusieurs fins :

  • Détecter tôt les problèmes émergents que la latence médiane ne laisse pas transparaître.
  • S'assurer que, lorsque vous atteignez une utilisation élevée des ressources, ce n'est pas au prix d'une gestion sous-optimale de certaines requêtes.
  • Surveiller une flotte lorsqu'un serveur isolé n'est pas opérationnel ou lorsqu'un chemin de code suivi par une minorité d'utilisateurs n'est pas géré correctement.
  • Alimenter les stratégies visant à réduire la latence de queue, telles que la définition de valeurs appropriées pour les délais d’expiration et intervalles entre tentatives répétées. Vous pouvez choisir de régler le délai d'expiration sur une valeur légèrement supérieure à la latence du 99e centile, afin d'abandonner les connexions et de provoquer une nouvelle tentative plutôt que de laisser des requêtes en attente pendant une durée excessive.

La latence de la queue peut avoir de nombreuses causes. Parmi les cas typiques, citons :

  • Des variations de la taille de la charge utile des requêtes
  • Une utilisation élevée du processeur
  • Une structure non optimale du code de l'application
  • Des variations de la distance d'acheminement des appels réseau
  • Des redémarrages inattendus et occasionnels du serveur, donnant lieu à des interruptions de connexions et à des démarrages à froid
  • Des mises en mémoire tampon ou en file d'attente
  • La gestion des connexions, par exemple pour rétablir des connexions ayant expiré
  • L'encombrement du réseau, y compris la perte de paquets
  • La récupération de mémoire ou le compactage des données occasionnant une suspension du traitement des requêtes utilisateur
  • La latence variable des services cloud
  • Des paramètres non optimaux pour l'expiration des connexions

Les stratégies permettant d'identifier les causes de latence incluent les suivantes :

  • La définition de méthodes permettant de mesurer la latence avec précision : la latence de queue représentant, par définition, une faible proportion du trafic, la précision des mesures est une nécessité qui doit être bien comprise.
  • La mise en œuvre de solutions de surveillance, de traçage et de journalisation : à l'aide d'OpenCensus et de Cloud Monitoring, l'application peut créer pour les requêtes une trace du délai racine contenant les délais enfants pour différentes mesures. Les délais enfants sont ensuite automatiquement générés avec l'intégration HTTP d'OpenCensus, et avec l'intégration de la trace dans les journaux pour enregistrer les éléments de contexte supplémentaires. Cela minimise le code supplémentaire nécessaire à la surveillance et au traçage.
  • L'exploration des données : examinez les rapports et les graphiques traduisant la queue des requêtes les plus longues et identifiez les délais enfants dont l'exécution prend le plus de temps.
  • L'identification des causes de la latence de queue : recueillez des informations auprès des sources associées qui peuvent contribuer à expliquer les raisons de délais enfants excessivement longs, et comparez-les avec des annotations dans des exemples de délais.
  • Une bonne compréhension des requêtes les plus détaillées : exportez les journaux vers BigQuery et combinez-les avec les informations de trace pour explorer et quantifier le comportement de l'application.

L’analyse présentée dans ce document résulte de l’analyse de systèmes à trafic élevé et soumis à un scaling horizontal à l'aide de Cloud Storage et de microservices. Les erreurs de mesure, le volume de la charge utile, la latence variable dans les services cloud et la charge sur les machines virtuelles hébergeant l'application représentaient les principales causes de variabilité de la latence de queue.

Les utilisateurs cloud ont le choix entre utiliser des métriques provenant d’intégrations prédéfinies incluses dans des bibliothèques, des métriques proposées par les fournisseurs cloud et des métriques personnalisées. Les intégrations prédéfinies incluent par exemple les intégrations gRPC, HTTP et SQL d'OpenCensus. Google Cloud inclut un grand nombre de métriques compatibles, qui sont exposées via Cloud Monitoring. Une solution alternative consiste à développer du code pour créer des métriques personnalisées supplémentaires, souvent des minuteurs et des compteurs. Comparer les séries temporelles issues de ces trois sources est une tâche courante dans l'analyse des sources de latence au sein des microservices et du stockage des données d'application.

Présentation de l'application de test

L'application de test simule les problèmes de latence de queue et s'intègre à OpenCensus et à Cloud Monitoring afin d'exposer les outils et les approches nécessaires pour identifier les problèmes. Le code source, les fichiers de compilation et les instructions sont fournis dans le projet GitHub. L'exemple d'application est écrit en Java, mais vous pouvez appliquer le contenu de ce document à d'autres langages. Vous pouvez en apprendre davantage sur l'instrumentation OpenCensus pour les requêtes HTTP dans les articles Jetty Integration for Java (intégration Jetty pour Java) et Go. L'application de test est basée sur cet exemple de code GitHub.

La figure ci-dessous représente un schéma de l'application.

Diagramme d'architecture de l'application cliente communiquant avec Cloud Storage et de l'application serveur sur Compute Engine.

L'application cliente communique avec Cloud Storage et avec une application côté serveur hébergée sur une instance Compute Engine.

Voici un flux de requête typique :

  1. Dans l'application cliente, l'API Cloud Storage extrait un fichier JSON de Cloud Storage.
  2. Le contenu du fichier est envoyé au serveur.
  3. Le serveur effectue un traitement sur les données envoyées par le client pour simuler le temps et les ressources que nécessiterait une application réelle.
  4. Une réponse HTTP est renvoyée au client.
  5. Le client effectue un traitement en aval. À ce stade, l'application de test remplace la logique métier par une simple opération arithmétique.

Au démarrage, l'application initialise les éléments suivants :

  • Le client Java Util Logging (JUL), afin d'exporter Cloud Logging en incluant les ID de trace. Le serveur utilise un appender de journalisation Logback pour assurer la corrélation entre traces et journaux.
  • Les vues des métriques HTTP OpenCensus sont enregistrées. L’intégration HTTP pour Java d'OpenCensus comprend un certain nombre de vues de métriques relatives à la latence, aux codes de réponse HTTP et aux octets envoyés et reçus.
  • L'exportateur stats Monitoring d'OpenCensus envoie des métriques agrégées à Cloud Monitoring dans un thread d'arrière-plan.
  • L'exportateur de traces Monitoring d'OpenCensus envoie des délais de trace à Cloud Monitoring dans un thread d'arrière-plan.
  • L'API cliente Cloud Storage télécharge les fichiers utilisés pour les charges utiles HTTP.
  • Un pool de threads d'application est créé pour simuler la charge dans l'application. L'utilisation de plusieurs threads entraîne sur le serveur une charge supérieure à celle d'un thread unique.

Les métriques de surveillance et les données de trace sont collectées et traitées à l'aide des étapes suivantes :

  1. Les bibliothèques OpenCensus collectent des métriques et des données de trace.
  2. OpenCensus agrège régulièrement les données de métriques et les exporte vers Cloud Monitoring. Les données de trace sont exportées vers Cloud Trace sans agrégation.
  3. OpenCensus ajoute des ID de trace aux entrées Cloud Logging. Les entrées de journal sont exportées vers le service Cloud Logging.

Vous pouvez exporter les métriques collectées à l'aide d'OpenCensus vers un certain nombre de systèmes de surveillance backend offrant des capacités de stockage, d'analyse, de visualisation et d'alerte. Chaque système possède un format de représentation de données différent. Ce document se concentre sur Cloud Monitoring.

Précision des métriques de surveillance agrégées

OpenCensus et Cloud Monitoring fournissent des outils que vous pouvez utiliser pour surveiller les performances des applications. Pour éviter d'avoir une confiance exagérée dans les courbes des graphiques, vous devez comprendre comment celles-ci sont générées, en particulier en ce qui concerne les courbes relatives à la latence de queue telle que le 99e centile (p99). Les sections suivantes examinent les limites de l'agrégation des données de métriques.

Représentation des séries temporelles de métriques dans Cloud Monitoring

Cloud Monitoring calcule les centiles de latence pour les métriques de distribution à partir des limites des buckets selon des intervalles numériques. C'est une méthode commune à Cloud Monitoring et OpenCensus, dans laquelle OpenCensus représente et exporte les données de métriques vers Cloud Monitoring. La méthode TimeSeries.list pour l'API Cloud Monitoring renvoie les décomptes et les limites associés à chaque bucket pour votre type de projet et de métriques. Vous pouvez récupérer les limites de bucket dans l'objet BucketOptions de l'API Cloud Monitoring, que vous pouvez tester dans l'explorateur d'API pour TimeSeries.list.

Pour filtrer sur la base de la latence du client HTTP OpenCensus dans l'explorateur d'API, vous pouvez utiliser le filtre suivant :

resource.type=global metric.type="custom.googleapis.com/opencensus/opencensus.io/http/client/roundtrip_latency"

Le résultat comprend un tableau d'objets de points de données. L'exemple suivant montre le schéma des buckets et leurs décomptes respectifs :

"points": [ {
  "interval": { "startTime": "2019-02-14T18:11:24.971Z",
                "endTime": "2019-02-14T18:33:35.541Z" },
  "value": {
    "distributionValue": {
...
    "explicitBuckets": {
      "bounds": [0, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, ...] }
              },
      "bucketCounts": [0, 0, 0, 0, 0, 14, 17, 164, 3, 0, 1, 0, 0, 0, 0, ...]
}}]

Chaque point apparaissant dans un graphique Monitoring pour représenter une valeur de latence est dérivé d'une distribution. Vous pouvez définir les buckets à l'aide d'un facteur de croissance au lieu de définir des limites pour chaque bucket. Ainsi, un facteur de croissance de 2,0 créerait une série de buckets de limites respectives 0-1, 1-2, 2-4, 4-8, etc. Pour en savoir plus sur les métriques Cloud Monitoring, consultez la page Structure des types de métriques.

Vous pouvez modifier le graphique pour afficher les courbes correspondant aux latences de 50e centile (médiane), 95e centile et 99e centile, ce qui aboutit au graphique illustré ci-dessous.

Graphique de latence de queue montrant les 50e, 95e et 99e centiles.

Ajoutez le type de graphique "Heatmap" ou carte de densité si vous souhaitez mieux comprendre les données utilisées pour calculer ces valeurs de centiles. Lorsque l’agrégateur est défini sur l'option somme, la Client latency heatmap (carte de densité de la latence client) suivante s'affiche.

Carte de densité de la latence de queue.

Les cartes de densité sont un moyen d'afficher les données de distribution stockées par Cloud Monitoring. Les zones de la carte de densité correspondent aux limites de bucket extraites à l'aide de l'API REST. Vous pouvez placer le pointeur de la souris sur une zone de la carte de densité afin d'afficher le nombre de points contenus dans le bucket correspondant pour une période donnée. Les données utilisées pour calculer les valeurs de centile du graphique en courbes sont dites "creuses".

Si vous souhaitez obtenir des mesures de latence plus précises, plusieurs stratégies s'offrent à vous :

  • Ajouter davantage de données. Vous pouvez générer plus de données dans le cadre d'expériences, mais le trafic associé à une application en production risque d'être insuffisant.
  • Réduire la taille des buckets et modifier leurs limites pour qu'elles correspondent mieux à vos données. C'est une option intéressante si vous créez vos propres métriques personnalisées. Cependant, vous ne pourrez peut-être pas effectuer ces ajustements si vous utilisez des données issues d'intégrations OpenCensus prédéfinies ou collectées par Google Cloud à partir de ses services de backend. Les limites des buckets pour les intégrations OpenCensus sont déterminées par les développeurs de ces intégrations dans les vues. Par exemple, les limites des buckets de latence pour les clients HTTP sont définies dans la classe HttpViewConstants.java. De même, les métriques Google Cloud sont définies en interne dans Cloud Monitoring.
  • Utilisez des traces ou des journaux au lieu des données de métriques pour les centiles de queue. Ces options sont explorées dans les sections suivantes de ce document.

Données simulées

Vous pouvez comparer la précision des modèles d'agrégation avec des données simulées brutes tirées d'une distribution aléatoire afin d'observer les différences occasionnées par les limites de buckets. Dans cet exemple, une distribution bêta modélise la distribution asymétrique typique des valeurs de latence réelle. Il s'agit d'une distribution d'une variable aléatoire continue présentant des valeurs uniquement positives, un pic distinct et une longue queue décroissante. La distribution bêta utilise la fonction random.beta de NumPy avec les paramètres a = 2, b = 5. Elle est multipliée par un facteur constant de 70 pour simuler des valeurs de latence réalistes avec une valeur médiane de 18,5 ms. La simulation comprend 100 000 valeurs aléatoires issues de la distribution bêta de la feuille Colab tail_latency_lab.ipynb incluse dans le projet GitHub.

Le graphique ci-dessous montre que, lorsque growthFactor = 2.0, les buckets sont définis de manière trop grossière pour donner des estimations précises de la latence de queue.

Modèle agrégé présentant un facteur de croissance de 2,0.

L'axe linéaire sur le graphique montre la forme attendue pour la distribution aléatoire. Le graphique suivant illustre les limites de bucket définies avec growthFactor = 1.4.

Modèle agrégé présentant un facteur de croissance de 1,4.

Le graphique ci-dessous montre que les limites de bucket définies avec growthFactor = 1.2 produisent une distribution ressemblant davantage à la forme souhaitée, mais qui reste assez grossière dans la plage de valeurs supérieures.

Modèle agrégé présentant un facteur de croissance de 1,2.

Le tableau suivant présente des estimations fondées sur la distribution à base de buckets, à comparer au calcul réalisé à partir des valeurs brutes.

Distribution Médiane 99e centile
Brut 18,5 49,5
Facteur de croissance de 2,0 19,2 62,0
Facteur de croissance de 1,4 18,7 53,9
Facteur de croissance de 1,2 18,6 51,1

Dans ce cas de figure, un facteur de croissance de 1,2 est nécessaire pour atteindre une estimation raisonnablement précise de la latence du 99e centile. À partir de l'exportateur OpenCensus, vous pouvez convertir les valeurs brutes en une distribution échantillonnée. Lorsque vous créez une vue OpenCensus, vous pouvez configurer les limites des buckets. Les vues contiennent un objet Aggregation. Pour en savoir plus, consultez la documentation Java ou la documentation Go.

Mesurer la latence HTTP avec OpenCensus

Lorsque vous mesurez la latence HTTP, vous mesurez le temps écoulé entre l'émission d'une requête HTTP par un client et la réception de cette requête par un serveur, puis enregistrez ce résultat. Vous pouvez mettre en œuvre cette opération de mesure dans votre code soit en créant un Tag OpenCensus, soit en utilisant l'intégration prédéfinie pour les clients HTTP. L'intégration HTTP pour Go est fournie par le biais du package HTTP de Go, et pour Java au travers du client HTTP Jetty. L’intégration HTTP d'OpenCensus présente l’avantage de ne pas nécessiter l'ajout d’objets Tag pour chaque métrique à collecter.

Identifier les sources de latence

L'application de test décrite ci-dessus a auto-généré un stress entraînant une latence de queue accrue. Nous illustrons ici les problèmes suivants :

  • Des charges utiles parfois très volumineuses
  • Une utilisation élevée du processeur
  • Des délais d'expiration courts
  • Des variations, au cours des différentes tentatives, dans le chemin du code de l'application

Dans les sections suivantes, nous allons voir comment identifier et quantifier ces problèmes.

Effet d'une charge utile volumineuse

Pour étudier l'effet du volume de la charge utile, le code suivant accroît de manière aléatoire la taille de la charge utile envoyée dans une requête HTTP sur 20 :

static byte[] getContent(String bucket) {
  BlobId blobId = null;
  int n = rand.nextInt(100);
  if (n >= 95) {
    blobId = BlobId.of(bucket, LARGE_FILE);
  } else {
    blobId = BlobId.of(bucket, SMALL_FILE);
  }
  return storage.readAllBytes(blobId);
}

Lorsque vous affichez les traces associées aux requêtes présentant des latences élevées dans la Trace list (Liste de traces), celles-ci peuvent être corrélées au volume de la charge utile.

Liste de traces indiquant les requêtes à latence élevée.

Si vous cliquez sur Afficher les événements, vous pouvez faire apparaître davantage de détails sur les charges utiles envoyées et reçues. Si la latence est élevée, ces détails montrent que le volume de la charge utile est plus important.

Pour identifier la latence, Cloud Trace est préférable à Cloud Monitoring pour les raisons suivantes :

  • Trace fournit les valeurs de latence pour les requêtes plutôt que des valeurs agrégées.
  • Trace indique la taille de la charge utile pour la requête.

Effet d'une utilisation élevée du processeur

Une utilisation élevée du processeur peut accroître la latence de queue en raison des traitements simultanés susceptibles d'entraîner un conflit de ressources et une mise en file d'attente des requêtes à traiter par le processeur. Cloud Monitoring propose une métrique intégrée d'utilisation du processeur pour les instances Compute Engine. Toutefois, lorsque vous exécutez des tests de performance ou enquêtez sur des problèmes de performance, nous vous recommandons d'installer l'agent Monitoring car celui-ci fournit davantage de métriques liées au système d'exploitation et Open Source, notamment pour l'utilisation de la mémoire et des disques, ainsi que d'autres métriques pouvant faire apparaître différents types de conflits de ressources. Pour plus d'informations, consultez la liste des métriques de l'agent.

Gérer l'utilisation du processeur est plus simple pour les applications serveur que pour les applications clientes du fait du scaling automatique. Sur Compute Engine, les groupes d'instances gérés peuvent utiliser des règles d'autoscaling basées sur l'utilisation moyenne du processeur, la capacité de diffusion de l'équilibrage de charge ou les métriques Cloud Monitoring. Ces stratégies ajoutent des machines virtuelles pour dimensionner la flotte en adéquation avec la charge. L’utilisation du processeur côté client peut être plus difficile à gérer de manière cohérente et peut affecter la mesure de la latence, car les charges utiles doivent être lues depuis le réseau et désérialisées, ce qui fait appel au processeur. Étant donné que c'est la latence telle que perçue par l'application cliente qui, dans une large mesure, détermine la qualité de l'expérience utilisateur, ce document se concentre sur ce type de latence.

La surveillance est plus appropriée que le traçage pour identifier l'effet d'une utilisation élevée du processeur, car cela ne résulte généralement pas d'une requête HTTP. Dans l'exemple suivant, l'effet de l'utilisation du processeur est étudié en exploitant l'application de test pour exécuter simultanément plusieurs threads exécuteurs, tout en exécutant le code client sur une machine virtuelle g1-small. Ensuite, l'utilisation du processeur et la latence sont comparées aux valeurs obtenues lorsque l'application cliente s'exécute sur une VM à processeur virtuel n1-standard-1.

Les graphiques suivants illustrent les valeurs médiane et au 95e centile de la latence de bout en bout pour une expérience donnée. Le taux de requêtes a été augmenté à 03h21 par suppression d'une instruction de veille entre les requêtes, afin de bien faire apparaître la différence de latence à l'occasion d'une augmentation drastique de l'utilisation du processeur. Les commandes permettant de créer les machines virtuelles et d'exécuter les tests sont disponibles dans le dépôt GitHub.

Graphique de la latence médiane par ID d'instance.

Le graphique précédent illustre la latence médiane pour le client ainsi que pour le serveur, au cours d'une expérience de montée en charge. Remarquez l'augmentation à 03h21 au moment de la montée en charge. Le graphique suivant illustre la latence au 95e centile au cours du même test.

Graphique du 95e centile de latence par ID d'instance.

Le tableau suivant montre l'utilisation du processeur au cours du test. Il présente là encore une augmentation notable à 03h21.

Graphique d'utilisation du processeur de l'instance Compute Engine.

Augmenter le taux de requêtes affecte la latence, mais d'une manière qui n'est pas proportionnelle à l'utilisation du processeur. Il est quelque peu surprenant que la latence soit cohérente entre la machine à un processeur virtuel et la petite instance, même si cette dernière fonctionnait à environ 78 % d'utilisation du processeur. Ce résultat suggère que vous pouvez exécuter une flotte présentant une utilisation élevée du processeur. Toutefois, nous mettons ici en garde contre le fait que ces tests ne constituent pas une analyse comparative, et que vos résultats peuvent varier lors de la mise en œuvre de l'application.

Cloud Profiler est un outil utile pour étudier les cas d'utilisation élevée du processeur.

Latence due aux branches du code de l'application et aux valeurs de paramètres

La journalisation peut vous aider à résoudre les problèmes les plus difficiles en matière de latence des applications. Cette section explique comment combiner des données de journal et de trace pour examiner les problèmes de latence suscités par la variation des chemins de code et les valeurs de certains paramètres. Une approche allant de la trace au journal évite l'insertion de nombreuses instructions de chronométrage dans votre code. Le problème le plus courant dans les applications basées sur des microservices est la gestion des délais d’expiration et des tentatives répétées. Des délais d'expiration excessifs peuvent dégrader l'expérience utilisateur et conduire à l'effondrement d'une application s’ils retiennent trop de connexions. Nous vous recommandons de définir un délai d'expiration court, d'attendre un moment, puis de réessayer. Cependant, la répétition agressive des tentatives peut conduire à des "tempêtes de nouvelles tentatives" et rendre encore plus difficile la récupération en cas d'incident. Vous devez définir le délai d'expiration de manière à ce que seul un faible pourcentage des requêtes, par exemple moins de 1 %, nécessite de nouvelles tentatives. Notez toutefois que l'application de test définit le délai de nouvel essai à une valeur faible pour les requêtes HTTP envoyées au serveur de test, afin d'illustrer les nouvelles tentatives en action sans avoir à attendre qu'une véritable panne se produise.

Les tentatives répétées sont également intégrées à la bibliothèque cliente de l'API Google pour Cloud Storage. Vous n'avez donc pas besoin de les mettre en œuvre vous-même dans votre propre code. Pour plus d'informations sur l'intervalle exponentiel entre les tentatives, consultez la documentation Intervalle exponentiel entre les tentatives tronqué.

Avec l'intégration OpenCensus des corrélations avec les journaux Cloud Monitoring, vous pouvez afficher les entrées de journal à partir de traces. Cette intégration utilise un outil d'enrichissement Enhancer Cloud Logging pour ajouter des informations de trace aux journaux. Vous pouvez l'ajouter à votre application en configurant Cloud Logging pour Java et en ajoutant la dépendance Maven opencensus-contrib-log-correlation-stackdriver 1 à votre build. Au moment où nous rédigeons ce document, l'intégration des journaux avec Go n'est pas mise en œuvre, mais vous pouvez demander cette fonctionnalité. L'alternative actuelle pour Go consiste à ajouter des annotations pour tracer les délais avec les informations requises.

Dans l'application de test, la méthode servant à préparer, envoyer et traiter les requêtes adressées au microservice est la suivante :

private void prepareSendProcess(
    HttpClient httpClient,
    HttpMethod method,
    Function<Integer[], Integer> downStreamFn,
    String fnName)
    throws InterruptedException {
  Tracer tracer = Tracing.getTracer();
  try (Scope scope = tracer.spanBuilder("main").startScopedSpan()) {
    StopWatch s = StopWatch.createStarted();
    byte[] content = new byte[0];
    if (method == HttpMethod.POST) {
      content = TestInstrumentation.getContent(testOptions.bucket());
    }
    byte[] payload = sendWithRetry(httpClient, method, content);
    TestInstrumentation.processPayload(payload, downStreamFn, fnName);
    TestInstrumentation.recordTaggedStat(
        method.toString(), s.getTime(TimeUnit.NANOSECONDS) / 1.0e6);
  }
}

La méthode crée pour la requête un délai avec champ d'application en tant que trace de niveau supérieur, qui couvre le code nécessaire aux phases de préparation, d'envoi de la requête HTTP au serveur et de traitement en aval. La méthode prepareSendProcess appelle sendWithRetry, qui encapsule la requête HTTP dans un mécanisme de nouvelle tentative :

private byte[] sendWithRetry(HttpClient httpClient, HttpMethod method, byte[] content)
    throws InterruptedException {
  ExponentialBackOff backoff = new ExponentialBackOff.Builder()
    .setInitialIntervalMillis(500)
    .setMaxElapsedTimeMillis(5*60*1000)
    .setMultiplier(2.0)
    .setRandomizationFactor(0.5)
    .build();
  for (int i = 0; i < MAX_RETRIES; i++) {
    try {
      return sendRequest(httpClient, method, content);
    } catch (RetryableException e) {
      LOGGER.log(Level.WARNING, "RetryableException attempt: " + (i + 1) + " " + e.getMessage());
    } catch (InterruptedException e) {
      LOGGER.log(
          Level.WARNING, "InterruptedException attempt: " + (i + 1) + " " + e.getMessage());
    } catch (TimeoutException e) {
      LOGGER.log(Level.WARNING, "TimeoutException attempt: " + (i + 1) + " " + e.getMessage());
    } catch (ExecutionException e) {
      LOGGER.log(Level.WARNING, "ExecutionException attempt: " + (i + 1) + " " + e.getMessage());
    }
    try {
      Thread.sleep(backoff.nextBackOffMillis());
    } catch(IOException e) {
      throw new RuntimeException("MaxElapsedTime exceeded");
    }
  }
  throw new RuntimeException("Max retries exceeded");
}

La méthode suivante envoie la requête HTTP :

private byte[] sendRequest(HttpClient httpClient, HttpMethod method, byte[] content)
    throws InterruptedException, TimeoutException, ExecutionException, RetryableException {
  String targetURL = testOptions.targetURL();
  HttpRequest request = (HttpRequest) httpClient.newRequest(targetURL).method(method);
  if (request == null) {
    throw new RetryableException("Request is null");
  }
  if (method == HttpMethod.POST) {
    ContentProvider contentProvider =
        new StringContentProvider(new String(content, StandardCharsets.UTF_8));
    request.content(contentProvider, "application/json");
  }
  request.timeout(testOptions.httpTimeout(), TimeUnit.MILLISECONDS);
  ContentResponse response = request.send();
  int status = response.getStatus();
  LOGGER.info("Response status: " + status + ", " + method);
  if (HttpStatus.isSuccess(status)) {
    byte[] payload = response.getContent();
    LOGGER.info("Response payload: " + payload.length + " bytes");
    return payload;
  } else if (HttpStatus.isServerError(status)) {
    throw new RetryableException(response.getReason());
  }
  return new byte[0];
}

Cette méthode crée un objet HTTPRequest, puis ajoute la charge utile si une requête POST est envoyée. Les détails ci-dessous constituent un exemple de trace.

Exemple de trace pour un objet `HTTPRequest`.

Dans Cloud Monitoring, lorsque vous cliquez sur Afficher les journaux, les journaux apparaissent dans les détails de la trace. Lorsque vous cliquez sur une icône Détails de la trace, comme décrit dans Rechercher et afficher des traces, les informations sur cette trace s'affichent. Vous pouvez faire correspondre le texte du journal au code source afin de comprendre quel chemin de code a été suivi pour générer la trace visualisée, ainsi que pour connaître les détails d'autres paramètres, tels que la méthode HTTP et la réponse. L'exemple précédent montre qu'un délai a expiré dans le cadre de cette trace. Par la suite, le délai s'en trouve allongé car il est pénalisé par les intervalles entre tentatives. La trace présente également six nouvelles tentatives, soit le nombre maximal configuré.

Pour consulter les événements de la trace, cliquez sur Show Events (Afficher les événements).

Événements de trace présentant une latence élevée.

La cause de la latence élevée est ici le volume de la charge utile. Le délai d'expiration de 8 ms était trop faible pour gérer cette charge utile lourde. Il doit donc être fixé à une valeur plus élevée.

Vous pouvez ajouter aux délais de traces des annotations comportant des informations similaires aux instructions de journalisation. Toutefois, pour ajouter aux délais de trace des informations clés sur les différents chemins de code de votre application, vous devez insérer au code de celle-ci des éléments relatifs aux délais de trace. Si votre équipe de développement ne maîtrise pas le traçage, une solution plus simple consiste à les ajouter aux instructions de journalisation à l'aide de JUL ou Logback et de configurer Logging pour Java. De plus, il est possible que votre code intègre déjà des instructions de journalisation.

Pour identifier les requêtes les plus lentes, vous pouvez générer un rapport d'analyse de trace incluant des exemples de traces dans différents centiles de requêtes, comme illustré dans la capture d'écran suivante.

Rapport d'analyse des traces comprenant des exemples de traces dans différents centiles de requêtes.

Pour afficher des traces détaillées pour les centiles les plus élevés, vous pouvez cliquer sur les valeurs de la colonne Sample Traces (exemples de traces).

Combiner les instructions de journalisation et les ID de trace dans BigQuery

Pour explorer plus en profondeur les informations de journalisation, vous pouvez interroger les journaux directement dans la visionneuse de journaux Cloud Logging à l'aide d'un filtre de journal.

Requête de journal avec filtre de journal.

L'un des points intéressants de ce filtre est qu’il connecte trois entrées de journal à partir du même ID de trace. Vous pouvez connecter des entrées de journal correspondant à la même requête à l'aide des ID de trace. Vous pouvez ensuite exporter les entrées de journal vers BigQuery grâce à un filtre tel que resource.type="gce_instance".

Filtre de journal pour l'instance Compute Engine.

Si vous exportez les journaux vers un ensemble de données appelé application_test_dataset, vous pouvez ensuite en explorer le contenu à l'aide de la requête suivante :

SELECT
  TIME(timestamp) AS Time,
  SUBSTR(textPayload, 90) as Message,
  SUBSTR(trace, 31) as trace_id
FROM
  `application_test_dataset.java_log_20190328`
WHERE REGEXP_CONTAINS(textPayload, "attempt")
LIMIT 10;
...
Time             Message                            trace_id
10:59:11.782000  WARNING: TimeoutException attempt: 008a0ce...

Une ligne d'exemple de résultat est affichée ici. Le résultat de cette requête fournit des informations similaires à la visionneuse de journaux. Cependant, comme les données de journal sont maintenant disponibles dans BigQuery, vous pouvez effectuer des requêtes plus puissantes. La requête suivante calcule, pour différents nombres de tentatives, le nombre de requêtes correspondant :

SELECT
  RetryCount,
  COUNT(trace) AS RequestCount
FROM(
  SELECT
    COUNT(REGEXP_CONTAINS(textPayload, "attempt")) as RetryCount,
    trace
  FROM
    `application_test_dataset.java_log_20190328`
  WHERE NOT REGEXP_CONTAINS(trace, '00000000000000000000000000000000')
  Group BY trace
)
GROUP BY RetryCount
ORDER BY RetryCount DESC
LIMIT 10;
...
RetryCount RequestCount
8          13
7          180
6          2332
5          242
4          507
3          416605
1          1

Les résultats de cette requête montrent que 13 requêtes ont fait l'objet de 8 nouvelles tentatives, soit le nombre maximal configuré. Vous pouvez utiliser les résultats de cette requête pour affiner vos paramètres de nouvelles tentatives. Par exemple, vous pouvez augmenter le délai d'expiration, le modifier en fonction de la taille de la charge utile, ou encore augmenter le nombre maximal de nouvelles tentatives.

Étapes suivantes