全部產品
Search
文件中心

Function Compute:鏈路追蹤

更新時間:Jul 06, 2024

本文介紹Go運行環境的鏈路追蹤相關內容。

背景資訊

阿里雲鏈路追蹤服務(Tracing Analysis)基於OpenTracing標準,相容開源社區,為分布式應用的開發人員提供了完整的分布式調用鏈查詢和診斷、分布式拓撲動態發現、應用效能即時匯總等功能。

Function Compute與鏈路追蹤整合後,支援使用Jaeger SDKOpenTelemetry上傳鏈路資訊,使您能夠跟蹤函數的執行,協助您快速分析和診斷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))
    }