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
形式的Tags
K/V
形式的Logs
SpanContext
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 | gclient | HTTP 客户端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。 |
Http Server | ghttp | HTTP 服务端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。 |
gRPC Client | contrib/rpc/grpcx | gRPC 客户端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。 |
gRPC Server | contrib/rpc/grpcx | gRPC 服务端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。 |
Logging | glog | 日志内容中需要注入当前请求的 TraceId ,以方便通过日志快速查找定位问题点。该特性是由 glog 组件实现,这需要开发者在输出日志的时候调用 Ctx 链式操作方法将 context.Context 上下文变量传递到当前输出日志操作链路中,没有传递 context.Context 上下文变量,就会丢失日志内容中的 TraceId 。 |
ORM | gdb | 数据库的执行是很重要的链路环节, Orm 组件需要将自身的执行情况投递到链路中,作为执行链路的一部分。 |
NoSQL Redis | gredis | Redis 的执行也是很重要的链路环节, Redis 需要将自身的执行情况投递到链路中,作为执行链路的一部分。 |
Utils | gtrace | 对于 Tracing 特性的管理需要做一定的封装,主要考虑的是可扩展性和易用性两方面。该封装由 gtrace 模块实现,文档地址: https://pkg.go.dev/github.com/gogf/gf/v2/net/gtrace |