Service Tracing, OpenTracing und Jaeger

Service Tracing, OpenTracing und Jaeger

Wir nutzen in unseren Projekten Microservice-Architekturen. Wenn Leistungsengpässe auftreten, wird viel Zeit für die Überwachung und Analyse von Protokollen aufgewendet. Bei der Protokollierung der Zeitabläufe einzelner Vorgänge in einer Protokolldatei ist es normalerweise schwierig zu verstehen, was zum Aufruf dieser Vorgänge geführt hat, um die Abfolge von Aktionen oder die zeitliche Verschiebung eines Vorgangs relativ zu einem anderen in verschiedenen Diensten zu verfolgen.

Um den manuellen Aufwand zu minimieren, haben wir uns für die Verwendung eines der Nachverfolgungstools entschieden. Wie und warum Sie Tracing nutzen können und wie wir es gemacht haben, wird in diesem Artikel besprochen.

Welche Probleme können mit Tracing gelöst werden?

  1. Finden Sie Leistungsengpässe sowohl innerhalb eines einzelnen Dienstes als auch im gesamten Ausführungsbaum zwischen allen teilnehmenden Diensten. Zum Beispiel:
    • Viele kurze aufeinanderfolgende Aufrufe zwischen Diensten, beispielsweise zur Geokodierung oder zu einer Datenbank.
    • Lange E/A-Wartezeiten, z. B. bei Netzwerkübertragungen oder Festplattenlesevorgängen.
    • Lange Datenanalyse.
    • Lange Vorgänge, die CPU erfordern.
    • Codeabschnitte, die für das Endergebnis nicht erforderlich sind und entfernt oder verzögert werden können.
  2. Verstehen Sie klar, in welcher Reihenfolge was aufgerufen wird und was passiert, wenn die Operation ausgeführt wird.
    Service Tracing, OpenTracing und Jaeger
    Es ist zu erkennen, dass zum Beispiel die Anfrage an den WS-Dienst kam -> der WS-Dienst hat die Daten über den R-Dienst ergänzt -> dann eine Anfrage an den V-Dienst gesendet -> der V-Dienst hat viele Daten vom geladen R-Dienst -> ging zum P-Dienst -> der P-Dienst ging erneut zu Dienst R -> Dienst V ignorierte das Ergebnis und ging zu Dienst J -> und gab erst dann die Antwort an Dienst WS zurück, während er weiterhin etwas anderes berechnete der Hintergrund.
    Ohne einen solchen Trace oder eine detaillierte Dokumentation des gesamten Prozesses ist es beim ersten Betrachten des Codes sehr schwierig zu verstehen, was passiert, und der Code ist über verschiedene Dienste verstreut und hinter einer Reihe von Bins und Schnittstellen versteckt.
  3. Sammlung von Informationen über den Ausführungsbaum für eine spätere verzögerte Analyse. In jeder Phase der Ausführung können Sie der in dieser Phase verfügbaren Ablaufverfolgung Informationen hinzufügen und dann herausfinden, welche Eingabedaten zu einem ähnlichen Szenario geführt haben. Zum Beispiel:
    • Benutzer-ID
    • Rechte
    • Art der ausgewählten Methode
    • Protokoll- oder Ausführungsfehler
  4. Umwandlung von Spuren in eine Teilmenge von Metriken und weitere Analyse bereits in Form von Metriken.

Welcher Trace kann protokolliert werden? Spanne

Bei der Ablaufverfolgung gibt es das Konzept einer Spanne, die ein Analogon eines Protokolls zur Konsole ist. Das Spa verfügt über:

  • Name, normalerweise der Name der Methode, die ausgeführt wurde
  • Der Name des Dienstes, in dem der Span generiert wurde
  • Eigene eindeutige ID
  • Eine Art Metainformation in Form eines Schlüssels/Werts, der darin protokolliert wurde. Beispielsweise Methodenparameter oder die Methode wurde mit einem Fehler beendet oder nicht
  • Start- und Endzeiten für diesen Zeitraum
  • ID des übergeordneten Bereichs

Jeder Span wird an den Span-Kollektor gesendet, um ihn zur späteren Überprüfung in der Datenbank zu speichern, sobald die Ausführung abgeschlossen ist. In Zukunft können Sie einen Baum aller Bereiche erstellen, indem Sie eine Verbindung über die übergeordnete ID herstellen. Bei der Analyse können Sie beispielsweise alle Spannen in einem Dienst finden, die mehr als einige Zeit in Anspruch genommen haben. Wenn Sie außerdem zu einem bestimmten Bereich gehen, sehen Sie den gesamten Baum oberhalb und unterhalb dieses Bereichs.

Service Tracing, OpenTracing und Jaeger

Opentrace, Jagger und wie wir es für unsere Projekte implementiert haben

Es gibt einen gemeinsamen Standard opentrace, die beschreibt, wie und was gesammelt werden soll, ohne durch die Rückverfolgung an eine bestimmte Implementierung in einer beliebigen Sprache gebunden zu sein. Beispielsweise erfolgt in Java die gesamte Arbeit mit Traces über die gemeinsame Opentrace-API, und darunter kann beispielsweise Jaeger oder eine leere Standardimplementierung, die nichts tut, ausgeblendet werden.
Wir benutzen Jaeger als Implementierung von Opentrace. Es besteht aus mehreren Komponenten:

Service Tracing, OpenTracing und Jaeger

  • Jaeger-Agent ist ein lokaler Agent, der normalerweise auf jedem Computer installiert wird und bei dem Dienste am lokalen Standardport angemeldet sind. Wenn kein Agent vorhanden ist, sind die Spuren aller Dienste auf diesem Computer normalerweise deaktiviert
  • Jaeger-Collector – alle Agenten senden gesammelte Spuren an ihn und er legt sie in der ausgewählten Datenbank ab
  • Die Datenbank ist ihre bevorzugte Cassandra, aber wir verwenden Elasticsearch, es gibt Implementierungen für einige andere Datenbanken und eine In-Memory-Implementierung, die nichts auf der Festplatte speichert
  • Jaeger-Query ist ein Dienst, der auf die Datenbank zugreift und bereits gesammelte Spuren zur Analyse zurückgibt
  • Jaeger-ui ist eine Weboberfläche zum Suchen und Anzeigen von Spuren, es geht um Jaeger-Query

Service Tracing, OpenTracing und Jaeger

Eine separate Komponente kann als Implementierung von Opentrace Jaeger für bestimmte Sprachen bezeichnet werden, über die Spans an Jaeger-Agent gesendet werden.
Jagger in Java verbinden Es kommt darauf an, die io.opentracing.Tracer-Schnittstelle zu implementieren, wonach alle Traces durch sie zum echten Agenten fliegen.

Service Tracing, OpenTracing und Jaeger

Auch für die Federkomponente können Sie eine Verbindung herstellen opentracing-spring-cloud-starter und Umsetzung von Jaeger opentracing-spring-jaeger-cloud-starter Dadurch wird automatisch die Ablaufverfolgung für alles konfiguriert, was diese Komponenten durchläuft, z. B. HTTP-Anfragen an Controller, Anfragen an die Datenbank über JDBC usw.

Verfolgt die Protokollierung in Java

Irgendwo auf der obersten Ebene muss der erste Span erstellt werden. Dies kann beispielsweise automatisch vom Spring Controller erfolgen, wenn eine Anfrage eingeht, oder manuell, wenn keine vorhanden ist. Die Übertragung erfolgt dann über den unten stehenden Scope. Wenn eine der folgenden Methoden einen Span hinzufügen möchte, übernimmt sie den aktuellen ActiveSpan aus dem Bereich, erstellt einen neuen Span und gibt an, dass sein übergeordneter Span der resultierende ActiveSpan ist, und macht den neuen Span aktiv. Beim Aufruf externer Dienste wird ihnen der aktuell aktive Span übergeben, und diese Dienste erstellen neue Spans mit Bezug auf diesen Span.
Die gesamte Arbeit läuft über die Tracer-Instanz. Sie können sie über den DI-Mechanismus oder über GlobalTracer.get() als globale Variable abrufen, wenn der DI-Mechanismus nicht funktioniert. Wenn der Tracer nicht initialisiert wurde, kehrt NoopTracer standardmäßig zurück, was jedoch nichts bewirkt.
Darüber hinaus wird der aktuelle Bereich vom Tracer über den ScopeManager abgerufen, ein neuer Bereich wird aus dem aktuellen Bereich mit einer Bindung des neuen Bereichs erstellt und anschließend wird der erstellte Bereich geschlossen, wodurch der erstellte Bereich geschlossen und der vorherige Bereich zurückgegeben wird der aktive Zustand. Der Bereich ist an einen Thread gebunden. Bei der Multithread-Programmierung dürfen Sie daher nicht vergessen, den aktiven Bereich auf einen anderen Thread zu übertragen, um den Bereich eines anderen Threads mit Bezug auf diesen Bereich weiter zu aktivieren.

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)) {
                ...
            }
        });
    }
}

Für die Multithread-Programmierung gibt es auch TracedExecutorService und ähnliche Wrapper, die beim Start asynchroner Aufgaben automatisch die aktuelle Spanne an den Thread weiterleiten:

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

Für externe http-Anfragen gibt es TracingHttpClient

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

Probleme, mit denen wir konfrontiert waren

  • Beans und DI funktionieren dann nicht immer, wenn der Tracer nicht in einem Dienst oder einer Komponente verwendet wird Automatisch verkabelt Tracer funktioniert möglicherweise nicht und Sie müssen GlobalTracer.get() verwenden.
  • Anmerkungen funktionieren nicht, wenn es sich nicht um eine Komponente oder einen Dienst handelt oder wenn die Methode von einer benachbarten Methode derselben Klasse aufgerufen wird. Sie müssen sorgfältig prüfen, was funktioniert, und die manuelle Trace-Erstellung verwenden, wenn @Traced nicht funktioniert. Sie können auch einen zusätzlichen Compiler für Java-Annotationen anhängen, dann sollten diese überall funktionieren.
  • Im alten Spring und Spring Boot funktioniert die automatische Konfiguration der Opentraing Spring Cloud aufgrund von Fehlern in DI nicht. Wenn Sie möchten, dass die Spuren in den Spring-Komponenten automatisch funktionieren, können Sie dies analog tun 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
  • Versuchen mit Ressourcen funktioniert in Groovy nicht. Sie müssen schließlich versuchen, es zu verwenden.
  • Jeder Dienst muss über einen eigenen spring.application.name verfügen, unter dem Ablaufverfolgungen protokolliert werden. Was bedeutet ein separater Name für den Verkauf und den Test, um sie nicht gemeinsam zu beeinträchtigen?
  • Wenn Sie GlobalTracer und Tomcat verwenden, verfügen alle in diesem Tomcat ausgeführten Dienste über einen GlobalTracer, sodass sie alle denselben Dienstnamen haben.
  • Beim Hinzufügen von Traces zu einer Methode müssen Sie sicherstellen, dass diese nicht viele Male in einer Schleife aufgerufen wird. Es ist notwendig, einen gemeinsamen Trace für alle Anrufe hinzuzufügen, um die Gesamtarbeitszeit zu gewährleisten. Andernfalls entsteht eine Überlastung.
  • Einmal in jaeger-ui wurden zu große Anfragen für eine große Anzahl von Spuren gestellt, und da sie nicht auf eine Antwort warteten, haben sie es noch einmal gemacht. Infolgedessen begann Jaeger-Query viel Speicher zu verbrauchen und Elastic zu verlangsamen. Hat durch einen Neustart von jaeger-query geholfen

Probennahme, Speicherung und Anzeige von Spuren

Es gibt drei Arten Probenahmespuren:

  1. Const, das alle Traces sendet und speichert.
  2. Probabilistisch, das Spuren mit einer bestimmten Wahrscheinlichkeit filtert.
  3. Ratelimiting, das die Anzahl der Spuren pro Sekunde begrenzt. Sie können diese Einstellungen auf dem Client konfigurieren, entweder auf dem Jaeger-Agent oder auf dem Collector. Jetzt verwenden wir const 1 im Bewertungsstapel, da es nicht sehr viele Anfragen gibt, diese aber lange dauern. Sollte dies in Zukunft zu einer übermäßigen Belastung des Systems führen, können Sie diese begrenzen.

Wenn Sie Cassandra verwenden, speichert es standardmäßig nur Ablaufverfolgungen für zwei Tage. Wir benutzen elasticsearch und Spuren werden dauerhaft gespeichert und nicht gelöscht. Für jeden Tag wird ein eigener Index erstellt, zum Beispiel jaeger-service-2019-03-04. Zukünftig müssen Sie die automatische Reinigung alter Spuren konfigurieren.

Um die Spuren anzuzeigen, benötigen Sie:

  • Wählen Sie den Dienst aus, nach dem Sie Traces filtern möchten, z. B. tomcat7-default für einen Dienst, der im Tomcat ausgeführt wird und keinen eigenen Namen haben kann.
  • Wählen Sie dann den Vorgang, das Zeitintervall und die Mindestbetriebsdauer aus, beispielsweise ab 10 Sekunden, um nur lange Ausführungen zu ermöglichen.
    Service Tracing, OpenTracing und Jaeger
  • Gehen Sie zu einer der Spuren und sehen Sie, was dort langsamer wurde.
    Service Tracing, OpenTracing und Jaeger

Wenn eine Anforderungs-ID bekannt ist, können Sie außerdem über eine Tag-Suche einen Trace anhand dieser ID finden, sofern diese ID im Trace-Bereich protokolliert wird.

Dokumentation

Artikel

Video

Source: habr.com

Kommentar hinzufügen