可观测核心概念
先通过简单的三个Span进行了解
hello 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
}
}
]
}
这是根 Span,表示整个操作的开始和结束。请注意,它有一个 trace_id 字段指示链路, 但没有 parent_id。因此这是一个根 Span。
hello-greetings 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 封装了特定任务,例如 hello-greetings,其父级 Span 是 hello Span。请注意,它的 trace_id 与根 Span 相同,这表明它们属于同一链路。此外,它的 parent_id 与 hello Span 的 span_id一致。
hello-salutations 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 表示此链路中的第三个操作,与上一个操作一样,它是 hello Span 的子级, 因此它与 hello-greetings Span 同级。
这三个 JSON 块的 trace_id 都相同,并且 parent_id 属性表示层次结构,它们构成了整个链路!
简单流程分析
一个典型的上下文透传和追踪流程如下:
-
请求到达:用户发起一个请求(如 GET /api/users/123)。
-
创建根 Span:入口服务(如 API 网关)接收到请求,生成一个唯一的 Trace ID,并创建一个 根 Span(Root Span),记录请求的开始。
-
注入上下文:API 网关将 Trace ID 和 根 Span ID 注入到后续调用的 HTTP 请求头中。 调用下游服务:API 网关调用用户服务。
-
提取上下文:用户服务接收到请求,从 HTTP 头中提取 Trace ID 和 父 Span ID。
-
创建子 Span:用户服务创建一个新的 Span,设置其 Trace ID 与提取的相同,Parent
-
Span ID 设置为提取的父 ID,并开始记录自己的操作。
-
继续透传:如果用户服务还需要调用数据库或其他服务,它会重复步骤 3-6,将当前的上下文(新的 Span ID 作为父 ID)继续传递下去。
-
形成 Trace:所有这些 Span 通过 Trace ID 被关联在一起,通过 Parent Span ID 形成树状调用链,最终构成一个完整的 Trace。
-
数据上报:各个服务将生成的 Span 数据上报给集中式的追踪后端(如 Jaeger, Zipkin, SkyWalking)。
-
可视化:追踪后端将属于同一个 Trace ID 的所有 Span 聚合起来,生成可视化的调用链图,
-
供开发者分析性能瓶颈和排查问题。
Segment
作为数据上报的基本单元(Unit of Reporting)
问题:如果每次方法调用都立即上报一个 Span,会产生海量的小数据包,给网络和后端存储带来巨大压力。
解决方案:将一个请求在单个服务实例上的所有 Span 聚合成一个 Segment,然后以 Segment 为单位进行序列化和上报。
好处: 减少网络开销:一次上报一个 Segment,而不是成百上千个 Span。 提高吞吐量:后端系统处理大块数据比处理大量小数据更高效。 保证数据完整性:一个 Segment 包含了该请求在本服务内的完整执行路径,避免了 Span 丢失导致的链路断裂
Span
一个 Span 代表一个作业或者操作的单元,是链路的组成部分。在 OpenTelemetry 中,它们包括以下信息:
-
名字
-
父 Span ID(根 Span 为空)
-
开始和结束时间
-
Span 上下文
-
属性
-
Span 事件
-
Span 链接
-
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"
}
]
}