本文介绍Go运行环境的链路追踪相关内容。
背景信息
阿里云链路追踪服务(Tracing Analysis)基于OpenTracing标准,兼容开源社区,为分布式应用的开发者提供了完整地分布式调用链查询和诊断、分布式拓扑动态发现、应用性能实时汇总等功能。
函数计算与链路追踪集成后,支持使用Jaeger SDK和OpenTelemetry上传链路信息,使您能够跟踪函数的执行,帮助您快速分析和诊断Serverless架构下的性能瓶颈,提高Serverless场景的开发诊断效率。
功能简介
您可以在函数计算控制台配置链路追踪。具体操作,请参见配置链路追踪。
为服务开启链路追踪后,函数计算会自动记录请求在系统侧的耗时,包含冷启动耗时、Initializer函数的耗时和函数的执行时间等。关于下图中系统Span的说明,请参见Span名称说明。
如您还需查看函数内业务侧的耗时,例如,在函数内访问RDS,NAS等服务的耗时,可以通过创建自定义Span来实现。
示例代码
函数计算的链路分析基于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) { // 获取函数计算上下文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) { // 获取函数计算上下文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)) }