Suivi des services, OpenTracing et Jaeger

Suivi des services, OpenTracing et Jaeger

Nous utilisons une architecture microservice dans nos projets. Lorsque des goulots d'étranglement de performances se produisent, beaucoup de temps est consacré à la surveillance et à l'analyse des journaux. Lors de la journalisation des minutages d'opérations individuelles dans un fichier journal, il est généralement difficile de comprendre ce qui a conduit à l'invocation de ces opérations, de suivre la séquence d'actions ou le décalage temporel d'une opération par rapport à une autre dans différents services.

Pour minimiser le travail manuel, nous avons décidé d'utiliser l'un des outils de traçage. Comment et pourquoi vous pouvez utiliser le traçage et comment nous l'avons fait, et seront discutés dans cet article.

Quels problèmes peuvent être résolus avec le traçage

  1. Trouvez les goulots d'étranglement des performances à la fois au sein d'un service unique et dans l'ensemble de l'arborescence d'exécution entre tous les services participants. Par exemple:
    • De nombreux appels courts et consécutifs entre services, par exemple vers un géocodage ou vers une base de données.
    • Attentes d'E/S longues, telles que les transferts réseau ou les lectures de disque.
    • Longue analyse de données.
    • Opérations longues nécessitant CPU.
    • Les sections de code qui ne sont pas nécessaires pour obtenir le résultat final et qui peuvent être supprimées ou retardées.
  2. Comprenez clairement dans quel ordre ce qui est appelé et ce qui se passe lorsque l'opération est effectuée.
    Suivi des services, OpenTracing et Jaeger
    On peut voir que, par exemple, la requête est arrivée au service WS -> le service WS a ajouté des données via le service R -> puis a envoyé une requête au service V -> le service V a chargé beaucoup de données du R service -> est allé au service P -> le service P est allé à nouveau au service R -> le service V a ignoré le résultat et est allé au service J -> et n'a renvoyé qu'ensuite la réponse au service WS, tout en continuant à calculer autre chose dans le arrière-plan.
    Sans une telle trace ou une documentation détaillée pour l'ensemble du processus, il est très difficile de comprendre ce qui se passe lorsque l'on regarde le code pour la première fois, et le code est dispersé sur différents services et caché derrière un tas de bacs et d'interfaces.
  3. Collecte d'informations sur l'arbre d'exécution pour une analyse différée ultérieure. À chaque étape de l'exécution, vous pouvez ajouter des informations à la trace disponible à ce stade, puis déterminer quelles données d'entrée ont conduit à un scénario similaire. Par exemple:
    • ID de l'utilisateur
    • Droits
    • Type de méthode sélectionnée
    • Journal ou erreur d'exécution
  4. Transformer les traces en un sous-ensemble de métriques et une analyse plus approfondie déjà sous la forme de métriques.

Quelle trace peut enregistrer. Portée

Dans le traçage, il y a le concept d'une portée, c'est un analogue d'un journal, à la console. Le spa dispose de :

  • Nom, généralement le nom de la méthode qui a été exécutée
  • Le nom du service dans lequel le span a été généré
  • Propre identifiant unique
  • Une sorte de méta-information sous la forme d'une clé/valeur qui y a été connectée. Par exemple, les paramètres de la méthode ou la méthode s'est terminée par une erreur ou non
  • Heures de début et de fin pour cette période
  • ID d'étendue parent

Chaque span est envoyé au collecteur de span pour être stocké dans la base de données pour un examen ultérieur dès qu'il a terminé son exécution. À l'avenir, vous pourrez créer une arborescence de toutes les étendues en vous connectant par identifiant parent. Lors de l'analyse, vous pouvez trouver, par exemple, toutes les durées d'un service qui ont pris plus d'un certain temps. De plus, en allant à une étendue spécifique, voir l'arbre entier au-dessus et en dessous de cette étendue.

Suivi des services, OpenTracing et Jaeger

Opentrace, Jagger et comment nous l'avons implémenté pour nos projets

Il existe une norme commune opentrace, qui décrit comment et ce qui doit être collecté, sans être lié par un traçage à une implémentation spécifique dans une langue. Par exemple, en Java, tout travail avec des traces est effectué via l'API Opentrace commune, et sous celle-ci, par exemple, Jaeger ou une implémentation par défaut vide qui ne fait rien peut être masquée.
Nous utilisons Jaeger en tant qu'implémentation d'Opentrace. Il se compose de plusieurs composants :

Suivi des services, OpenTracing et Jaeger

  • Jaeger-agent est un agent local qui est généralement installé sur chaque machine et les services y sont connectés sur le port local par défaut. S'il n'y a pas d'agent, les traces de tous les services sur cette machine sont généralement désactivées
  • Jaeger-collector - tous les agents lui envoient les traces collectées, et il les place dans la base de données sélectionnée
  • La base de données est leur cassandra préférée, mais nous utilisons elasticsearch, il existe des implémentations pour quelques autres bases de données et une implémentation en mémoire qui n'enregistre rien sur le disque
  • Jaeger-query est un service qui va à la base de données et renvoie les traces déjà collectées pour analyse
  • Jaeger-ui est une interface web pour rechercher et visualiser des traces, il va à jaeger-query

Suivi des services, OpenTracing et Jaeger

Un composant distinct peut être appelé l'implémentation d'opentrace jaeger pour des langages spécifiques, à travers lequel les étendues sont envoyées à jaeger-agent.
Connecter Jagger en Java revient à implémenter l'interface io.opentracing.Tracer, après quoi toutes les traces qui la traversent voleront vers l'agent réel.

Suivi des services, OpenTracing et Jaeger

Également pour le composant de ressort, vous pouvez connecter opentracing-spring-cloud-starter et mise en œuvre de Jaeger opentracing-spring-jaeger-cloud-starter qui configurera automatiquement le traçage pour tout ce qui passe par ces composants, par exemple les requêtes http aux contrôleurs, les requêtes à la base de données via jdbc, etc.

Journalisation des traces en Java

Quelque part au niveau supérieur, le premier Span doit être créé, cela peut être fait automatiquement, par exemple, par le contrôleur de ressort lorsqu'une demande est reçue, ou manuellement s'il n'y en a pas. Il est ensuite transmis via le champ d'application ci-dessous. Si l'une des méthodes ci-dessous souhaite ajouter un Span, elle prend l'activeSpan actuel du Scope, crée un nouveau Span et indique que son parent est l'activeSpan résultant, et active le nouveau Span. Lors de l'appel de services externes, la plage active actuelle leur est transmise et ces services créent de nouvelles plages en référence à cette plage.
Tout le travail passe par l'instance Tracer, vous pouvez l'obtenir via le mécanisme DI, ou GlobalTracer.get () en tant que variable globale si le mécanisme DI ne fonctionne pas. Par défaut, si le traceur n'a pas été initialisé, NoopTracer retournera ce qui ne fait rien.
De plus, la portée actuelle est obtenue à partir du traceur via le ScopeManager, une nouvelle portée est créée à partir de la portée actuelle avec une liaison de la nouvelle portée, puis la portée créée est fermée, ce qui ferme la portée créée et renvoie la portée précédente à l'état actif. La portée est liée à un thread, donc lors de la programmation multi-thread, vous ne devez pas oublier de transférer la plage active vers un autre thread, pour une activation ultérieure de la portée d'un autre thread en référence à cette plage.

io.opentracing.Tracer tracer = ...; // GlobalTracer.get()

void DoSmth () {
   try (Scope scope = tracer.buildSpan("DoSmth").startActive(true)) {
      ...
   }
}
void DoOther () {
    Span span = tracer.buildSpan("someWork").start();
    try (Scope scope = tracer.scopeManager().activate(span, false)) {
        // Do things.
    } catch(Exception ex) {
        Tags.ERROR.set(span, true);
        span.log(Map.of(Fields.EVENT, "error", Fields.ERROR_OBJECT, ex, Fields.MESSAGE, ex.getMessage()));
    } finally {
        span.finish();
    }
}

void DoAsync () {
    try (Scope scope = tracer.buildSpan("ServiceHandlerSpan").startActive(false)) {
        ...
        final Span span = scope.span();
        doAsyncWork(() -> {
            // STEP 2 ABOVE: reactivate the Span in the callback, passing true to
            // startActive() if/when the Span must be finished.
            try (Scope scope = tracer.scopeManager().activate(span, false)) {
                ...
            }
        });
    }
}

Pour la programmation multi-thread, il existe également TracedExecutorService et des wrappers similaires qui transfèrent automatiquement l'étendue actuelle au thread lorsque des tâches asynchrones sont lancées :

private ExecutorService executor = new TracedExecutorService(
    Executors.newFixedThreadPool(10), GlobalTracer.get()
);

Pour les requêtes http externes, il y a TraçageHttpClient

HttpClient httpClient = new TracingHttpClientBuilder().build();

Problèmes auxquels nous avons été confrontés

  • Les beans et DI ne fonctionnent pas toujours si le traceur n'est pas utilisé dans un service ou un composant, alors Câblage automatique Tracer peut ne pas fonctionner et vous devrez utiliser GlobalTracer.get().
  • Les annotations ne fonctionnent pas s'il ne s'agit pas d'un composant ou d'un service, ou si la méthode est appelée depuis une méthode voisine de la même classe. Vous devez faire attention à vérifier ce qui fonctionne et utiliser la création de trace manuelle si @Traced ne fonctionne pas. Vous pouvez également joindre un compilateur supplémentaire pour les annotations Java, alors elles devraient fonctionner partout.
  • Dans l'ancien démarrage du printemps et du printemps, la configuration automatique du nuage de printemps opentraing ne fonctionne pas en raison de bogues dans DI, alors si vous voulez que les traces dans les composants du printemps fonctionnent automatiquement, vous pouvez le faire par analogie avec github.com/opentracing-contrib/java-spring-jaeger/blob/master/opentracing-spring-jaeger-starter/src/main/java/io/opentracing/contrib/java/spring/jaeger/starter/JaegerAutoConfiguration.java
  • Try with resources ne fonctionne pas dans groovy, vous devez utiliser try finally.
  • Chaque service doit avoir son propre nom spring.application.name sous lequel les traces seront enregistrées. Qu'est-ce qu'un nom distinct pour la vente et le test, afin de ne pas les gêner ensemble.
  • Si vous utilisez GlobalTracer et tomcat, tous les services exécutés dans ce tomcat ont un GlobalTracer, ils auront donc tous le même nom de service.
  • Lorsque vous ajoutez des traces à une méthode, vous devez vous assurer qu'elle n'est pas appelée plusieurs fois dans une boucle. Il est nécessaire d'ajouter une trace commune pour tous les appels, ce qui garantit le temps de travail total. Sinon, une surcharge sera créée.
  • Une fois dans jaeger-ui, des requêtes trop importantes étaient faites pour un grand nombre de traces, et comme ils n'attendaient pas de réponse, ils recommençaient. En conséquence, Jaeger-Query a commencé à consommer beaucoup de mémoire et à ralentir Elastic. Aidé en redémarrant jaeger-query

Échantillonnage, stockage et visualisation des traces

Il existe trois types traces d'échantillonnage:

  1. Const qui envoie et enregistre toutes les traces.
  2. Probabiliste qui filtre les traces avec une probabilité donnée.
  3. Ratelimiting qui limite le nombre de traces par seconde. Vous pouvez configurer ces paramètres sur le client, soit sur l'agent jaeger, soit sur le collecteur. Maintenant, nous utilisons const 1 dans la pile de valuateurs, car il n'y a pas beaucoup de requêtes, mais elles prennent beaucoup de temps. À l'avenir, si cela exerce une charge excessive sur le système, vous pouvez la limiter.

Si vous utilisez cassandra, par défaut, il ne stocke les traces que pendant deux jours. Nous utilisons elasticsearch et les traces sont stockées pour toujours et ne sont pas supprimées. Un index distinct est créé pour chaque jour, par exemple jaeger-service-2019-03-04. À l'avenir, vous devrez configurer le nettoyage automatique des anciennes traces.

Pour visualiser les traces dont vous avez besoin :

  • Sélectionnez le service par lequel vous souhaitez filtrer les traces, par exemple, tomcat7-default pour un service qui s'exécute dans le tomcat et ne peut pas avoir son propre nom.
  • Sélectionnez ensuite l'opération, l'intervalle de temps et le temps d'opération minimum, par exemple à partir de 10 secondes, pour ne prendre que des exécutions longues.
    Suivi des services, OpenTracing et Jaeger
  • Allez à l'une des traces et voyez ce qui ralentissait là-bas.
    Suivi des services, OpenTracing et Jaeger

De plus, si un identifiant de requête est connu, vous pouvez trouver une trace par cet identifiant via une recherche de balise, si cet identifiant est enregistré dans la plage de trace.

Documentation

Articles

Vidéos

Source: habr.com

Ajouter un commentaire