Maison > Article > développement back-end > Unifiez la portée des différents services grâce à 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.
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()), ) }
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 }
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) } }
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 ?
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()), ) }
Vous utilisez correctement otelgrpc.unaryclientinterceptor
otelgrpc.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
pas getstandard
le contexte span de
newctx
直接传递给 getcountry
函数来完成此操作,如 getstandard
Pour les tests, essayez de vous assurer que le client est créé après avoir appelé la fonction
getcountry
请求的上下文将包括来自 getstandard
Vous pouvez le faire en passant newctx
directement à la fonction
: createclient
和 getcountry
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 }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.et , qui ne sont pas affichées ici par souci de concision). a> Aussi :
Vérifiez également votre propagateur : assurez-vous d'utiliser le mêmecontext propagatormain
dans les deux services, de préférence le
Vous pouvez définir le propagateur explicitement comme suit :
otel.settextmappropagator(propagation.tracecontext{})
Ajoutez les lignes ci-dessus au début des deux fonctions de service getcountry
.
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()
并在 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()
如果上下文正确传播,两个服务中的跟踪 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!