3. OTel 架构与数据模型:Span、Trace、Resource、InstrumentationScope、Attributes、Events、Links
OpenTelemetry 的数据模型是构建可观测性体系的基石。理解其核心概念及其相互关系,是进行 SDK 埋点和自定义扩展的前提。本章将深入剖析 OTel 架构中的六大核心数据模型:Span、Trace、Resource、InstrumentationScope、Attributes、Events 和 Links。
3.1 核心数据模型总览
OTel 的数据模型遵循一种层次化、关联化的设计。一个 Trace 由多个 Span 组成,每个 Span 携带 Attributes、Events 和 Links。而 Span 的生成者由 InstrumentationScope 标识,运行环境则由 Resource 描述。下图展示了这些核心概念之间的逻辑关系。
3.2 Span:可观测性的基本工作单元
Span 是 OTel 中最核心的数据模型,代表一个分布式系统中的单个操作或工作单元。它记录了操作的名称、开始和结束时间、状态以及上下文信息。
3.2.1 Span 的核心字段
| 字段 | 类型 | 描述 | 是否必须 |
|---|---|---|---|
| TraceId | 16 字节数组 | 标识 Span 所属的 Trace。一个 Trace 内的所有 Span 共享同一个 TraceId。 | 是 |
| SpanId | 8 字节数组 | Span 的唯一标识符。 | 是 |
| ParentSpanId | 8 字节数组 | 父 Span 的 SpanId。根 Span(Trace 中的第一个 Span)此字段为空。 | 否 |
| Name | 字符串 | Span 的人类可读名称,例如 "HTTP GET /api/users"。 | 是 |
| Kind | 枚举 | Span 的角色:CLIENT、SERVER、INTERNAL、PRODUCER、CONSUMER。 |
是 |
| StartTimestamp | 时间戳 | 操作开始的精确时间。 | 是 |
| EndTimestamp | 时间戳 | 操作结束的精确时间。未结束时为空。 | 否 |
| Status | 对象 | 操作结果状态:Unset、Ok、Error。 |
否 |
| Attributes | 键值对列表 | 描述 Span 的元数据。 | 否 |
| Events | 事件列表 | Span 生命周期内发生的带时间戳的注解。 | 否 |
| Links | 链接列表 | 关联到其他 Span 的引用。 | 否 |
3.2.2 Span Kind 详解
Span Kind 定义了 Span 在分布式追踪中的角色,帮助后端系统更好地理解调用拓扑。
- INTERNAL:默认值。表示应用内部的操作,不跨越进程边界。例如:函数调用、内部计算。
- SERVER:表示服务端处理请求的 Span。通常作为入口 Span,其父 Span 来自客户端。
- CLIENT:表示客户端发起请求的 Span。通常作为出口 Span,其子 Span 是服务端的 SERVER Span。
- PRODUCER:表示生产者向消息队列发送消息的 Span。通常与 CONSUMER Span 通过 Links 关联。
- CONSUMER:表示消费者从消息队列接收消息的 Span。通常与 PRODUCER Span 通过 Links 关联。
CLIENT Span;而在处理 HTTP 请求的 Web 框架中,应创建 SERVER Span。错误的 Kind 会导致调用链拓扑混乱。
3.3 Trace:分布式请求的完整路径
Trace 是一个逻辑概念,它不是一个独立的数据结构,而是通过 TraceId 将一组 Span 关联起来形成的树状结构。一个 Trace 记录了请求从入口到出口经过的所有服务、组件和操作的完整路径。
- 根 Span (Root Span):Trace 中的第一个 Span,没有父 Span。它代表整个请求的入口点。
- 子 Span (Child Span):由父 Span 创建的 Span,代表请求路径中的一个子操作。
- TraceId:全局唯一的 16 字节标识符,用于在分布式系统中串联所有相关的 Span。
3.4 Resource:描述生成遥测数据的实体
Resource 是一个不可变的对象,用于描述生成遥测数据的实体,例如:运行应用的主机、容器、Kubernetes Pod、进程或服务实例。Resource 信息会附加到其产生的所有 Span、Metrics 和 Logs 上。
Resource 由一组 Attributes 组成,遵循语义约定(Semantic Conventions)。
| 属性键 (Key) | 示例值 (Value) | 描述 |
|---|---|---|
service.name |
"payment-service" |
服务名称,用于标识应用。 |
service.version |
"1.2.3" |
服务版本号。 |
host.name |
"ip-10-0-1-23" |
主机名。 |
container.id |
"a1b2c3d4e5f6" |
容器 ID。 |
k8s.pod.uid |
"pod-uid-12345" |
Kubernetes Pod UID。 |
telemetry.sdk.name |
"opentelemetry" |
使用的 SDK 名称。 |
TracerProvider 初始化时配置统一的 Resource。
3.5 InstrumentationScope:标识 Span 的创建者
InstrumentationScope 用于标识创建 Span 的库、框架或自定义代码。它解决了“这个 Span 是从哪里来的?”这个问题。每个 Span 都必须关联一个 InstrumentationScope。
InstrumentationScope 包含以下关键信息:
- Name:作用域的名称,例如
"io.opentelemetry.spring-webmvc"或"com.mycompany.custom-lib"。 - Version:作用域的版本号,例如
"1.0.0"。 - SchemaUrl:可选的 Schema URL,用于定义该作用域使用的语义约定。
name="my-company.instrumentation" 和 version="1.0.0"。
3.6 Attributes:Span 的元数据
Attributes 是键值对(Key-Value Pairs),用于为 Span 添加描述性元数据。它们是可选的,但强烈建议使用,因为 Attributes 是进行高效过滤、分组和聚合分析的基础。
3.6.1 属性规则
- Key:必须是字符串,推荐使用点分隔的命名空间,例如
http.method、db.system。 - Value:必须是原始类型或其数组:
string、boolean、int、double、string[]、boolean[]、int[]、double[]。 - 不可变:一旦 Span 创建完成并导出,其 Attributes 不应再被修改。
- 语义约定:OTel 定义了丰富的语义约定(Semantic Conventions),推荐优先使用,以保证跨服务、跨工具的一致性。
3.6.2 常用语义约定示例
| 领域 | 属性键 | 示例值 |
|---|---|---|
| HTTP | http.method |
"GET" |
http.url |
"https://api.example.com/users" |
|
http.status_code |
200 |
|
| 数据库 | db.system |
"mysql" |
db.statement |
"SELECT * FROM users WHERE id = ?" |
|
| 消息队列 | messaging.system |
"kafka" |
messaging.destination |
"order-topic" |
3.7 Events:Span 内的带时间戳注解
Events 是附加在 Span 上的带时间戳的结构化日志记录。它们用于记录 Span 生命周期内发生的特定、有意义的事件,例如:异常抛出、缓存命中、重要业务逻辑执行点。
每个 Event 包含:
- Name:事件名称,例如
"exception"、"cache.hit"。 - Timestamp:事件发生的时间。
- Attributes:描述事件的键值对。
"exception" 的 Event,并设置 exception.type、exception.message 和 exception.stacktrace 等属性。这比将异常信息塞入 Span 的 Attributes 更加语义化,也便于后端系统进行异常聚合和分析。
3.8 Links:关联不同 Trace 的 Span
Links 允许一个 Span 关联到同一个 Trace 内或不同 Trace 内的一个或多个 Span。这种机制主要用于处理异步、批处理和消息队列场景,其中标准的父子关系无法准确描述调用链。
每个 Link 包含:
- SpanContext:被关联 Span 的上下文(TraceId, SpanId, TraceFlags, Tracestate)。
- Attributes:描述关联关系的键值对。
3.8.1 Links 的典型使用场景
- 消息队列:生产者(PRODUCER Span)发送消息到队列,消费者(CONSUMER Span)从队列接收消息。消费者 Span 通过 Link 关联到生产者 Span,而不是父子关系,因为它们在时间上可能不重叠,且属于不同的 Trace。
- 批处理:一个批处理任务处理多个独立的请求。批处理 Span 可以通过 Links 关联到每个请求的入口 Span。
- 异步操作:一个操作触发多个并行的异步任务。主 Span 可以通过 Links 关联到每个异步任务的 Span。
3.9 数据模型关系总结
下表总结了六大核心数据模型的关键特征和相互关系。
| 数据模型 | 核心作用 | 是否独立存在 | 与其他模型的关系 |
|---|---|---|---|
| Trace | 关联一组 Span,形成请求的完整路径 | 逻辑概念,通过 TraceId 隐式存在 | 包含多个 Span |
| Span | 记录单个操作 | 是,核心数据单元 | 属于一个 Trace;携带 Attributes、Events、Links;关联 Resource 和 InstrumentationScope |
| Resource | 描述遥测数据的来源实体 | 是,全局对象 | 附加到所有 Span、Metrics、Logs |
| InstrumentationScope | 标识 Span 的创建者 | 是,每个 Span 关联一个 | 关联到创建它的 Span |
| Attributes | 提供元数据,用于过滤和分析 | 否,依附于 Span/Resource/Event/Link | 作为键值对附加到 Span、Resource、Event、Link |
| Events | 记录 Span 内的带时间戳事件 | 否,依附于 Span | 作为事件列表附加到 Span |
| Links | 关联不同 Trace 或同一 Trace 内的 Span | 否,依附于 Span | 作为链接列表附加到 Span,引用其他 Span 的 SpanContext |