Verteilte Ablaufverfolgung mit OpenCensus und Java

Einführung

Ich habe versucht, verteiltes Tracing mit OpenCensus API und Java zu implementieren. OpenCensus ist eine Spezifikation, die auf Googles Cloud Trace (ehemals StackDriver Trace) basiert und eine Spezifikation für die verteilte Ablaufverfolgung und Metrikerfassung ist. Auf diese Weise kann die PG-Seite das Backend mit Jeager, OpenZipkin, Cloud Trace usw. verknüpfen, während eine einheitliche API verwendet wird.

Tatsächlich ist OpenCensus alt und jetzt durch Integration in Open Tracing mit ähnlichen Spezifikationen als Open Telemetry standardisiert. Dies umfasst kommerzielle APMs wie Datadog, NewRelic, Dynatrace und Instana. Sie sollten diese daher in Zukunft verwenden. Zuerst spielte ich mit Open Telemetry, aber obwohl ich mit Jeager zusammenarbeiten konnte, war es Cloud Trace unterstützt noch keine Java-Version. Vorerst habe ich OpenCensus ausprobiert, das eine Menge Dokumentation zu haben scheint.

Die Dokumente waren jedoch nicht so gut organisiert, wie ich erwartet hatte, daher werde ich sie zur Erinnerung zusammenfassen.

Der Code, den ich dieses Mal erstellt habe, ist unten. https://github.com/koduki/miniban/tree/example/opensensus

Systemkonfiguration

Als Systemkonfiguration empfängt es einfach eine Anfrage mit api-endpoint und wirft den Prozess mit Geschäftslogik mit REST / JSON an das Backend api-core.

opencensus.png

Es ist nicht so unterteilt wie ein Mikrodienst, aber dies ist eine gängige Konfiguration, und ich denke, es reicht aus, um die verteilte Ablaufverfolgung zu überprüfen. Jede API wird mit Quarkus in JAX-RS implementiert. Grundsätzlich verwende ich keine MicroProfile-spezifische API, daher sollte jede Java EE-Umgebung so funktionieren, wie sie ist.

Implementierung

Abhängige Bibliotheken

Fügen Sie pom.xml als abhängige Bibliothek Folgendes hinzu.

<dependency>
    <groupId>io.opencensus</groupId>
    <artifactId>opencensus-api</artifactId>
    <version>0.26.0</version>
</dependency>
<dependency>
    <groupId>io.opencensus</groupId>
    <artifactId>opencensus-impl</artifactId>
    <version>0.26.0</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.opencensus</groupId>
    <artifactId>opencensus-exporter-trace-stackdriver</artifactId>
    <version>0.26.0</version>
</dependency>
<dependency>
    <groupId>io.opencensus</groupId>
    <artifactId>opencensus-exporter-trace-jaeger</artifactId>
    <version>0.26.0</version>
</dependency>
<dependency>
    <groupId>io.opencensus</groupId>
    <artifactId>opencensus-contrib-http-jaxrs</artifactId>
    <version>0.26.0</version>
</dependency>

Die erforderlichen APIs sind opencensus-api und opencensus-impl. opencensus-exporter-xxx ist eine Vielzahl von Exporter-Bibliotheken, die Trace-Informationen senden, und opencensus-contrib-http-jaxrs ist eine praktische Bibliothek für JAX-RS. Dieses Mal haben wir zwei Exporteure angegeben, Stackdriver und Jaeger, aber normalerweise einen.

Exporter-Initialisierung

Initialisieren und registrieren Sie zunächst den Exporter. Dies muss nur einmal durchgeführt werden. Verwenden Sie daher "@Initialized (ApplicationScoped.class)", um es beim Start zu laden.

Bootstrap.java


@ApplicationScoped
public class Bootstrap {
    public void handle(@Observes @Initialized(ApplicationScoped.class) Object event) {
        JaegerTraceExporter.createAndRegister(
                JaegerExporterConfiguration.builder()
                        .setThriftEndpoint("http://localhost:14268/api/traces")
                        .setServiceName("api-endpoint")
                        .build());
    }
}

Schnellstart verwendet die URL direkt als Argument für "createAndRegister", dies ist jedoch noch nicht geschehen. Es sieht aus wie ein empfohlener Code, verwenden Sie also "JaegerExporterConfiguration".

Wie Sie sehen können, verwenden Sie setThriftEndpoint, um die URL des Linkziels anzugeben, und verwenden Sie setServiceName, um den Dienstnamen auf Jaeger zu verwenden.

Wenn Sie den registrierten Teil dieses Exporters ändern, können Sie das Backend beliebig wechseln. Wenn Sie beispielsweise Cloud Trace verwenden möchten, ändern Sie dies wie folgt.

Bootstrap.java


@ApplicationScoped
public class Bootstrap {
    public void handle(@Observes @Initialized(ApplicationScoped.class) Object event) {
        String gcpProjectId = "your GCP project ID";
        StackdriverTraceExporter.createAndRegister(
                StackdriverTraceConfiguration.builder()
                        .setProjectId(gcpProjectId)
                        .build());
    }
}

Erstellen einer Trace-Spanne

Als nächstes kommt die Schaffung von Span.

try (Scope ss = Tracing.getTracer()
        .spanBuilder("Span Name")
        .setRecordEvents(true)
        .setSampler(Samplers.alwaysSample())
        .startScopedSpan()) {

    // do somthing.
}

Tracing.getTracer () ruft den Tracer von einem Singleton oder Global ab. Verwenden Sie von dort aus "spanBuilder", um den Span zusammenzusetzen. Der Punkt ist setSampler. Der Sampler gibt an, wie oft der Trace abgerufen werden soll. Wenn Sie hier nicht "Samplers.alwaysSample ()" angeben, werden die Trace-Informationen nicht immer geschrieben. In der Produktion kann es Fälle geben, in denen ein angemessener Schwellenwert in Bezug auf Kosten und Last festgelegt wird. Zum Zeitpunkt des Tests gibt es jedoch zu wenige andere Anforderungen als Lasttests. Geben Sie daher immer an. Details zu anderen Samplern finden Sie unter hier.

Übrigens ist es mühsam, jedes Mal das Obige zu schreiben, deshalb habe ich irgendwann den folgenden Helfer definiert.

public static <R> R trace(Supplier<R> callback) {
    var depth = 2;
    var className = Thread.currentThread().getStackTrace()[depth].getClassName();
    var methodName = Thread.currentThread().getStackTrace()[depth].getMethodName();

    try (var ss = Tracing.getTracer()
            .spanBuilder(className + "$" + methodName)
            .setRecordEvents(true)
            .setSampler(Samplers.alwaysSample())
            .startScopedSpan()) {
        return callback.get();
    }
}

Der Klassenname und der Methodenname werden erfasst und automatisch im Span-Namen angegeben. Wenn Sie es verwenden, sieht es wie folgt aus.

@GET
@Path("/{userId}/balance")
public Map<String, Long> getBalance(@PathParam("userId") String userId) {
    return trace(() -> {
        var balance = service.getBalance(userId);
        return balance;
    });
}

Weitergabe des Trace-Kontexts

Wenn Sie sich in derselben Anwendung befinden, führen Sie einfach startScopedSpan wie oben aus, und der verschachtelte Bereich wird ohne Erlaubnis erstellt. Das Herzstück der verteilten Rückverfolgung ist jedoch die systemübergreifende Zusammenarbeit. Daher ist es notwendig, entfernte Kontexte zu verknüpfen. Ab diesem Zeitpunkt waren die Dokumente jedoch alt oder nicht richtig geschrieben, sodass es sich um einen Versuch und Irrtum handelte.

traceparent

In OpenCensus scheint der Kontext ursprünglich mit Header-Informationen wie X-B3-TraceId / X-B3-ParentSpanId übergeben worden zu sein, jetzt jedoch [W3C-standardisierter Trace-Kontext](https: //w3c.github) .io / trace-context /) wird verwendet. Dies bettet einen Parameter namens "traceparent" in den HTTP-Header ein und setzt darauf basierend "Trace ID", "Span ID" und "Trace Options (Trace-Flags)" zusammen.

traceparent hat die folgenden Werte.

traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01

Der folgende Code analysiert Traceparent mit OpenCensus und erstellt einen SpanContext.

int VERSION_SIZE = 2;
int TRACEPARENT_DELIMITER_SIZE = 1;
int TRACE_ID_HEX_SIZE = 2 * TraceId.SIZE;
int SPAN_ID_HEX_SIZE = 2 * SpanId.SIZE;
int TRACE_ID_OFFSET = VERSION_SIZE + TRACEPARENT_DELIMITER_SIZE;
int SPAN_ID_OFFSET = TRACE_ID_OFFSET + TRACE_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE;
int TRACE_OPTION_OFFSET = SPAN_ID_OFFSET + SPAN_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE;

// Get tranceparent
String traceparent = headers.getRequestHeaders().getFirst("traceparent");

// Parse traceparent
TraceId traceId = TraceId.fromLowerBase16(traceparent, TRACE_ID_OFFSET);
SpanId spanId = SpanId.fromLowerBase16(traceparent, SPAN_ID_OFFSET);
TraceOptions traceOptions = TraceOptions.fromLowerBase16(traceparent, TRACE_OPTION_OFFSET);

//SpanContext aus traceparent erstellen
SpanContext spanContext = SpanContext.create(traceId, spanId, traceOptions);

JAX-RS-Client (Traceparent während REST-Aufruf angeben)

Um den Kontext an die Remote-API zu übermitteln, muss zum Zeitpunkt des REST-Aufrufs Trace-Eltern zum HTTP-Header usw. hinzugefügt werden. Sie können dies manuell tun, aber die OpenCensus-Bibliothek für JAX-RS "opencensus-contrib-http-jaxrs "Wird genutzt.

var url = 'http://localhost:5000';
var target = ClientBuilder.newClient()
        .target(url)
        .path("/account/" + userId + "/balance");

target.register(JaxrsClientFilter.class);
return target
        .request(MediaType.APPLICATION_JSON)
        .get(new GenericType<Map<String, Long>>() {});

Durch die Registrierung von JaxrsClientFilter bei target.register (JaxrsClientFilter.class) wird traceparent automatisch aus dem aktuellen Span generiert und dem Anforderungsheader hinzugefügt.

JAX-RS-Client (Span aus Trace-Eltern erstellen)

Dies ist die Implementierung von API-Core auf der Serverseite von JAX-RS. Es scheint, dass es automatisch mithilfe des Containerfilters von opencensus-contrib-http-jaxrs festgelegt werden kann. Es hat aus irgendeinem Grund nicht funktioniert, also werde ich es selbst implementieren.

private static final TextFormat textFormat = Tracing.getPropagationComponent().getTraceContextFormat();
private static final TextFormat.Getter<HttpServletRequest> getter = new TextFormat.Getter<HttpServletRequest>() {
    @Override
    public String get(HttpServletRequest httpRequest, String s) {
        return httpRequest.getHeader(s);
    }
};

@GET
@Path("/{userId}/balance")
public Map<String, Long> getBalance(@Context HttpServletRequest request, @PathParam("userId") String userId) throws SpanContextParseException {
    var spanContext = textFormat.extract(request, getter);

    var depth = 1;
    var className = Thread.currentThread().getStackTrace()[depth].getClassName();
    var methodName = Thread.currentThread().getStackTrace()[depth].getMethodName();

    try (var ss = Tracing.getTracer()
            .spanBuilderWithRemoteParent(className + "$" + methodName, spanContext)
            .setRecordEvents(true)
            .setSampler(Samplers.alwaysSample())
            .startScopedSpan()) {
        var balance = service.getBalance(userId);
        return balance;
    }
}

Mit der TextFormat-Klasse können Sie einenSpanContext erstellen, ohne das mühsame Parsen selbst durchführen zu müssen. Verwenden Sie im Gegensatz zur normalen Span-Erstellung "spanBuilderWithRemoteParent", um einen Span aus einem Remote-Kontext zu erstellen. Es gibt kein Problem mit "spanBuilder" beim Erstellen nachfolgender untergeordneter Bereiche.

Damit sind die Einstellungen für die verteilte Ablaufverfolgung mit OpenCensus in Java abgeschlossen. Verschieben Sie sie daher tatsächlich und prüfen Sie, ob sie mit Jaeger oder Cloud Trace verknüpft ist.

Zusammenfassung

Ich habe versucht, verteiltes Tracing mit OpenCensus in Java zu implementieren. Um ehrlich zu sein, es gibt Dokumente auf einen Blick und es ist eine berühmte Spezifikation, daher wird es viele Blog-Artikel geben, also dachte ich, dass es in ungefähr einer Stunde ein einfacher Sieg sein würde, aber ich war vom Sumpf begeistert und es dauerte ungefähr eine Stunde.

Ich habe das Gefühl, dass ich den Mangel an Dokumenten und Mängeln beseitigen möchte, aber ich bin sicher, dass die Open Telemetry mit Aufwand betrieben werden sollte. Ich denke auch, dass der Grund, warum die Details nicht geschrieben werden, im Grunde darin liegt, dass einige Leute, die mit verteiltem Tracing vertraut sind, die Bibliothek und FW entwerfen und dann verwenden. MP Open Tracing oder Open Telemetry Open Telemetry Auto-Instrumentation für Java wird berührt, auch wenn Sie nicht damit vertraut sind.

Infolgedessen denke ich, dass ich diesmal detaillierter geworden bin, also frage ich mich, ob das in Ordnung war. Open Telemetry muss bald erneut in Frage gestellt werden.

Dann viel Spaß beim Hacken!

Referenz

Recommended Posts

Verteilte Ablaufverfolgung mit OpenCensus und Java
Verwenden Sie Java mit MSYS und Cygwin
Installieren Sie Java und Tomcat mit Ansible
Verwenden Sie JDBC mit Java und Scala.
PDF und TIFF mit Java 8 ausgeben
Mit Java verschlüsseln und mit C # entschlüsseln
Überwachen Sie Java-Anwendungen mit Jolokia und Hawtio
Verknüpfen Sie Java- und C ++ - Code mit SWIG
Probieren wir WebSocket mit Java und Javascript aus!
[Java] Lesen und Schreiben von Dateien mit OpenCSV
Java und JavaScript
XXE und Java
Erstellen und testen Sie Java + Gradle-Anwendungen mit Wercker
Versuchen Sie, Ruby und Java in Dapr zu integrieren
JSON mit Java und Jackson Teil 2 XSS-Maßnahmen
Bereiten Sie eine Scraping-Umgebung mit Docker und Java vor
KMS) Umschlagverschlüsselung mit OpenSL- und Java-Entschlüsselung
Verschlüsseln / Entschlüsseln mit AES256 in PHP und Java
[Java] Konvertieren und Importieren von Dateiwerten mit OpenCSV
[Review] Lesen und Schreiben von Dateien mit Java (JDK6)
[Java] Richten Sie Zeichen auch mit gemischten Zeichen halber und voller Breite aus
Verwenden Sie die schnelle Mapping-Bibliothek MapStruct mit Lombok und Java 11
Getter und Setter (Java)
Wechseln Sie die Plätze mit Java
AtCoder ABC129 D 2D-Array In Ruby und Java gelöst
[Java] Thread und ausführbar
Zusammenfassung des ToString-Verhaltens mit Java- und Groovy-Annotationen
Installieren Sie Java mit Ansible
Führen Sie Maven unter Java 8 aus, während Sie unter Java 6 kompilieren und unter Java 11 testen
Lösen mit Ruby, Perl und Java AtCoder ABC 128 C.
Java wahr und falsch
[Java] Vergleich von Zeichenketten und && und ||
Bequemer Download mit JAVA
[Java] Verweisen Sie auf und setzen Sie private Variablen mit Reflektion
Schalten Sie Java mit direnv
Java - Serialisierung und Deserialisierung
[Java] Argumente und Parameter
Java-Download mit Ansible
Ich möchte Bildschirmübergänge mit Kotlin und Java machen!
timedatectl und Java TimeZone
[Java] Verzweigen und Wiederholen
Bereiten Sie die Umgebung für Java11 und JavaFx mit Ubuntu 18.4 vor
Lass uns mit Java kratzen! !!
Gesichtserkennungs-App mit Amazon Rekognition und Java
Erstellen Sie Java mit Wercker
[Java] Variablen- und Typtypen
Java (Klasse und Instanz)
[Java] Entwicklung mit mehreren Dateien mittels Paket und Import
Serverloses Java EE beginnend mit Quarkus und Cloud Run
[Java] Überladen und überschreiben
Endian-Konvertierung mit JAVA
In Java 2D-Karte speichern und mit for-Anweisung drehen
Suchen Sie die Adressklasse und den Adresstyp aus der IP-Adresse mit Java
Erste Schritte mit Java und Erstellen eines Ascii Doc-Editors mit JavaFX
Fensteraggregation von Sensordaten mit Apache Flink und Java 8
Behandeln Sie die Java 8-Datums- und Uhrzeit-API mit Thymeleaf mit Spring Boot
Protokollaggregation und -analyse (Arbeiten mit AWS Athena in Java)
Behandeln Sie Ausnahmen kühl mit Java 8-Lambda-Ausdrücken und der Stream-API
Ich möchte eine Liste mit Kotlin und Java erstellen!
Ich möchte eine Funktion mit Kotlin und Java erstellen!
Kotlin Post- und Pre-Inkrement und Operatorüberladung (Vergleich mit C, Java, C ++)