我们开发的应用程序可能具有不同的形态和架构:有些是单体应用,有些是微服务。为单体应用程序添加遥测数据相对来说简单,因为所有数据都在同一进程中。然而对于微服务应用程序,情况可能会更具挑战性。
通常,分布式微服务应用程序的不同服务之间仅通过网络连接。然而,当我们想要创建有效的链路追踪数据,就要考虑到下面的问题:
即使是微服务应用程序,我们也希望观察到从开始到结束的用户路径,这意味着跨越多个服务的边界。这就是我们所说的分布式链路追踪。不过我们如何实现这一点呢?我们如何使链路追踪信息贯穿可能是分布在多个进程,并且是不同的基础架构上呢?
传播( propagation )
在 OpenTelemetry 中,解决这个挑战的方案是通过传播来实现。这意味着以某种方式将链路追踪 ID(和父跨度 ID)传递给被调用服务,以便它们可以将该信息添加到分布式链路追踪路径中的一个跨度上。下面是一个示意图:
这里我们有三个服务,通过使用传播,我们能够将跟踪 ID 和父跨度 ID 作为头信息传递。在 Go 中,传播可以通过全局设置来处理:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
// ...
otel.SetTextMapPropagator(
propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{}),
)
在示例代码中,我们可以在控制器层(Handler,通常用于处理HTTP请求并生成相应的响应,承担的作用包括路由和请求分发,请求处理逻辑,响应生成)进行设置:
http.Handle(
fmt.Sprintf("/%s/", rootPath),
otelhttp.NewHandler(
http.HandlerFunc(userCart),
"http_user_cart",
otelhttp.WithTracerProvider(otel.GetTracerProvider()),
otelhttp.WithPropagators(otel.GetTextMapPropagator()),
))
当从一个服务发送 HTTP 请求到另一个服务时,可通过 otelhttp
库的辅助函数来创建和管理分布式追踪的跨度对象:
import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
// ...
resp, err := otelhttp.Get(ctx, fmt.Sprintf("%s/%s", userServiceEndpoint, userName))
行李(Baggage)
从上图中可以看出 service 1
生成了一些数据 attr1
。这些与 service 1
相关的数据可能要添加到 service 2
或 service 3
所在跨度对象的属性中。由于这些服务可能无法直接访问此数据,在 OpenTelemetry 中是通过行李来解决这个问题。行李本质上是携带额外信息的键值对,通过请求传递数据给不同服务和组件。
在 Go 中,我们可以通过以下方式添加行李信息:
reqAddrBaggage, err := baggage.NewMember("req.addr", r.RemoteAddr)
if err != nil {
// Handle error...
}
reqBaggage, err := baggage.New(reqAddrBaggage)
if err != nil {
// Handle error...
}
ctx = baggage.ContextWithBaggage(ctx, reqBaggage)
这样设置后我们的 HTTP 请求将包括 req.addr
行李 。
后续消费端的服务(在图中,这可能是 service 2
或 service 3
),就可以从请求上下文中解析行李:
import "go.opentelemetry.io/otel/baggage"
// ...
reqBaggage := baggage.FromContext(ctx)
span.SetAttributes(attribute.String(
"req.addr",
reqBaggage.Member("req.addr").Value()),
)
此代码解析行李信息,并将其作为当前跨度的属性添加进去。
示例
经过之前对传播和行李的讨论,现在让我们看看 OpenTelemetry 如何发送这些数据。在示例的购物车应用程序中,如果我发出请求并从价格服务或用户服务中查看请求头,将会看到以下两行信息:
Baggage: req.addr=10.244.0.11%3A60086
Traceparent: 00-9861e8c7b097206fed82e0f6b379aae0-4aa019606aed70b6-01
请求头 Traceparent
是链路追踪 ID(本例中是“9861e8c7b097206fed82e0f6b379aae0”)和父跨度 ID (“4aa019606aed70b6”)。还有一个 Baggage
,其中包括在请求发起的源服务(购物车服务),它被添加到 req.addr
行李中。下图可以看到 req.addr
行李在用户服务中被引用:
总结
在 OpenTelemetry 中通过使用传播和行李,很好的解决了“分布式链路追踪”中“分布式”的问题。这样可以帮助您获取更有价值的链路追踪数据!
本文翻译自:Observability with OpenTelemetry Part 5 - Propagation and Baggage | Thomas Stringer
扩展阅读:
- 方法论:面向故障处理的可观测性体系建设
- 白皮书:事件 OnCall 中心建设方法
- 好工具:FlashDuty - 一站式告警处理平台:告警降噪、排班OnCall