链路追踪中有哪些关键概念?
Traces 用于描述 App 中请求的整个完整路径。
在分布式系统中,通常会有很多个微服务,以及多个中间件、数据库,一个请求需要多个组件协同工作才能完成。这些组件之间的调用关系就构成了一个 Trace。Trace 通常由多个 Span 组成,每个 Span 代表了一个子处理过程。
当然,即便是单体系统,链路追踪也有用。因为即便是单体系统,通常也会用到数据库、缓存等外部服务,而且系统内部也会有很多不同的方法调用,Trace 可以串联这些操作,帮助我们更好地理解系统的运行情况。
Trace 由多个 Span 组成,下面是一个 Span 的样例:
{
"name": "hello",
"context": {
"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "051581bf3cb55c13"
},
"parent_id": null,
"start_time": "2022-04-29T18:52:58.114201Z",
"end_time": "2022-04-29T18:52:58.114687Z",
"attributes": {
"http.route": "some_route1"
},
"events": [
{
"name": "Guten Tag!",
"timestamp": "2022-04-29T18:52:58.114561Z",
"attributes": {
"event_attributes": 1
}
}
]
}
name
:Span 的名称,描述了 Span 所代表的操作。context
:Span 的上下文,包括 Trace ID 和 Span ID。parent_id
:父 Span 的 ID,用于构建 Span 之间的关系。start_time
和end_time
:Span 的开始和结束时间。attributes
:Span 的属性,用于记录 Span 的一些特征。events
:Span 的事件,用于记录 Span 的一些事件。
上面的 Span,其 parent_id 为 null,表示这是一个 Trace 的根 Span。在一个 Trace 中,必然会有一个根 Span。下面这张图形象的展示了一个 Trace 的多个 Span 之间的关系:
在这个瀑布图中,最上面的那个 client 就表示根 Span,client 调用服务端的 /api
,/api
是服务端生成的一个 Span,这个 Span 是根 Span 的子 Span。服务端又调用了 /authN
,/payment
, /dispatch
等其他接口,每个接口都生成了一个 Span,各个 Span 又有子操作,即子 Span:
/authN
调用了/authZ
,/authZ
是/authN
的子 Span。/payment
调用了 DB,之后又调用了Ext.Merchant
,这两个操作分别是/payment
的子 Span。/dispatch
调用了 5 个子操作,这 5 个子操作左侧是对齐的,表示它们是并行执行的。
下面我们来继续上面的 hello 的例子,看看 hello Span 的子 Span 的数据结构:
{
"name": "hello-greetings",
"context": {
"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "5fb397be34d26b51"
},
"parent_id": "051581bf3cb55c13",
"start_time": "2022-04-29T18:52:58.114304Z",
"end_time": "2022-04-29T22:52:58.114561Z",
"attributes": {
"http.route": "some_route2"
},
"events": [
{
"name": "hey there!",
"timestamp": "2022-04-29T18:52:58.114561Z",
"attributes": {
"event_attributes": 1
}
},
{
"name": "bye now!",
"timestamp": "2022-04-29T18:52:58.114585Z",
"attributes": {
"event_attributes": 1
}
}
]
}
这个 Span 的 parent_id 是 hello Span 的 span_id,表示这是 hello Span 的子 Span。这样,我们就可以分析出这两个 Span 之间的 ChildOf 关系。
再来看一个 Span 的数据:
{
"name": "hello-salutations",
"context": {
"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "93564f51e1abe1c2"
},
"parent_id": "051581bf3cb55c13",
"start_time": "2022-04-29T18:52:58.114492Z",
"end_time": "2022-04-29T18:52:58.114631Z",
"attributes": {
"http.route": "some_route3"
},
"events": [
{
"name": "hey there!",
"timestamp": "2022-04-29T18:52:58.114561Z",
"attributes": {
"event_attributes": 1
}
}
]
}
这个 Span 的 parent_id 也是 hello Span 的 span_id,表示这是 hello Span 的另一个子 Span。Span hello-greetings 和 Span hello-salutations 是兄弟关系,它们都是 hello Span 的子 Span。
这三个 Span 都有相同的 trace_id,表示它们属于同一个 Trace。这样,我们就可以通过 Span 之间的关系,构建出一个完整的 Trace。
您需要注意的另一件事是,每个 Span 看起来都像一个结构化的日志。将 Traces 视为结构化日志的集合,其中包含上下文、关联、层次结构等。但是,这些 “结构化日志” 可能来自不同的进程、服务、VM、数据中心等。这就是允许跟踪表示任何系统的端到端视图的原因。
要了解 OpenTelemetry 中的跟踪是如何工作的,让我们看看一个组件列表,这些组件将在检测代码中发挥作用。
Tracer Provider
Tracer Provider(有时称为 TracerProvider)是Tracer的工厂。在大多数应用程序中,Tracer Provider 初始化一次,其生命周期与应用程序的生命周期匹配。Tracer Provider 初始化还包括 Resource 和 Exporter 初始化。这通常是使用 OpenTelemetry 进行跟踪的第一步。在某些语言 SDK 中,已为您初始化了全局 TracerProvider。
Tracer
Tracer 创建的 span 包含有关给定操作(例如服务中的请求)所发生情况的更多信息。Tracer 是从 TracerProvider 创建的。
Trace Exporters
Trace Exporter 将跟踪发送给使用者。此使用者可以是调试和开发时的标准输出、OpenTelemetry Collector 或您选择的任何开源或供应商后端。
Context Propagation
Context Propagation 是实现 Distributed Tracing 的核心概念。WithContext 传播,Span 可以相互关联并组装成一个跟踪,而不管 Span 是在何处生成的。
Spans
跨度表示工作或操作单元。Span 是 Trace 的构建块。在 OpenTelemetry 中,它们包括以下信息:
- Name:名字
- Parent span ID:父 span ID(根 span 为空)
- Start and End Timestamps:开始和结束时间戳
- Span Context:Span 上下文
- Attributes:属性
- Span Events:Span 事件
- Span Links:Span 链接
- Span Status:Span 状态
Span 样例:
{
"name": "/v1/sys/health",
"context": {
"trace_id": "7bba9f33312b3dbb8b2c2c62bb7abe2d",
"span_id": "086e83747d0e381e"
},
"parent_id": "",
"start_time": "2021-10-22 16:04:01.209458162 +0000 UTC",
"end_time": "2021-10-22 16:04:01.209514132 +0000 UTC",
"status_code": "STATUS_CODE_OK",
"status_message": "",
"attributes": {
"net.transport": "IP.TCP",
"net.peer.ip": "172.17.0.1",
"net.peer.port": "51820",
"net.host.ip": "10.177.2.152",
"net.host.port": "26040",
"http.method": "GET",
"http.target": "/v1/sys/health",
"http.server_name": "mortar-gateway",
"http.route": "/v1/sys/health",
"http.user_agent": "Consul Health Check",
"http.scheme": "http",
"http.host": "10.177.2.152:26040",
"http.flavor": "1.1"
},
"events": [
{
"name": "",
"message": "OK",
"timestamp": "2021-10-22 16:04:01.209512872 +0000 UTC"
}
]
}
Span Context
Span 上下文是每个 span 上的不可变对象,其中包含以下内容:
- 表示 span 所属的跟踪的 Trace ID
- Span ID
- Trace Flags,一种二进制编码,包含有关跟踪的信息
- Trace State,可以携带供应商特定的 traceinformation 的键值对列表
Span 上下文是 Span 的一部分,它与 Distributed Context 和 Baggage 一起序列化和传播。由于 Span 上下文包含跟踪 ID,因此在创建 Span Link 时会使用它。
Attributes
属性是包含元数据的键值对,您可以使用这些元数据对 Span 进行注释,以携带有关它正在跟踪的操作的信息。
例如,如果 span 跟踪将商品添加到电子商务系统中用户购物车的操作,则可以捕获用户的 ID、要添加到购物车的商品的 ID 以及购物车 ID。
您可以在创建 span 期间或之后向 span 添加属性。最好在创建 Span 时添加属性,以使属性可用于 SDK 采样。如果必须在 span 创建后添加值,请使用该值更新 span。
属性具有每种语言 SDK 实现的以下规则:
- 键必须是非 null 字符串值
- 值必须是非 null 字符串、布尔值、浮点值、整数、这些值的数组
此外,还有语义属性,这是常见操作中通常存在的元数据的已知命名约定。尽可能使用语义属性命名会很有帮助,这样就可以跨系统标准化常见类型的元数据。
Span Events
可以将 Span 事件视为 Span 上的结构化日志消息(或注释),通常用于表示 Span 持续时间内有意义的单个时间点。
例如,考虑 Web 浏览器中的两个场景:
- 跟踪页面加载
- 表示页面何时变为交互式(即页面加载完成)
Span 最适合用于第一种情况,因为它是具有 start和 end 的操作。Span Event 最适合用于跟踪第二种情况,因为它表示单一时间点。
何时使用 span 事件与 span 属性
由于 span 事件也包含属性,因此何时使用 events 而不是 attributes 的问题可能并不总是有明显的答案。为了告知您的决定,请考虑特定时间戳是否有意义。
例如,当您使用 span 跟踪操作并且操作完成时,您可能希望将操作中的数据添加到遥测数据中。
- 如果操作完成的时间戳有意义或相关,请将数据附加到 span 事件。
- 如果时间戳没有意义,请将数据附加为 span 属性。
Span Links
Links 是 Span 之间的关系,表示它们之间的相关性。与 Parent-Child 关系不同,Links 可以将两个 Span 连接在一起,即使它们不在同一调用链中。例如,跨服务调用时,可以使用 Links 来关联不同服务生成的 Span。
Span Status
每个 span 都有一个状态。三个可能的值是:
- Unset:未设置状态,通常表示没有遇到错误
- Error:表示 span 有错误
- Ok:明确表示 span 成功
默认值为 Unset。为 Unset 的 span 状态表示它跟踪的操作已成功完成,没有错误。
当 span 状态为 Error 时,这意味着它跟踪的操作中发生了一些错误。例如,这可能是由于处理请求的服务器上的 HTTP 500 错误造成的。
Unset 表示一个 span 完成且没有错误。Ok表示开发人员何时明确将 span 标记为成功。在大多数情况下,没有必要将 span 显式标记为 Ok。
Span Kind
创建 span 时,它是 Client、Server、Internal、Producer 或 Consumer 之一。这种 span 类型为跟踪后端提供了有关如何组装跟踪的提示。根据 OpenTelemetry 规范,server span 的父级通常是 client span,而 client span 的子级通常是 server span。同样,consumer span 的父级始终是生产者,而 producer span 的子级始终是使用者。如果未提供,则假定 span 类型为 internal。
总结
在本文中,我们讨论了链路追踪的关键概念。我们了解了 Trace、Span、Span Context、Attributes、Span Events、Span Links、Span Status 和 Span Kind。这些概念是 OpenTelemetry 中链路追踪的基础,帮助我们更好的理解 Tracing 的逻辑。