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
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.
Comprenez clairement dans quel ordre ce qui est appelé et ce qui se passe lorsque l'opération est effectuée.
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.
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
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.
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 :
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
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.
É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.
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()
);
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.
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
Probabiliste qui filtre les traces avec une probabilité donnée.
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.
Allez à l'une des traces et voyez ce qui ralentissait là-bas.
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.
www.youtube.com/watch?v=qg0ENOdP1Lo Comment nous avons utilisé Jaeger et Prometheus pour fournir des requêtes utilisateur ultra-rapides — Bryan Boreham