Heim  >  Artikel  >  Backend-Entwicklung  >  Vereinheitlichen Sie den Umfang verschiedener Dienste mithilfe von OpenTelemetry

Vereinheitlichen Sie den Umfang verschiedener Dienste mithilfe von OpenTelemetry

WBOY
WBOYnach vorne
2024-02-13 23:00:11438Durchsuche

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

Der Herausgeber von PHP, Xiaoxin, stellt Ihnen heute ein leistungsstarkes Tool vor – OpenTelemetry, das Entwicklern dabei helfen kann, eine einheitliche Bereichsverwaltung in verschiedenen Diensten zu erreichen. In modernen verteilten Systemen bestehen Anwendungen häufig aus mehreren Mikrodiensten, von denen jeder über eigene Protokolle, Metriken und Ablaufverfolgungsinformationen verfügt. OpenTelemetry bietet eine einfache und leistungsstarke Möglichkeit, diese Informationen zu integrieren und zu verwalten, sodass Entwickler die Leistung und das Verhalten des gesamten Systems besser verstehen und debuggen können. Ob in einer lokalen Entwicklungsumgebung oder in einer Produktionsumgebung, OpenTelemetry hilft Entwicklern, ihre Anwendungen besser zu verstehen und zu optimieren.

Frageninhalt

Ich habe gerade angefangen, Opentelemetry zu verwenden und zwei (Mikro-)Dienste dafür erstellt: Standard und Geomap.

Der Endbenutzer sendet eine Anfrage an den Standard-Dienst, der wiederum eine Anfrage an Geomap sendet, um die Informationen zu erhalten, die wiederum die Ergebnisse an den Endbenutzer zurücksenden. Ich verwende grpc für die gesamte Kommunikation.

Ich habe meine Funktion so instrumentiert:

Für Standards:

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

Für Geokarten:

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

}

Beide Dienste sind so konfiguriert, dass sie ihre Spans an das Jaeger-Backend senden und fast die gleiche Hauptfunktionalität haben (subtile Unterschiede werden in den Kommentaren erwähnt):

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

Wenn ich mir die Ablaufverfolgung ansehe, die durch die Anfrage des Endbenutzers an den StandardDienst generiert wurde, kann ich sehen, dass er wie erwartet seinen GeomapDienst aufruft:

Allerdings sehe ich keine Eigenschaften oder Ereignisse, die dem Unterbereich hinzugefügt wurden (ich habe eine Eigenschaft und zwei Ereignisse hinzugefügt, als ich die Funktion getcountry von geomap

Allerdings ist mir aufgefallen, dass diese Eigenschaften in einem anderen separaten Trace verfügbar sind (verfügbar unter dem „geomap“-Dienst in Jaeger), dessen Span-IDs völlig unabhängig von den Subspans im Standarddienst sind:

Was ich nun erwarte, ist, eine Ablaufverfolgung zu haben und alle Eigenschaften/Ereignisse im Zusammenhang mit der Geomap in Unterbereichen innerhalb des Standardbereichs anzuzeigen. Wie erhalte ich hier das erwartete Ergebnis?

Problemumgehung

Der Span-Kontext (der die Trace-ID und die Span-ID enthält, wie in „Dienstinstrumentierung und -begriff “ beschrieben) sollte vom übergeordneten Span an den untergeordneten Span weitergegeben werden, damit sie Teil desselben Trace werden.

Mit Opentelemetry erfolgt dies normalerweise automatisch durch Instrumentierung des Codes mithilfe von Plugins, die für verschiedene Bibliotheken, einschließlich grpc, bereitgestellt werden.
Allerdings scheint die Weitergabe in Ihrem Fall nicht richtig zu funktionieren.

In Ihrem Code hätten Sie getstandard 函数中启动一个新范围,然后在发出 getcountry 请求时使用该上下文 (newctx)。这是正确的,因为新上下文应该包含父跨度的跨度上下文 (getstandard).
Aber das Problem könnte mit Ihrer createclient-Funktion zusammenhängen:

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

Sie verwenden otelgrpc.unaryclientinterceptor richtig. otelgrpc.unaryclientinterceptor 在这里,这应该确保上下文正确传播,但不清楚何时调用此函数。如果在调用 getstandard 函数之前调用它,则用于创建客户端的上下文将包含来自 getstandard Dies sollte hier sicherstellen, dass der Kontext korrekt weitergegeben wird, aber es ist nicht klar, wann diese Funktion aufgerufen wird. Wenn es vor dem Aufruf der Funktion

aufgerufen wird, enthält der zum Erstellen des Clients verwendete Kontext

nicht getstandard den Span-Kontext von

.

newctx 直接传递给 getcountry 函数来完成此操作,如 getstandardStellen Sie zum Testen sicher, dass der Client nach dem Aufruf der Funktion

erstellt wird und dass in der gesamten Anfrage derselbe Kontext verwendet wird.

getcountry 请求的上下文将包括来自 getstandardSie können dies tun, indem Sie newctx direkt an die

-Funktion übergeben, wie in der modifizierten Version der

-Funktion gezeigt: 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
}

Der Kontext, der zum Erstellen des Clients und zum Stellen der

-Anfrage verwendet wird, enthält jetzt Span-Kontexte von

und sie sollten als Teil derselben Ablaufverfolgung in Jaeger erscheinen.
  • (Überprüfen Sie wie immer, ob von Funktionen wie

    und Fehler zurückgegeben werden, die der Kürze halber hier nicht angezeigt werden.) a> Außerdem:

    Überprüfen Sie auch Ihren Propagator: Stellen Sie sicher, dass Sie in beiden Diensten denselben

    Kontextpropagatormain verwenden, vorzugsweise den

    w3c Tracecontextpropagator
  • , der in Opentelemetry die Standardeinstellung ist.
  • Sie können den Propagator explizit wie folgt festlegen:

    otel.settextmappropagator(propagation.tracecontext{})
    

    Fügen Sie die obige Zeile am Anfang beider Dienstfunktionen hinzu. getcountry

    🎜🎜Stellen Sie sicher, dass Sie Metadaten übergeben: Der grpc-Interceptor sollte den Tracing-Kontext automatisch aus den angeforderten Metadaten einfügen/extrahieren, aber überprüfen Sie noch einmal, ob er ordnungsgemäß funktioniert. 🎜 🎜Nachdem Sie den Span in der Funktion 🎜 gestartet haben, können Sie die Trace-ID und die Span-ID protokollieren: 🎜
    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 应该匹配。

Das obige ist der detaillierte Inhalt vonVereinheitlichen Sie den Umfang verschiedener Dienste mithilfe von OpenTelemetry. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:stackoverflow.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen