Maison > développement back-end > Golang > le corps du texte

Unifiez la portée des différents services grâce à OpenTelemetry

WBOY
Libérer: 2024-02-13 23:00:11
avant
482 Les gens l'ont consulté

使用 OpenTelemetry 统一不同服务的范围

L'éditeur de PHP, Xiaoxin, vous présente aujourd'hui un outil puissant - OpenTelemetry, qui peut aider les développeurs à parvenir à une gestion unifiée de la portée dans différents services. Dans les systèmes distribués modernes, les applications sont souvent composées de plusieurs microservices, chacun avec ses propres journaux, métriques et informations de traçage. OpenTelemetry fournit un moyen simple et puissant d'intégrer et de gérer ces informations, permettant aux développeurs de mieux comprendre et déboguer les performances et le comportement de l'ensemble du système. Que ce soit dans un environnement de développement local ou dans un environnement de production, OpenTelemetry aide les développeurs à mieux comprendre et optimiser leurs applications.

Contenu de la question

Je viens de commencer à utiliser opentelemetry et j'ai créé deux (micro)services pour cela : standard et geomap.

L'utilisateur final envoie une requête au service standard, qui à son tour envoie une requête à geomap pour obtenir les informations, qui à son tour renvoie les résultats à l'utilisateur final. J'utilise grpc pour toutes les communications.

J'ai instrumenté ma fonction comme ceci :

Pour les Normes :

type standardservice struct {
    pb.unimplementedstandardserviceserver
}

func (s *standardservice) getstandard(ctx context.context, in *pb.getstandardrequest) (*pb.getstandardresponse, error) {

    conn, _:= createclient(ctx, geomapsvcaddr)
    defer conn1.close()

    newctx, span1 := otel.tracer(name).start(ctx, "getstandard")
    defer span1.end()

    countryinfo, err := pb.newgeomapserviceclient(conn).getcountry(newctx,
        &pb.getcountryrequest{
            name: in.name,
        })

    //...

    return &pb.getstandardresponse{
        standard: standard,
    }, nil

}

func createclient(ctx context.context, svcaddr string) (*grpc.clientconn, error) {
    return grpc.dialcontext(ctx, svcaddr,
        grpc.withtransportcredentials(insecure.newcredentials()),
        grpc.withunaryinterceptor(otelgrpc.unaryclientinterceptor()),
    )
}
Copier après la connexion

Pour Cartes géographiques :

type geomapservice struct {
    pb.unimplementedgeomapserviceserver
}

func (s *geomapservice) getcountry(ctx context.context, in *pb.getcountryrequest) (*pb.getcountryresponse, error) {

    _, span := otel.tracer(name).start(ctx, "getcountry")
    defer span.end()

    span.setattributes(attribute.string("country", in.name))

    span.addevent("retrieving country info")

    //...
    
    span.addevent("country info retrieved")

    return &pb.getcountryresponse{
        country: &country,
    }, nil

}
Copier après la connexion

Les deux services sont configurés pour envoyer leurs spans au backend Jaeger et partagent presque la même fonctionnalité principale (différences subtiles notées dans les commentaires) :

const (
    name        = "mapedia"
    service     = "geomap" //or standard
    environment = "production"
    id          = 1
)

func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
    // Create the Jaeger exporter
    exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
    if err != nil {
        return nil, err
    }
    tp := tracesdk.NewTracerProvider(
        // Always be sure to batch in production.
        tracesdk.WithBatcher(exp),
        // Record information about this application in a Resource.
        tracesdk.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceName(service),
            attribute.String("environment", environment),
            attribute.Int64("ID", id),
        )),
    )
    return tp, nil
}

func main() {

    tp, err := tracerProvider("http://localhost:14268/api/traces")
    if err != nil {
        log.Fatal(err)
    }

    defer func() {
        if err := tp.Shutdown(context.Background()); err != nil {
            log.Fatal(err)
        }
    }()
    otel.SetTracerProvider(tp)

    listener, err := net.Listen("tcp", ":"+port)
    if err != nil {
        panic(err)
    }

    s := grpc.NewServer(
        grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
    )
    reflection.Register(s)
    pb.RegisterGeoMapServiceServer(s, &geomapService{}) // or pb.RegisterStandardServiceServer(s, &standardService{})
    if err := s.Serve(listener); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}
Copier après la connexion

Quand je regarde la trace générée par la requête de l'utilisateur final au service standard, je constate qu'il appelle, comme prévu, son service geomap :

Cependant, je ne vois aucune propriété ou événement qui a été ajouté à la sous-plage (j'ai ajouté une propriété et 2 événements lorsque j'ai instrumenté la fonction getcountry< 的 de geomap<).

Cependant, j'ai remarqué que ces propriétés sont disponibles dans une autre trace distincte (disponible sous le service "geomap" de Jaeger) dont les identifiants de span n'ont aucun rapport avec les sous-spans du service standard :

Maintenant, ce que j'attends, c'est d'avoir une trace et de voir toutes les propriétés/événements liés à la geomap dans les sous-portées de la portée standard. Comment puis-je obtenir le résultat attendu à partir d’ici ?

Solution de contournement

Le contexte span (contenant l'identifiant de trace et l'identifiant de span comme décrit dans "instrumentation et terme de service ") doit être propagé du span parent au span enfant afin qu'ils fassent partie de la même trace.

En utilisant opentelemetry, cela se fait généralement automatiquement en instrumentant le code à l'aide de plugins fournis pour diverses bibliothèques, dont grpc.
Cependant, la propagation ne semble pas fonctionner correctement dans votre cas.

Dans votre code, vous auriez getstandard 函数中启动一个新范围,然后在发出 getcountry 请求时使用该上下文 (newctx)。这是正确的,因为新上下文应该包含父跨度的跨度上下文 (getstandard).
Mais le problème est peut-être lié à votre fonction createclient :

func createclient(ctx context.context, svcaddr string) (*grpc.clientconn, error) {
    return grpc.dialcontext(ctx, svcaddr,
        grpc.withtransportcredentials(insecure.newcredentials()),
        grpc.withunaryinterceptor(otelgrpc.unaryclientinterceptor()),
    )
}
Copier après la connexion

Vous utilisez correctement otelgrpc.unaryclientinterceptorotelgrpc.unaryclientinterceptor 在这里,这应该确保上下文正确传播,但不清楚何时调用此函数。如果在调用 getstandard 函数之前调用它,则用于创建客户端的上下文将包含来自 getstandard Ici, cela devrait garantir que le contexte se propage correctement, mais il n'est pas clair quand cette fonction est appelée. S'il est appelé avant d'appeler la fonction

, le contexte utilisé pour créer le client ne contiendra

pas getstandard le contexte span de

.

newctx 直接传递给 getcountry 函数来完成此操作,如 getstandardPour les tests, essayez de vous assurer que le client est créé après avoir appelé la fonction

et que le même contexte est utilisé tout au long de la requête.

getcountry 请求的上下文将包括来自 getstandardVous pouvez le faire en passant newctx directement à la fonction

, comme indiqué dans la version modifiée de la fonction

 : createclientgetcountry

func (s *standardservice) getstandard(ctx context.context, in *pb.getstandardrequest) (*pb.getstandardresponse, error) {
    newctx, span1 := otel.tracer(name).start(ctx, "getstandard")
    defer span1.end()

    conn, _:= createclient(newctx, geomapsvcaddr)
    defer conn.close()

    countryinfo, err := pb.newgeomapserviceclient(conn).getcountry(newctx,
        &pb.getcountryrequest{
            name: in.name,
        })

    //...

    return &pb.getstandardresponse{
        standard: standard,
    }, nil
}
Copier après la connexion

Le contexte utilisé pour créer le client et effectuer la requête

inclura désormais les contextes span de

et ils devraient apparaître dans le cadre de la même trace dans Jaeger.
  • (Comme toujours, vérifiez les erreurs renvoyées par des fonctions comme

    et , qui ne sont pas affichées ici par souci de concision). Aussi :

    Vérifiez également votre propagateur : assurez-vous d'utiliser le même

    context propagatormain dans les deux services, de préférence le

    w3c tracecontextpropagator
  • , qui est la valeur par défaut en opentelemetry.
  • Vous pouvez définir le propagateur explicitement comme suit :

    otel.settextmappropagator(propagation.tracecontext{})
    
    Copier après la connexion

    Ajoutez les lignes ci-dessus au début des deux fonctions de service getcountry.

    🎜 🎜🎜Assurez-vous de transmettre les métadonnées : l'intercepteur grpc doit automatiquement injecter/extraire le contexte de traçage des métadonnées de la requête, mais vérifiez à nouveau qu'il fonctionne correctement. 🎜 🎜Après avoir démarré le span dans la fonction 🎜, vous pouvez enregistrer l'identifiant de trace et l'identifiant du span : 🎜
    ctx, span := otel.tracer(name).start(ctx, "getcountry")
    sc := trace.spancontextfromcontext(ctx)
    log.printf("trace id: %s, span id: %s", sc.traceid(), sc.spanid())
    defer span.end()
    
    Copier après la connexion

    并在 getstandard 函数中执行相同的操作:

    newCtx, span1 := otel.Tracer(name).Start(ctx, "GetStandard")
    sc := trace.SpanContextFromContext(newCtx)
    log.Printf("Trace ID: %s, Span ID: %s", sc.TraceID(), sc.SpanID())
    defer span1.End()
    
    Copier après la connexion

    如果上下文正确传播,两个服务中的跟踪 id 应该匹配。

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:stackoverflow.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal