跳到主要内容
版本:2.10.x(Latest)

OpenTelemetry

分布式链路跟踪(Distributed Tracing)的概念最早是由 Google 提出来的,发展至今技术已经比较成熟,也是有一些协议标准可以参考。目前在 Tracing 技术这块比较有影响力的是两大开源技术框架:Netflix 公司开源的 OpenTracingGoogle 开源的 OpenCensus。两大框架都拥有比较高的开发者群体。为形成统一的技术标准,两大框架最终磨合成立了 OpenTelemetry 项目,简称 otel。具体可以参考:

  1. OpenTracing介绍
  2. OpenTelemetry介绍

因此,我们的 Tracing 技术方案以 OpenTelemetry 为实施标准,协议标准的一些 Golang 实现开源项目:

  1. https://github.com/open-telemetry/opentelemetry-go
  2. https://github.com/open-telemetry/opentelemetry-go-contrib

其他第三方的框架和系统(如 Jaeger/Prometheus/Grafana 等)也会按照标准化的规范来对接 OpenTelemetry,使得系统的开发和维护成本大大降低。

重要概念

我们先看看 OpenTelemetry 的架构图,我们这里不会完整介绍,只会介绍其中大家常用的几个概念。 关于 OpenTelemetry 的内部技术架构设计介绍,可以参考 OpenTelemetry架构 , 关于语义约定请参考: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md

TracerProvider

主要负责创建 Tracer,一般是需要第三方的分布式链路跟踪管理平台提供具体的实现。默认情况是一个空的 TracerProvider (NoopTracerProvider),虽然也能创建 Tracer 但是内部其实不会执行具体的数据流传输逻辑。

Tracer

Tracer 是创建和管理 Span 的接口。一次完整的追踪链路(Trace)由一个或多个 Span 组成。下图示例表示了一个由 8Span 组成的追踪链路:

        [Span A]  ←←←(the root span)
|
+------+------+
| |
[Span B] [Span C] ←←←(Span C is a `ChildOf` Span A)
| |
[Span D] +---+-------+
| |
[Span E] [Span F] >>> [Span G] >>> [Span H]



(Span G `FollowsFrom` Span F)

时间轴的展现方式会更容易理解:

––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|> time

[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]

我们通常通过以下方式创建一个 Tracer

gtrace.NewTracer(tracerName)

Span

Span 是一条追踪链路中的基本组成要素,一个 Span 表示一个独立的工作单元,比如可以表示一次函数调用、一次 HTTP 请求等。Span 会记录如下基本要素:

  • 操作名称(Operation Name
  • 服务的开始时间和结束时间
  • Attributes(属性,K/V 键值对形式)
  • Events(事件,K/V 键值对形式)
  • SpanContext(跨进程传播的上下文信息)

Span 是这么多对象中使用频率最高的,因此创建 Span 也非常简便,例如:

gtrace.NewSpan(ctx, spanName, opts...)

Attributes

AttributesK/V 键值对的形式保存用户自定义标签,主要用于链路追踪结果的查询过滤。例如:http.method="GET", http.status_code=200。其中 key 值必须为字符串,value 必须是字符串、布尔型或者数值型。Span 中的 Attributes 仅自己可见,不会随着 SpanContext 传递给后续 Span。设置 Attributes 方式例如:

span.SetAttributes(
label.String("http.remote", conn.RemoteAddr().String()),
label.String("http.local", conn.LocalAddr().String()),
)

Events

EventsAttributes 类似,也是 K/V 键值对形式。与 Attributes 不同的是,Events 还会记录写入事件的时间戳,因此 Events 主要用于记录某些事件发生的时刻。Eventskey 值同样必须为字符串,但对 value 类型则没有限制。例如:

span.AddEvent("http.request", trace.WithAttributes(
label.Any("http.request.header", headers),
label.Any("http.request.baggage", gtrace.GetBaggageMap(ctx)),
label.String("http.request.body", bodyContent),
))

SpanContext

SpanContext 携带着一些用于**跨服务通信(跨进程)**的数据,主要包含:

  • 足够在系统中标识该 Span 的信息,比如:SpanIdTraceId
  • Baggage - 为整条追踪链保存跨服务(跨进程)的 K/V 格式的用户自定义数据。BaggageAttributes 类似,也是 K/V 键值对。与 Attributes 不同的是:
    • keyvalue 都只能是字符串格式
    • Baggage 不仅当前 Span 可见,其会随着 SpanContext 传递给后续所有的子 Span。要小心谨慎地使用 Baggage - 因为在所有的 Span 中传递这些 K/V 会带来不小的网络和 CPU 开销。

Propagator

Propagator 传播器用于端对端的上下文数据编码/解码,例如:ClientServer 端的数据传输,TraceIdSpanIdBaggage 都需要通过传播器来管理数据传输。业务端开发者往往对 Propagator 无感知,只有中间件/拦截器的开发者需要了解它的作用。OpenTelemetry 的标准协议实现库提供了常用的 TextMapPropagator,用于常见的文本数据端到端传输。此外,为保证 TextMapPropagator 中的传输数据兼容性,不应当包含特殊字符,具体请参考:https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md

GoFrame 框架通过 gtrace 模块使用了以下传播器对象,并全局设置到了 OpenTelemetry 中:

// defaultTextMapPropagator is the default propagator for context propagation between peers.
defaultTextMapPropagator = propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
)

支持组件

提示

GoFrame 的核心组件都已经全面支持 OpenTelemetry 标准,并且 自动开启 了链路跟踪特性,开发者无需显示调用、使用无感知。在没有注入外部 TracerProvider 的情况下,框架会使用默认的 TracerProvider,该 TracerProvider 只会自动创建 TraceIDSpanID,以便打通请求日志的链路,并不会执行复杂逻辑。

包括但不限于以下核心组件:

自动支持链路跟踪特性的组件组件名描述
HTTP ClientgclientHTTP 客户端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。
HTTP ServerghttpHTTP 服务端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。
gRPC Clientcontrib/rpc/grpcxgRPC 客户端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。
gRPC Servercontrib/rpc/grpcxgRPC 服务端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。
Loggingglog日志内容中需要注入当前请求的 TraceId,以便通过日志快速定位问题。该特性由 glog 组件实现,开发者在输出日志时需要调用 Ctx 链式操作方法将 context.Context 上下文变量传递到日志输出链路中。如果没有传递 context.Context 上下文变量,日志内容中将会丢失 TraceId
ORMgdb数据库的执行是很重要的链路环节,ORM 组件会将自身的执行情况记录到链路中,作为执行链路的一部分。
NoSQL RedisgredisRedis 的执行也是很重要的链路环节,Redis 组件会将自身的执行情况记录到链路中,作为执行链路的一部分。
Utilsgtrace对于 Tracing 特性的管理需要做一定的封装,主要考虑的是可扩展性和易用性两方面。该封装由 gtrace 模块实现,文档地址: https://pkg.go.dev/github.com/gogf/gf/v2/net/gtrace

参考资料