本文介紹Go運行環境的鏈路追蹤相關內容。
背景資訊
阿里雲鏈路追蹤服務(Tracing Analysis)基於OpenTracing標準,相容開源社區,為分布式應用的開發人員提供了完整的分布式調用鏈查詢和診斷、分布式拓撲動態發現、應用效能即時匯總等功能。
Function Compute與鏈路追蹤整合後,支援使用Jaeger SDK和OpenTelemetry上傳鏈路資訊,使您能夠跟蹤函數的執行,協助您快速分析和診斷Serverless架構下的效能瓶頸,提高Serverless情境的開發診斷效率。
功能簡介
您可以在Function Compute控制台配置鏈路追蹤。具體操作,請參見配置鏈路追蹤。
為服務開啟鏈路追蹤後,Function Compute會自動記錄請求在系統側的耗時,包含冷啟動耗時、Initializer函數的耗時和函數的執行時間等。關於下圖中系統Span的說明,請參見Span名稱說明。
如您還需查看函數內業務側的耗時,例如,在函數內訪問RDS或NAS等服務的耗時,可以通過建立自訂Span來實現。
範例程式碼
Function Compute的鏈路分析基於OpenTracing協議的Jaeger實現,Go運行時提供以下兩種建立自訂Span的方式。
使用OpenTelemetry(推薦)
在Go語言的代碼中,您可以通過OpenTelemetry SDK手動埋點,將資料上報到鏈路追蹤服務端。完整的範例程式碼,請參見golang-tracing-openTelemetry。
範例程式碼解析如下。
- 添加依賴。
go get github.com/aliyun/fc-runtime-go-sdk go get go get go.opentelemetry.io/otel go get go.opentelemetry.io/otel/sdk go get go.opentelemetry.io/otel/exporters/jaeger
- 上報資料到鏈路追蹤服務端。
func HandleRequest(ctx context.Context, event MyEvent) (string, error) { // 擷取Function Compute上下文Tracing資訊 fctx, ok := fccontext.FromContext(ctx) if !ok { return "", fmt.Errorf("failed to get FcContext") } spanCtx, endpoint, err := getFcTracingInfo(fctx) if err != nil { return "", fmt.Errorf("failed to getFcTracingInfo, error: %v", err) } // 建立Tracer Provider tp, err := NewTracerProvider(endpoint) if err != nil { return "", fmt.Errorf("OpenTracingJaegerEndpoint: %s, error: %v", fctx.Tracing.JaegerEndpoint, err) } // 設定為全域 otel.SetTracerProvider(tp) if err != nil { return "", fmt.Errorf("failed to getFcSpanCtx, error: %v", err) } // 建立自訂Span startMySpan(trace.ContextWithSpanContext(ctx, spanCtx)) return fmt.Sprintf("hello world! 你好,%s!", event.Name), nil }
- 建立一個
tracerProvider
,提供對Tracers的訪問。func tracerProvider(url string) (*tracesdk.TracerProvider, error) { // 建立Jaeger exporter exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) if err != nil { return nil, err } tp := tracesdk.NewTracerProvider( // 註冊exporter tracesdk.WithBatcher(exp), // 在Resource裡記錄應用資訊 tracesdk.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String("FCTracer"), attribute.String("environment", "production"), attribute.Int64("ID", 1), )), ) return tp, nil }
- 擷取上下文的Tracing資訊,將OpenTracingSpanContext轉換為OpenTelemetrySpanContext。
func getFcTracingInfo(fctx *fccontext.FcContext) (trace.SpanContext, string, error) { // 擷取Tracing資訊 spanContext := trace.SpanContext{} endpoint := fctx.Tracing.JaegerEndpoint OpenTracingSpanContext := fctx.Tracing.OpenTracingSpanContext if len(endpoint) == 0 { return spanContext, endpoint, fmt.Errorf("invalid jaeger endpoint") } spanContextSlice := strings.Split(OpenTracingSpanContext, ":") // 填充TracingID高位 tid, err := trace.TraceIDFromHex("0000000000000000" + spanContextSlice[0]) if err != nil { return spanContext, endpoint, err } fid := trace.FlagsSampled spanContext = spanContext.WithTraceID(tid).WithTraceFlags(fid).WithRemote(true) return spanContext, endpoint, nil }
- 建立
tracer
並通過轉換的OpenTelemetrySpanContext建立子Span。每一個Span代表調用鏈中被命名並計時的連續性執行片段,您也可以基於該Span繼續建立子Span。func startMySpan(ctx context.Context) { // 使用全域TracerProvider. tr := otel.Tracer("fc-Trace") ctx, parentSpan := tr.Start(ctx, "fc-operation") defer parentSpan.End() parentSpan.SetAttributes(attribute.Key("version").String("fc-v1")) time.Sleep(150 * time.Millisecond) child(ctx) } func child(ctx context.Context) { tr := otel.Tracer("fc-Trace") _, childSpan := tr.Start(ctx, "fc-operation-child") defer childSpan.End() time.Sleep(100 * time.Millisecond) childSpan.AddEvent("timeout") }
使用Jaeger SDK
您可以通過Jaeger SDK埋點,將資料上報到鏈路追蹤服務端。完整的範例程式碼,請參見golang-tracing。
範例程式碼解析如下。
- 添加依賴。
go get github.com/aliyun/fc-runtime-go-sdk go get github.com/opentracing/opentracing-go go get github.com/uber/jaeger-client-go
- 上報資料到鏈路追蹤服務端。
func HandleRequest(ctx context.Context, event MyEvent) (string, error) { // 擷取Function Compute上下文Tracing資訊 fctx, _ := fccontext.FromContext(ctx) endpoint := fctx.Tracing.JaegerEndpoint OpenTracingSpanContext := fctx.Tracing.OpenTracingSpanContext if len(endpoint) == 0 { return "", fmt.Errorf("invalid jaeger endpoint") } // 建立Tracer tracer, closer := NewJaegerTracer("FCTracer", endpoint) defer closer.Close() // 恢複spanContext spanContext, err := jaeger.ContextFromString(OpenTracingSpanContext) if err != nil { return "", fmt.Errorf("OpenTracingSpanContext: %s, error: %v", fctx.Tracing.OpenTracingSpanContext, err) } // 建立自訂Span startMySpan(spanContext, tracer) return fmt.Sprintf("hello world! 你好,%s!", event.Name), nil }
- 建立一個
tracer
對象,提供對Tracers的訪問。func NewJaegerTracer(service, endpoint string) (opentracing.Tracer, io.Closer) { sender := transport.NewHTTPTransport(endpoint) tracer, closer := jaeger.NewTracer(service, jaeger.NewConstSampler(true), jaeger.NewRemoteReporter(sender)) return tracer, closer }
- 轉換spanContext並建立自訂Span,您也可以基於該Span繼續建立子Span。
func startMySpan(context jaeger.SpanContext, tracer opentracing.Tracer) { parentSpan := tracer.StartSpan("MyFCSpan", opentracing.ChildOf(context)) defer parentSpan.Finish() parentSpan.SetOperationName("fc-operation") parentSpan.SetTag("version", "fc-v1") time.Sleep(150 * time.Millisecond) // 開啟子Span childSpan := tracer.StartSpan("fc-operation-child", opentracing.ChildOf(parentSpan.Context())) defer childSpan.Finish() time.Sleep(100 * time.Millisecond) childSpan.LogFields( log.String("type", "cache timeout"), log.Int("waited.millis", 100)) }