Traçage distribué avec OpenCensus et Java

introduction

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

Configuration du système

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.

opencensus.png

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.

la mise en oeuvre

Bibliothèques dépendantes

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.

Initialisation de l'exportateur

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());
    }
}

Création d'une plage de trace

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

Propagation du contexte de trace

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);

Client JAX-RS (donnez traceparent pendant l'appel REST)

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.

Client JAX-RS (création de Span à partir de traceparent)

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.

Résumé

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!

référence

Recommended Posts

Traçage distribué avec OpenCensus et Java
Utiliser java avec MSYS et Cygwin
Installez Java et Tomcat avec Ansible
Utilisez JDBC avec Java et Scala.
Sortie PDF et TIFF avec Java 8
Crypter avec Java et décrypter avec C #
Surveillez les applications Java avec jolokia et hawtio
Lier le code Java et C ++ avec SWIG
Essayons WebSocket avec Java et javascript!
[Java] Lecture et écriture de fichiers avec OpenCSV
Java et JavaScript
XXE et Java
Créez et testez des applications Java + Gradle avec Wercker
Essayez d'intégrer Ruby et Java avec Dapr
JSON avec Java et Jackson Part 2 XSS mesures
Préparer un environnement de scraping avec Docker et Java
KMS) Chiffrement d'enveloppe avec décryptage openssl et java
Crypter / décrypter avec AES256 en PHP et Java
[Java] Convertir et importer des valeurs de fichier avec OpenCSV
[Review] Lecture et écriture de fichiers avec java (JDK6)
[Java] Aligne les caractères même avec des caractères mixtes demi-largeur et pleine largeur
Utilisez Fast Mapping Livery MapStruct avec Lombok et Java 11
Getter et Setter (Java)
Changer de siège avec Java
Tableau 2D AtCoder ABC129 D résolu en Ruby et Java
[Java] Thread et exécutable
Résumé du comportement de ToString avec les annotations Java et Groovy
Installez Java avec Ansible
Exécutez Maven sur Java 8 lors de la compilation sur Java 6 et des tests sur Java 11
Résolution avec Ruby, Perl et Java AtCoder ABC 128 C
Java vrai et faux
[Java] Comparaison des chaînes de caractères et && et ||
Téléchargement confortable avec JAVA
[Java] Se référer et définir des variables privées avec réflexion
Changer java avec direnv
Java - Sérialisation et désérialisation
[Java] Arguments et paramètres
Téléchargement Java avec Ansible
Je veux faire des transitions d'écran avec kotlin et java!
timedatectl et Java TimeZone
[Java] Branchement et répétition
Préparer l'environnement pour java11 et javaFx avec Ubuntu 18.4
Raclons avec Java! !!
Application de reconnaissance faciale conçue avec Amazon Rekognition et Java
Construire Java avec Wercker
[Java] Types de variables et types
java (classe et instance)
[Java] Développement avec plusieurs fichiers en utilisant package et import
Java EE sans serveur à partir de Quarkus et Cloud Run
[Java] Surcharge et remplacement
Conversion Endian avec JAVA
Stocker dans une carte Java 2D et tourner avec pour instruction
Trouvez la classe d'adresse et le type d'adresse à partir de l'adresse IP avec Java
Premiers pas avec Java et création d'un éditeur Ascii Doc avec JavaFX
Agrégation de fenêtres de données de capteurs avec Apache Flink et Java 8
Gérez l'API de date et d'heure Java 8 avec Thymeleaf avec Spring Boot
Agrégation et analyse de journaux (utilisation d'AWS Athena en Java)
Gérez les exceptions avec fraîcheur avec les expressions lambda Java 8 et l'API Stream
Je veux faire une liste avec kotlin et java!
Je veux créer une fonction avec kotlin et java!
Kotlin post- et pré-incrémentation et surcharge des opérateurs (comparaison avec C, Java, C ++)