J'ai essayé d'implémenter le traçage distribué avec OpenCensus API et Java. OpenCensus est une spécification basée sur Cloud Trace de Google (anciennement StackDriver Trace) et est une spécification pour le traçage distribué et la collecte de métriques. En utilisant cela, le côté PG peut lier le backend à Jeager, OpenZipkin, Cloud Trace, etc. tout en utilisant une API unifiée.
En fait, OpenCensus est ancien et est maintenant standardisé en tant que Open Telemetry en s'intégrant à Open Tracing avec des spécifications similaires. Ceci est compatible avec les APM commerciaux tels que Datadog, NewRelic, Dynatrace, Instana, vous devriez donc l'utiliser à l'avenir. Donc, au début, je jouais avec Open Telemetry, mais bien que j'ai pu coopérer avec Jeager, c'était Cloud Trace ne prend pas encore en charge la version Java. Pour le moment, j'ai essayé OpenCensus, qui semble avoir beaucoup de documentation.
Cependant, les documents n'ont pas été organisés autant que je m'y attendais, je vais donc les résumer à titre de rappel.
Le code que j'ai créé cette fois est ci-dessous. https://github.com/koduki/miniban/tree/example/opensensus
La configuration du système reçoit simplement une requête avec ʻapi-endpoint et lance le processus vers le backend ʻapi-core
avec une logique métier avec REST / JSON.
Ce n'est pas aussi subdivisé qu'un micro-service, mais c'est une configuration courante et je pense que c'est suffisant pour la vérification du traçage distribué. Chaque API est implémentée dans JAX-RS à l'aide de Quarkus. Fondamentalement, je n'utilise pas l'API spécifique à MicroProfile, donc tout environnement Java EE devrait fonctionner tel quel.
Ajoutez ce qui suit à pom.xml en tant que bibliothèque dépendante.
<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>
Les API requises sont opencensus-api et opencensus-impl. opencensus-exporter-xxx est une variété de bibliothèques d'exportateurs qui envoient des informations de trace, et ʻopencensus-contrib-http-jaxrs` est une bibliothèque pratique pour JAX-RS. Cette fois, nous avons spécifié deux exportateurs, stackdriver et jaeger, mais généralement l'un d'entre eux.
Tout d'abord, initialisez et enregistrez l'exportateur. Cela ne doit être fait qu'une seule fois, utilisez donc @Initialized (ApplicationScoped.class)
pour le charger au démarrage.
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());
}
}
Quick Start prend l'URL directement comme argument de createAndRegister
, mais ce n'est pas déjà fait. Cela ressemble à un code recommandé, utilisez donc JaegerExporterConfiguration
.
Comme vous pouvez le voir, utilisez setThriftEndpoint
pour spécifier l'URL de la destination du lien et utilisez setServiceName
pour utiliser le nom du service sur Jaeger.
Si vous modifiez la partie enregistrée de cet exportateur, vous pouvez changer le backend de manière arbitraire. Par exemple, si vous souhaitez utiliser Cloud Trace, modifiez comme suit.
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());
}
}
Vient ensuite la création de Span.
try (Scope ss = Tracing.getTracer()
.spanBuilder("Span Name")
.setRecordEvents(true)
.setSampler(Samplers.alwaysSample())
.startScopedSpan()) {
// do somthing.
}
Tracing.getTracer ()
obtient le traceur d'un singleton ou global. À partir de là, utilisez spanBuilder
pour assembler le Span.
Le point est «setSampler». L'échantillonneur spécifie la fréquence d'obtention de la trace. Si vous ne spécifiez pas ici Samplers.alwaysSample ()
, les informations de trace ne seront pas toujours écrites. En production, il peut y avoir des cas où un seuil approprié est défini en termes de coût et de charge, mais au moment des tests, il y a trop peu de demandes autres que les tests de charge, alors assurez-vous de toujours spécifier.
Les détails des autres échantillonneurs peuvent être trouvés à ici.
Au fait, il est difficile d'écrire ce qui précède à chaque fois, j'ai donc défini l'aide suivante un jour.
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();
}
}
Le nom de la classe et le nom de la méthode sont acquis et automatiquement spécifiés dans le nom Span. Lors de son utilisation, cela ressemble à ce qui suit.
@GET
@Path("/{userId}/balance")
public Map<String, Long> getBalance(@PathParam("userId") String userId) {
return trace(() -> {
var balance = service.getBalance(userId);
return balance;
});
}
Si vous êtes dans la même application, faites simplement startScopedSpan
comme ci-dessus et le Span imbriqué sera créé sans autorisation.
Cependant, le cœur du traçage distribué est la coopération intersystème. Par conséquent, il est nécessaire de lier des contextes distants. Cependant, à partir de ce moment, les documents étaient anciens ou mal rédigés, il s'agissait donc d'essais et d'erreurs.
traceparent
Il semble qu'OpenCensus a transmis le contexte à l'origine en utilisant des informations d'en-tête telles que X-B3-TraceId / X-B3-ParentSpanId, mais maintenant c'est [Contexte de trace normalisé W3C](https: //w3c.github). .io / trace-context /) est utilisé. Cela intègre un paramètre appelé «traceparent» dans l'en-tête HTTP et assemble «Trace ID», «Span ID» et «Trace Options (trace-flags)» en fonction de celui-ci.
traceparent a les valeurs suivantes.
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
Le code ci-dessous analyse traceparent avec OpenCensus et crée un 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);
//Création de SpanContext à partir de traceparent
SpanContext spanContext = SpanContext.create(traceId, spanId, traceOptions);
Afin de transmettre le contexte à l'API distante, il est nécessaire d'ajouter traceparent à l'en-tête HTTP, etc. au moment de l'appel REST. Vous pouvez le faire manuellement, mais la bibliothèque OpenCensus pour JAX-RS "opencensus-contrib-http-jaxrs "Est utilisé.
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>>() {});
En enregistrant JaxrsClientFilter avec target.register (JaxrsClientFilter.class)
, traceparent est automatiquement généré à partir du Span actuel et ajouté à l'en-tête de la requête.
Il s'agit de l'implémentation de api-core côté serveur de JAX-RS. Il semble qu'il puisse être défini automatiquement en utilisant le filtre de conteneur de opencensus-contrib-http-jaxrs. Cela n'a pas fonctionné pour une raison quelconque, alors je vais le mettre en œuvre moi-même.
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;
}
}
En utilisant la classe TextFormat
, vous pouvez créer unSpanContext
sans avoir à effectuer vous-même l'analyse fastidieuse.
De plus, contrairement à la création de Span normale, utilisez spanBuilderWithRemoteParent
pour créer une étendue à partir d'un contexte distant.
Il n'y a aucun problème avec spanBuilder
lors de la création de Spans enfants suivants.
Cela complète les paramètres du traçage distribué à l'aide d'OpenCensus en Java, veuillez donc le déplacer et vérifier s'il est lié à Jaeger ou Cloud Trace.
J'ai essayé d'implémenter le traçage distribué en utilisant OpenCensus en Java. Pour être honnête, il y a des documents en un coup d'œil et c'est une spécification célèbre, donc il y aura beaucoup d'articles de blog, alors j'ai pensé que ce serait une victoire facile dans environ une heure, mais je suis devenu accro au marais et cela a pris environ une heure.
J'ai l'impression que je veux me débarrasser du manque de documents et des lacunes, mais je suis sûr que cet effort devrait être utilisé pour la télémétrie ouverte. De plus, je pense que la raison pour laquelle les détails ne sont pas écrits est essentiellement que certaines personnes qui sont familiarisées avec le traçage distribué conçoivent la bibliothèque et le FW puis les utilisent. MP Open Tracing ou Open Telemetry Open Telemetry Auto-Instrumentation for Java est touché même si vous ne le connaissez pas.
Eh bien, en conséquence, je pense que je suis devenu plus détaillé cette fois, alors je me demande si cela allait. La télémétrie ouverte devra bientôt être remise en question.
Alors bon piratage!
Recommended Posts