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
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
.
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.
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.
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());
}
}
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;
});
}
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);
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.
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.
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!
Recommended Posts