背景資訊
阿里雲鏈路追蹤服務(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(推薦)
使用Jaeger SDK
在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埋點,將資料上報到鏈路追蹤服務端。完整的範例程式碼,請參見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))
}