2. 核心概念:Trace、Span、Metrics、Logs、Baggage、Context Propagation
在构建多租户可观测性体系之前,必须统一理解 OpenTelemetry 定义的六大核心概念。这些概念构成了数据采集、关联与传递的语义基础。下面逐一拆解其定义、职责与相互关系。
2.1 Trace 与 Span:分布式追踪的骨架
Trace(追踪)代表一个端到端请求在分布式系统中的完整路径。它由多个 Span(跨度)组成,每个 Span 对应系统中的一个操作单元(如一次数据库查询、一次 RPC 调用)。
- Trace ID:全局唯一标识一次完整请求,贯穿所有服务。
- Span ID:标识单个操作单元,父子 Span 通过
ParentSpanId关联。 - Span 属性:键值对,记录操作元数据(如 HTTP 方法、状态码、租户 ID)。
- Span 事件:带时间戳的日志记录,用于标记异常或关键里程碑。
多租户关键点:在 Span 属性中注入
tenant.id 或 tenant.name,即可在 Trace 级别实现租户隔离查询。
Span 生命周期状态:
Unset → Ok / Error。错误状态应携带描述性消息,便于快速定位故障租户。
2.2 Metrics:聚合的量化视图
Metrics(指标)是聚合后的数值数据,用于反映系统在时间维度上的状态变化。OpenTelemetry 支持三种基本指标类型:
| 指标类型 | 说明 | 典型场景 |
|---|---|---|
| Counter | 单调递增计数器 | 请求总数、错误总数 |
| Histogram | 值分布统计 | 请求延迟 P50/P99、响应大小 |
| UpDownCounter | 可增减的计数器 | 活跃连接数、队列深度 |
Metrics 不包含单个请求的细节,但能提供高层次的健康度与趋势信息。在多租户场景中,通常按租户维度打标签(tenant_id)实现租户级聚合。
2.3 Logs:结构化的事件记录
Logs(日志)是带有时间戳的结构化或非结构化文本记录。OpenTelemetry 将日志视为 LogRecord 对象,包含:
- Timestamp:事件发生时间。
- Severity:严重级别(TRACE / DEBUG / INFO / WARN / ERROR / FATAL)。
- Body:日志正文。
- Attributes:结构化键值对,可关联 Trace ID、Span ID、租户 ID。
常见误区:不要将日志视为独立孤岛。通过注入
trace_id 和 span_id,日志可以与 Trace 无缝关联,形成“日志→追踪”的快速跳转链路。
2.4 Baggage:跨服务的上下文数据传递
Baggage(行李)是一种键值对集合,随请求上下文跨服务传播。与 Span 属性不同,Baggage 会沿着 Trace 路径传递到所有下游服务。
- 用途:传递租户 ID、用户角色、请求来源等业务上下文。
- 传播方式:通过 HTTP 头(
baggage)或 gRPC 元数据传递。 - 注意事项:Baggage 数据会暴露给所有下游服务,避免传递敏感信息(如密码、Token)。
多租户最佳实践:将
tenant.id 放入 Baggage,确保所有下游服务自动继承租户上下文,无需手动传递参数。
2.5 Context Propagation:上下文传播机制
Context Propagation(上下文传播)是 OpenTelemetry 的核心机制,负责将 Trace、Span、Baggage 等信息在服务间传递。它通过以下方式实现:
- 创建 Context:在请求入口处创建包含 Trace ID、Span ID、Baggage 的上下文对象。
- 注入(Inject):将上下文序列化到传输协议(HTTP 头、gRPC 元数据、消息队列属性)。
- 提取(Extract):下游服务从协议中反序列化上下文,恢复 Trace 链路。
- 传递(Propagate):在异步调用、线程池、消息队列中保持上下文不丢失。
OpenTelemetry 默认支持 W3C Trace Context 标准(traceparent 和 tracestate 头),同时兼容 Baggage 头。
2.6 核心概念对比总结
| 概念 | 数据形态 | 粒度 | 跨服务传播 | 多租户用途 |
|---|---|---|---|---|
| Trace | 树状结构 | 请求级 | 是(通过 Context) | 租户请求全链路追踪 |
| Span | 操作单元 | 操作级 | 否(父子关联) | 租户操作耗时与状态 |
| Metrics | 聚合数值 | 时间窗口级 | 否 | 租户级 QPS、延迟、错误率 |
| Logs | 结构化文本 | 事件级 | 否(可关联 Trace) | 租户错误日志快速定位 |
| Baggage | 键值对 | 请求级 | 是(显式传播) | 传递租户 ID、角色 |
| Context Propagation | 传播机制 | 基础设施级 | 是(核心能力) | 确保租户上下文不丢失 |
2.7 上下文传播流程示例
以下是一个典型的多租户请求上下文传播流程:
- API 网关:从请求头提取
X-Tenant-ID,创建 Trace 并注入 Baggage(tenant.id=abc123)。 - 服务 A:提取上下文,创建子 Span,记录
tenant.id属性,调用服务 B。 - 服务 B:自动继承 Baggage,创建子 Span,记录 Metrics(按租户标签聚合),输出日志(携带 Trace ID)。
- 异步处理:通过消息队列传递上下文,确保消费者 Span 正确关联父 Trace。
代码示例:在 Go 中注入租户上下文
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/propagation"
)
// 在入口处注入租户 ID
func injectTenantContext(ctx context.Context, tenantID string) context.Context {
member, _ := baggage.NewMember("tenant.id", tenantID)
bag, _ := baggage.New(member)
return baggage.ContextWithBaggage(ctx, bag)
}
// 在下游提取租户 ID
func extractTenantID(ctx context.Context) string {
bag := baggage.FromContext(ctx)
return bag.Member("tenant.id").Value()
}
2.8 多租户上下文传播避坑指南
- 不要遗漏异步场景:线程池、消息队列、定时任务必须显式传递 Context,否则 Trace 断裂。
- 不要过度使用 Baggage:只传递必要的业务上下文(如租户 ID),避免传递大量数据导致性能下降。
- 不要忽略 W3C 标准:使用
traceparent和tracestate头,确保与第三方系统兼容。 - 不要混淆 Span 属性与 Baggage:Span 属性仅本地可见,Baggage 跨服务传播,按需选择。
推荐实践:在服务入口处统一提取 Baggage 中的租户 ID,并注入到当前 Span 的 Attributes 中,同时传递给 Metrics 和 Logs 的标签系统,实现“一次注入,全局可用”。