本文介紹Go運(yùn)行環(huán)境的鏈路追蹤相關(guān)內(nèi)容。

背景信息

阿里云鏈路追蹤服務(wù)(Tracing Analysis)基于OpenTracing標(biāo)準(zhǔn),兼容開(kāi)源社區(qū),為分布式應(yīng)用的開(kāi)發(fā)者提供了完整地分布式調(diào)用鏈查詢(xún)和診斷、分布式拓?fù)鋭?dòng)態(tài)發(fā)現(xiàn)、應(yīng)用性能實(shí)時(shí)匯總等功能。

函數(shù)計(jì)算與鏈路追蹤集成后,支持使用Jaeger SDKOpenTelemetry上傳鏈路信息,使您能夠跟蹤函數(shù)的執(zhí)行,幫助您快速分析和診斷Serverless架構(gòu)下的性能瓶頸,提高Serverless場(chǎng)景的開(kāi)發(fā)診斷效率。

功能簡(jiǎn)介

您可以在函數(shù)計(jì)算控制臺(tái)配置鏈路追蹤。具體操作,請(qǐng)參見(jiàn)配置鏈路追蹤

為服務(wù)開(kāi)啟鏈路追蹤后,函數(shù)計(jì)算會(huì)自動(dòng)記錄請(qǐng)求在系統(tǒng)側(cè)的耗時(shí),包含冷啟動(dòng)耗時(shí)、Initializer函數(shù)的耗時(shí)和函數(shù)的執(zhí)行時(shí)間等。關(guān)于下圖中系統(tǒng)Span的說(shuō)明,請(qǐng)參見(jiàn)Span名稱(chēng)說(shuō)明鏈路追蹤

如您還需查看函數(shù)內(nèi)業(yè)務(wù)側(cè)的耗時(shí),例如,在函數(shù)內(nèi)訪問(wèn)RDS,NAS等服務(wù)的耗時(shí),可以通過(guò)創(chuàng)建自定義Span來(lái)實(shí)現(xiàn)。

示例代碼

函數(shù)計(jì)算的鏈路分析基于OpenTracing協(xié)議的Jaeger實(shí)現(xiàn),Go運(yùn)行時(shí)提供以下兩種創(chuàng)建自定義Span的方式。

使用OpenTelemetry(推薦)

在Go語(yǔ)言的代碼中,您可以通過(guò)OpenTelemetry SDK手動(dòng)埋點(diǎn),將數(shù)據(jù)上報(bào)到鏈路追蹤服務(wù)端。完整的示例代碼,請(qǐng)參見(jiàn)golang-tracing-openTelemetry

示例代碼解析如下。
  • 添加依賴(lài)。
    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
  • 上報(bào)數(shù)據(jù)到鏈路追蹤服務(wù)端。
    func HandleRequest(ctx context.Context, event MyEvent) (string, error) {
        // 獲取函數(shù)計(jì)算上下文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)
        }
        // 創(chuàng)建Tracer Provider
        tp, err := NewTracerProvider(endpoint)
        if err != nil {
            return "", fmt.Errorf("OpenTracingJaegerEndpoint: %s, error: %v", fctx.Tracing.JaegerEndpoint, err)
        }
        // 設(shè)置為全局
        otel.SetTracerProvider(tp)
        if err != nil {
            return "", fmt.Errorf("failed to getFcSpanCtx, error: %v", err)
        }
        // 創(chuàng)建自定義Span
        startMySpan(trace.ContextWithSpanContext(ctx, spanCtx))
        return fmt.Sprintf("hello world! 你好,%s!", event.Name), nil
    }
  • 創(chuàng)建一個(gè)tracerProvider,提供對(duì)Tracers的訪問(wèn)。
    func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
        // 創(chuàng)建Jaeger exporter
        exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
        if err != nil {
            return nil, err
        }
        tp := tracesdk.NewTracerProvider(
            // 注冊(cè)exporter
            tracesdk.WithBatcher(exp),
            // 在Resource里記錄應(yīng)用信息
            tracesdk.WithResource(resource.NewWithAttributes(
                semconv.SchemaURL,
                semconv.ServiceNameKey.String("FCTracer"),
                attribute.String("environment", "production"),
                attribute.Int64("ID", 1),
            )),
        )
        return tp, nil
    }
  • 獲取上下文的Tracing信息,將OpenTracingSpanContext轉(zhuǎn)換為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
    }
  • 創(chuàng)建tracer并通過(guò)轉(zhuǎn)換的OpenTelemetrySpanContext創(chuàng)建子Span。每一個(gè)Span代表調(diào)用鏈中被命名并計(jì)時(shí)的連續(xù)性執(zhí)行片段,您也可以基于該Span繼續(xù)創(chuàng)建子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

您可以通過(guò)Jaeger SDK埋點(diǎn),將數(shù)據(jù)上報(bào)到鏈路追蹤服務(wù)端。完整的示例代碼,請(qǐng)參見(jiàn)golang-tracing

示例代碼解析如下。
  • 添加依賴(lài)。
    go get github.com/aliyun/fc-runtime-go-sdk
    go get github.com/opentracing/opentracing-go
    go get github.com/uber/jaeger-client-go
  • 上報(bào)數(shù)據(jù)到鏈路追蹤服務(wù)端。
    func HandleRequest(ctx context.Context, event MyEvent) (string, error) {
        // 獲取函數(shù)計(jì)算上下文Tracing信息
        fctx, _ := fccontext.FromContext(ctx)
        endpoint := fctx.Tracing.JaegerEndpoint
        OpenTracingSpanContext := fctx.Tracing.OpenTracingSpanContext
        if len(endpoint) == 0 {
            return "", fmt.Errorf("invalid jaeger endpoint")
        }
        // 創(chuàng)建Tracer
        tracer, closer := NewJaegerTracer("FCTracer", endpoint)
        defer closer.Close()
        // 恢復(fù)spanContext
        spanContext, err := jaeger.ContextFromString(OpenTracingSpanContext)
        if err != nil {
            return "", fmt.Errorf("OpenTracingSpanContext: %s, error: %v", fctx.Tracing.OpenTracingSpanContext, err)
        }
        // 創(chuàng)建自定義Span
        startMySpan(spanContext, tracer)
        return fmt.Sprintf("hello world! 你好,%s!", event.Name), nil
    }
  • 創(chuàng)建一個(gè)tracer對(duì)象,提供對(duì)Tracers的訪問(wèn)。
    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
    }
  • 轉(zhuǎn)換spanContext并創(chuàng)建自定義Span,您也可以基于該Span繼續(xù)創(chuàng)建子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)
        // 開(kāi)啟子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))
    }