4. Trace数据模型:Span结构、TraceID与SpanID、Attributes与Events、Resource与Scope

分布式追踪的数据模型是理解Tempo检索机制的基础。Tempo原生兼容OpenTelemetry(OTel)数据模型,所有检索加速策略——从TraceID路由到Span级过滤——都建立在对Span、TraceID、SpanID、Attributes、Events、Resource、Scope等核心概念的精确理解之上。本章将逐一拆解这些概念,并揭示它们如何影响Tempo的存储与查询路径。

4.1 Span结构:追踪的最小原子单元

Span(跨度)是分布式追踪中描述一次操作的最小工作单元。一个完整的Trace由多个Span通过父子关系或因果关系连接而成。每个Span记录了一个操作在特定服务中的开始时间、结束时间、状态以及上下文信息。

核心要点:Span是Tempo检索的最小粒度。Tempo的TraceQL查询最终是在Span级别进行过滤和聚合,而非Trace级别。

一个标准的Span结构包含以下关键字段:

字段 类型 说明 检索相关性
TraceID 16/32字节 全局唯一标识,关联同一请求的所有Span Tempo按TraceID分片存储,是最高效的检索键
SpanID 8字节 当前Span的唯一标识 用于构建父子关系,TraceQL支持按SpanID精确查找
ParentSpanID 8字节 父Span的ID,根Span此字段为空 用于重建调用链拓扑,影响TraceQL的ancestor/descendant操作
Name 字符串 操作名称,如/api/v1/checkout TraceQL的span.name过滤,常用作检索入口
StartTime / EndTime 纳秒时间戳 Span的开始和结束时间 时间范围过滤是Tempo检索的必选条件
Status 枚举 Unset / Ok / Error TraceQL的status过滤,快速定位错误Span
Attributes 键值对列表 用户自定义或框架自动注入的元数据 TraceQL的核心过滤维度,支持.http.status_code等
Events 事件列表 Span生命周期内的结构化日志 用于异常堆栈、消息队列偏移量等场景
Resource 资源对象 描述产生Span的实体(服务、主机、容器) TraceQL的resource.*过滤,如resource.service.name
Scope 作用域对象 描述产生Span的库/框架信息 TraceQL的scope.*过滤,如scope.name
MTTR加速提示:在Tempo中,span.name和resource.service.name是故障排查时最常用的两个检索字段。建议在TraceQL查询中优先使用这两个字段缩小范围,再结合status=error快速定位问题Span。

4.2 TraceID与SpanID:追踪的坐标系统

TraceID和SpanID构成了分布式追踪的坐标系统。理解它们的生成规则和存储方式,是优化Tempo检索性能的关键。

4.2.1 TraceID:全局唯一标识

  • 长度:OpenTelemetry标准支持16字节(128位)或32字节(256位)两种长度。Tempo默认使用16字节TraceID。
  • 生成规则:通常由入口服务(如API网关)生成,包含时间戳、随机数或机器标识。Tempo不强制TraceID的生成算法,但推荐使用高熵随机数以避免哈希冲突。
  • 存储影响:Tempo将TraceID作为分片键(Shard Key)进行数据分布。TraceID的哈希值决定了Span数据落在哪个Ingester或Storage Bucket中。
  • 检索加速:按TraceID查询是Tempo最高效的检索路径,直接命中分片,无需全表扫描。

4.2.2 SpanID:局部唯一标识

  • 长度:固定8字节(64位)。
  • 生成规则:由产生Span的SDK自动生成,保证在同一个Trace内唯一即可。
  • 父子关系:通过ParentSpanID字段关联。根Span的ParentSpanID为空。
  • 检索场景:TraceQL支持span_id精确查询,适用于已知特定SpanID的深度排查场景。
TraceID与SpanID层级关系 TraceID: 0x4a3f...b1c2 SpanID: 0x001 (Root) SpanID: 0x002 Parent: 0x001 SpanID: 0x003 Parent: 0x001 SpanID: 0x004 Parent: 0x002 同一TraceID下的所有Span共享相同的TraceID,通过SpanID和ParentSpanID构建调用链树形结构
性能陷阱:不要依赖SpanID进行大规模扫描查询。Tempo的索引结构针对TraceID优化,SpanID查询需要遍历所有分片。仅在已知精确SpanID且TraceID未知的极少数场景下使用。

4.3 Attributes与Events:Span的元数据与事件

4.3.1 Attributes:结构化的键值对元数据

Attributes是Span的元数据载体,以键值对(Key-Value Pair)形式存在。Tempo将Attributes作为TraceQL过滤的核心维度,直接影响检索效率。

Attributes的常见分类:

分类 示例 TraceQL访问方式 检索优化建议
HTTP属性 http.method, http.status_code, http.url span.http.status_code 优先过滤http.status_code >= 500快速定位服务端错误
数据库属性 db.system, db.statement, db.operation span.db.system 避免在db.statement上做模糊匹配,改用db.operation精确过滤
消息队列属性 messaging.system, messaging.destination span.messaging.destination 按messaging.destination分组统计延迟
自定义业务属性 order.id, user.id, payment.amount span.order.id 高基数属性(如user.id)会增加索引开销,建议配合时间范围使用
MTTR加速实践:在故障复盘时,优先使用{ span.http.status_code = 500 } + { resource.service.name = "checkout-service" }的组合过滤,可以在毫秒级定位到所有5xx错误的Span,再通过span.http.url缩小到具体接口。

4.3.2 Events:Span生命周期内的结构化日志

Events是Span内部发生的、带时间戳的结构化事件记录。与Attributes不同,Events可以出现多次,且每个Event都包含独立的时间戳和属性集。

Events的典型应用场景:

  • 异常堆栈捕获:当Span状态变为Error时,SDK自动注入exception事件,包含exception.type、exception.message、exception.stacktrace等属性。
  • 消息队列偏移量:记录消息的offset、partition、consumer.group等信息。
  • 缓存命中/未命中:记录缓存操作的cache.hit、cache.key等事件。
  • 自定义业务里程碑:记录业务处理过程中的关键节点,如payment.authorized、inventory.reserved。
// Events在Span中的JSON表示示例
{
  "events": [
    {
      "timeUnixNano": "1712345678000000000",
      "name": "exception",
      "attributes": [
        {"key": "exception.type", "value": {"stringValue": "java.lang.NullPointerException"}},
        {"key": "exception.message", "value": {"stringValue": "Cannot invoke method on null object"}},
        {"key": "exception.stacktrace", "value": {"stringValue": "at com.example.service... (line 42)"}}
      ]
    },
    {
      "timeUnixNano": "1712345679000000000",
      "name": "cache.miss",
      "attributes": [
        {"key": "cache.key", "value": {"stringValue": "user:profile:12345"}},
        {"key": "cache.backend", "value": {"stringValue": "redis"}}
      ]
    }
  ]
}
TraceQL中的Events查询:使用event:exception可以过滤所有包含异常事件的Span。结合event:exception[exception.type = "java.lang.NullPointerException"]可以精确匹配特定异常类型。

4.4 Resource与Scope:Span的归属上下文

Resource和Scope提供了Span的归属上下文信息,是Tempo实现跨服务、跨库检索的关键维度。

4.4.1 Resource:产生Span的实体信息

Resource描述产生Span的物理或逻辑实体,通常在整个服务生命周期内保持不变。Tempo将Resource信息存储在独立的索引中,支持高效的resource.*过滤。

Resource的典型属性:

属性键 说明 示例值 检索价值
service.name 服务名称 checkout-service 最常用的检索入口,按服务隔离故障域
service.namespace 服务命名空间 production 区分环境(prod/staging)
service.instance.id 服务实例ID pod-abc-123 定位特定实例的异常
host.name 主机名 ip-10-0-1-42 按物理节点排查
deployment.environment 部署环境 staging 环境级过滤

4.4.2 Scope:产生Span的库/框架信息

Scope描述产生Span的Instrumentation库(如OpenTelemetry SDK、Spring Boot Starter、gRPC拦截器)的信息。Scope在Tempo中用于区分不同框架产生的Span,支持scope.*过滤。

Scope的典型属性:

  • scope.name:库名称,如io.opentelemetry.spring-webmvc-6.0
  • scope.version:库版本,如1.32.0
Scope的MTTR价值:当升级了某个Instrumentation库后出现Span丢失或数据异常时,使用{ scope.name = "io.opentelemetry.spring-webmvc-6.0" }可以快速筛选出该库产生的所有Span,对比升级前后的行为差异。

4.5 数据模型对Tempo检索性能的影响

理解数据模型后,可以总结出以下影响Tempo检索性能的关键原则:

  1. TraceID是最高效的检索键:按TraceID查询直接命中分片,延迟通常在10ms以内。
  2. Resource属性是第二高效的过滤维度:Tempo为resource.service.name等常用属性建立了独立索引。
  3. Span Attributes的过滤效率取决于基数:低基数属性(如http.status_code)过滤效率高;高基数属性(如user.id)需要配合时间范围使用。
  4. Events查询需要遍历Span:Events不建立独立索引,查询效率低于Attributes。
  5. Scope过滤适用于库级问题排查:在常规故障场景中,优先使用Resource和Attributes过滤。
Tempo数据模型检索效率层级 层级1:TraceID 精确查询(最高效,直接命中分片) 层级2:Resource 属性过滤(独立索引,高效) 层级3:低基数Span Attributes(如 http.status_code,索引效率较高) 层级4:高基数Span Attributes(如 user.id,需配合时间范围) 层级5:Events 查询(无独立索引,效率最低)
检索策略建议:在MTTR复盘场景中,始终从层级1或层级2开始检索。避免直接使用层级5的Events查询作为入口,这会导致Tempo扫描大量数据,显著增加查询延迟。