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 描述。下图展示了这些核心概念之间的逻辑关系。

OTel 核心数据模型逻辑关系图 Resource InstrumentationScope Trace (TracdId) Span (SpanId, ParentSpanId) Attributes Events Links 描述运行环境 标识创建者 包含 携带 记录 关联 关键关系说明 1. 一个 Trace 包含一个或多个 Span,通过 TraceId 关联。 2. 每个 Span 必须关联一个 InstrumentationScope,标识其来源(如库、框架、自定义代码)。 3. Resource 描述生成 Span 的实体(如主机、容器、进程),可被多个 Span 共享。 4. Attributes、Events、Links 是 Span 的附属数据,用于丰富 Span 的语义。

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 关联。
实践建议: 在自定义埋点时,正确设置 Span Kind 至关重要。例如,使用 HTTP 客户端库发起请求时,应创建 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。
关键点: Trace 的完整性依赖于上下文传播(Context Propagation)。在跨进程调用(如 HTTP 请求、RPC 调用、消息队列)时,必须将 TraceId 和 SpanId 通过协议头(如 W3C Trace-Context)传递给下游服务,下游服务才能创建正确的子 Span 并关联到同一个 Trace。

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 名称。
注意: Resource 是全局的,通常在 SDK 初始化时设置一次。不要在每次创建 Span 时重复设置相同的 Resource 信息,这会增加数据冗余和传输开销。正确的做法是在 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,用于定义该作用域使用的语义约定。
实践建议: 在自定义扩展中,务必为你的 InstrumentationScope 设置一个有意义的名称和版本。这有助于在数据后端(如 Jaeger、Zipkin)中区分不同来源的 Span,方便进行问题定位和性能分析。例如,为你的自定义埋点库设置 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:描述事件的键值对。
最佳实践: 使用 Events 记录异常信息是标准做法。当捕获到异常时,创建一个名为 "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。
Links 典型场景:消息队列 Producer Span (Trace A) Message Queue Consumer Span (Trace B) 发送消息 消费消息 Link (关联不同 Trace) 为什么使用 Links 而不是父子关系? 1. Producer 和 Consumer 属于不同的 Trace (Trace A vs Trace B),无法建立父子关系。 2. 消息的发送和消费在时间上可能不连续,父子关系要求子 Span 在父 Span 的时间范围内。 3. Links 提供了一种松耦合的关联方式,保留了完整的因果信息,同时不破坏 Trace 的独立性。

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
学习路径建议: 在深入 SDK 埋点之前,请务必熟练掌握 Span 和 Attributes 的使用。它们是日常埋点中最频繁操作的对象。理解 Resource 和 InstrumentationScope 有助于你更好地组织和管理遥测数据。Events 和 Links 则是在处理复杂异步场景时的利器。