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结构包含以下关键字段:
| 字段 | 类型 | 说明 | 检索相关性 |
|---|---|---|---|
| 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 |
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的深度排查场景。
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)会增加索引开销,建议配合时间范围使用 |
{ 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"}}
]
}
]
}
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.0scope.version:库版本,如1.32.0
{ scope.name = "io.opentelemetry.spring-webmvc-6.0" }可以快速筛选出该库产生的所有Span,对比升级前后的行为差异。
4.5 数据模型对Tempo检索性能的影响
理解数据模型后,可以总结出以下影响Tempo检索性能的关键原则:
- TraceID是最高效的检索键:按TraceID查询直接命中分片,延迟通常在10ms以内。
- Resource属性是第二高效的过滤维度:Tempo为
resource.service.name等常用属性建立了独立索引。 - Span Attributes的过滤效率取决于基数:低基数属性(如
http.status_code)过滤效率高;高基数属性(如user.id)需要配合时间范围使用。 - Events查询需要遍历Span:Events不建立独立索引,查询效率低于Attributes。
- Scope过滤适用于库级问题排查:在常规故障场景中,优先使用Resource和Attributes过滤。