一、OpenTelemetry
分布式链路跟踪( Distributed Tracing )的概念最早是由 Google 提出来的,发展至今技术已经比较成熟,也是有一些协议标准可以参考。目前在 Tracing 技术这块比较有影响力的是两大开源技术框架: Netflix 公司开源的 OpenTracing 和 Google 开源的 OpenCensus。两大框架都拥有比较高的开发者群体。为形成统一的技术标准,两大框架最终磨合成立了 OpenTelemetry 项目,简称 otel。具体可以参考:
因此,我们的 Tracing 技术方案以 OpenTelemetry 为实施标准,协议标准的一些 Golang 实现开源项目:
- https://github.com/open-telemetry/opentelemetry-go
- 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 表示一次完整的追踪链路, tracer 由一个或多个 span 组成。下图示例表示了一个由 8 个 span 组成的 tracer:
[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) - 服务的开始时间和结束时间
K/V形式的TagsK/V形式的LogsSpanContext
Span 是这么多对象中使用频率最高的,因此创建 Span 也非常简便,例如:
gtrace.NewSpan(ctx, spanName, opts...)
Attributes
Attributes 以 K/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
Events 与 Attributes 类似,也是 K/V 键值对形式。与 Attributes 不同的是, Events 还会记录写入 Events 的时间,因此 Events 主要用于记录某些事件发生的时间。 Events 的 key 值同样必须为字符串,但对 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的信息,比如:span_id, trace_id。 -
Baggage- 为整条追踪连保存跨服务(跨进程)的K/V格式的用户自定义数据。Baggage与Attributes类似,也是K/V键值对。与Attributes不同的是: -
其
key跟value都只能是字符串格式 -
Baggage不仅当前span可见,其会随着SpanContext传递给后续所有的子span。要小心谨慎的使用Baggage- 因为在所有的span中传递这些K,V会带来不小的网络和CPU开销。
Propagator
Propagator 传播器用于端对端的数据编码/解码,例如: Client 到 Server 端的数据传输, TraceId、 SpanId 和 Baggage 也是需要通过传播器来管理数据传输。业务端开发者往往对 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 只会自动创建 TraceID 及 SpanID,并不会执行复杂逻辑。
包括但不限于以下核心组件:
Http Client
HTTP 客户端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。
Http Server
HTTP 服务端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。
gRPC Client
gRPC 客户端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。
gRPC Server
gRPC 服务端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。
Logging
日志内容中需要注入当前请求的 TraceId,以方便通过日志快速查找定位问题点。该特性是由 glog 组件实现,这需要开发者在输出日志的时候调用 Ctx 链式操作方法将 context.Context 上下文变量传递到当前输出日志操作链路中,没有传递 context.Context 上下文变量,就会丢失日志内容中的 TraceId。
ORM
数据库的执行是很重要的链路环节, Orm 组件需要将自身的执行情况投递到链路中,作为执行链路的一部分。
Redis
Redis 的执行也是很重要的链路环节, Redis 需要将自身的执行情况投递到链路中,作为执行链路的一部分。
Utils
对于 Tracing 特性的管理需要做一定的封装,主要考虑的是可扩展性和易用性两方面。该封装由 gtrace 模块实现,文档地址: https://pkg.go.dev/github.com/gogf/gf/v2/net/gtrace