php小編小新今天為大家介紹一個強大的工具-OpenTelemetry,它可以幫助開發者在不同的服務中實現統一的範圍管理。在現代的分散式系統中,應用程式通常由多個微服務組成,每個微服務都有自己的日誌、指標和追蹤資訊。 OpenTelemetry提供了一種簡單而強大的方式來整合和管理這些訊息,使得開發者能夠更好地理解和調試整個系統的性能和行為。無論是在本地開發環境還是在生產環境中,OpenTelemetry都能幫助開發者更好地理解和優化他們的應用程式。
我剛開始使用 opentelemetry,並為此創建了兩個(微)服務:standard 和 geomap。
最終用戶向standard服務發送請求,後者會向geomap發送請求以獲取訊息,然後再將結果傳回給最終用戶。我使用 grpc 進行所有通訊。
我已經對我的功能進行了這樣的檢測:
對於標準:
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()), ) }
對於地理地圖:
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 }
這兩個服務都配置為將其跨度發送到 jaeger 後端並共享幾乎相同的主要功能(評論中指出了細微的差異):
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) } }
當我查看最終用戶對標準服務的請求產生的追蹤時,我可以看到它正如預期的那樣,調用其geomap服務:
但是,我沒有看到已新增至子範圍的任何屬性或事件(我在偵測geomapgetcountry 函數時新增了一個屬性和2 個事件/em>) 。
然而,我注意到這些屬性在另一個單獨的追蹤中可用(在 jaeger 中的「geomap」服務下可用),其跨度 id 與標準服務中的子跨度完全無關:
現在我期望的是有一個跟踪,並查看與 標準 範圍內的子範圍中的 geomap 相關的所有屬性/事件。如何從這裡得到預期的結果?
跨度上下文(包含追蹤id 和跨度id,如「service instrumentation & 中所述)術語")應該從父跨度傳播到子跨度,以便它們成為同一跟踪的一部分。
使用 opentelemetry,這通常是透過使用為各種庫(包括 grpc)提供的插件來檢測程式碼來自動完成的。
但是,在您的情況下,傳播似乎無法正常工作。
在您的程式碼中,您將在getstandard
函數中啟動一個新範圍,然後在發出getcountry
請求時使用該上下文(newctx
) 。這是正確的,因為新上下文應該包含父跨度的跨度上下文 (getstandard
)。
但問題可能與您的 createclient
函數有關:
func createclient(ctx context.context, svcaddr string) (*grpc.clientconn, error) { return grpc.dialcontext(ctx, svcaddr, grpc.withtransportcredentials(insecure.newcredentials()), grpc.withunaryinterceptor(otelgrpc.unaryclientinterceptor()), ) }
您正確使用了otelgrpc.unaryclientinterceptor
在這裡,這應該確保上下文正確傳播,但不清楚何時調用此函數。如果在呼叫 getstandard
函數之前呼叫它,則用於建立客戶端的上下文將不包含來自 getstandard
的跨度上下文。
為了進行測試,請嘗試確保在呼叫 getstandard
函數之後建立用戶端,並且在整個請求中使用相同的上下文。
您可以透過將 newctx
直接傳遞給 getcountry
函數來完成此操作,如 getstandard
函數的修改版本所示:
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 }
現在,用於建立客戶端並發出 getcountry
請求的上下文將包括來自 getstandard
的跨度上下文,並且它們應作為 jaeger 中同一追蹤的一部分出現。
(一如既往,請檢查 createclient
和 getcountry
等函數傳回的錯誤,為簡潔起見,此處未顯示)。
此外:
另請檢查您的傳播器:確保您使用相同的上下文傳播器 a> 在這兩個服務中,最好是w3c tracecontextpropagator,這是opentelemetry中預設的。
您可以如下明確設定傳播器:
otel.settextmappropagator(propagation.tracecontext{})
將以上行加入到兩個服務中 main
函數的開頭。
確保傳遞元資料:grpc 攔截器應自動從請求的元資料中註入/提取追蹤上下文,但要仔細檢查以確保其正常運作。
在 getcountry
函數中啟動跨度後,您可以記錄追蹤 id 和跨度 id:
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 应该匹配。
以上是使用 OpenTelemetry 統一不同服務的範圍的詳細內容。更多資訊請關注PHP中文網其他相關文章!