2. OpenTelemetry 核心概念:Signal、Span、Trace、Context Propagation、Exporter

在深入 Go 项目慢查询与链路追踪实践之前,必须先建立对 OpenTelemetry(简称 OTel)核心概念的准确理解。这些概念构成了可观测性的数据模型与传输基础,是后续所有代码实现与问题排查的基石。

2.1 核心概念总览

OpenTelemetry 将可观测性数据抽象为三大类 Signal(信号),每一类信号解决一个维度的观测问题。下表列出了三类信号及其在慢查询场景中的典型用途:

Signal(信号) 作用域 慢查询场景用途
Traces(链路) 请求端到端路径 追踪一条 SQL 从入口到数据库的完整调用链,定位慢在哪一步
Metrics(指标) 聚合统计 采集数据库连接池大小、P99 延迟、QPS,发现慢查询趋势
Logs(日志) 离散事件 记录慢 SQL 原文、执行计划、错误堆栈,辅助根因分析
课程聚焦: 本课程以 Traces 为主线,结合 Logs 与 Metrics 做关联分析。本章重点拆解 Traces 信号内部的核心构造。

2.2 Span:链路追踪的基本单元

Span 是链路追踪中最小的可观测工作单元。每一个 Span 代表一个操作片段,例如一次数据库查询、一次 RPC 调用、一个函数执行。Span 携带了描述该操作的全部元数据。

一个 Span 必须包含以下关键字段:

  • Span Name:操作名称,例如 SELECT * FROM orders 或 HTTP GET /api/users。
  • Span ID:全局唯一标识符,用于在 Trace 内定位该 Span。
  • Trace ID:所属 Trace 的全局唯一标识符。
  • Parent Span ID:父 Span 的 ID,用于构建父子层级关系。根 Span 无父 ID。
  • Start Timestamp / End Timestamp:操作开始与结束时间,用于计算延迟。
  • Attributes(属性):键值对形式的元数据,例如 db.system="postgresql"、db.statement="SELECT ..."。
  • Status:操作结果状态,包括 Unset、Ok、Error。
  • Events(事件):Span 生命周期内的带时间戳的注解,例如记录异常堆栈。
慢查询关键属性: 在 Go 项目中,我们通常会在 Span 的 Attributes 中记录 db.statement(SQL 原文)、db.duration(实际执行耗时)、db.rows_affected(影响行数),以便后续快速筛选慢 Span。

2.3 Trace:Span 的有向无环图

Trace 是一个请求从入口到出口的完整调用链,由一组具有父子关系的 Span 组成。这些 Span 通过 Trace ID 关联,并通过 Parent Span ID 形成树状结构(有向无环图)。

下图展示了一个典型的慢查询 Trace 结构:

Trace 结构示例:慢查询请求链路 HTTP /api/orders ServiceA.Validate ServiceA.BizLogic ServiceA.Cache DB: SELECT orders 红色 Span 表示慢查询操作,其延迟占整个 Trace 的 85% 以上 通过 Trace 可以快速定位到瓶颈在数据库层,而非业务逻辑或缓存

2.4 Context Propagation:跨进程传递追踪上下文

Context Propagation(上下文传播) 是链路追踪能够跨越服务边界的关键机制。它确保一个 Trace 内的所有 Span 能够通过相同的 Trace ID 关联起来,即使这些 Span 分布在不同的进程、不同的机器甚至不同的数据中心。

传播过程分为两个阶段:

  1. 注入(Inject):在发送端(例如 HTTP 客户端、gRPC 客户端),将当前 Span 的上下文(Trace ID、Span ID、Baggage 等)序列化到请求的传播载体中(例如 HTTP Header、gRPC Metadata)。
  2. 提取(Extract):在接收端(例如 HTTP 服务端、gRPC 服务端),从传播载体中反序列化出上下文,并创建新的子 Span,将其父 Span ID 设置为提取到的 Span ID。

OpenTelemetry 定义了标准的传播格式,最常用的是 W3C Trace Context,通过 traceparent 和 tracestate 两个 HTTP Header 传递上下文。

常见陷阱: 在 Go 项目中,如果使用了异步 goroutine 或消息队列,必须手动传递上下文,否则会导致 Trace 断裂。例如,使用 context.WithValue 或 otel.GetTextMapPropagator().Inject 显式传播。

2.5 Exporter:将数据送出进程

Exporter(导出器) 负责将采集到的 Span、Metrics、Logs 数据发送到后端存储或分析系统。OpenTelemetry 采用插件化架构,支持多种 Exporter,开发者可以根据基础设施选择。

下表列出了 Go 项目中常用的 Exporter 及其适用场景:

Exporter 名称 协议/方式 适用场景
OTLP Exporter gRPC / HTTP 发送到 OpenTelemetry Collector,再由 Collector 转发到后端(推荐生产方案)
Jaeger Exporter Thrift / gRPC 直接发送到 Jaeger 后端,适合中小规模或快速原型
Zipkin Exporter HTTP (JSON) 兼容 Zipkin 生态,适合已有 Zipkin 基础设施的团队
Prometheus Exporter HTTP (Metrics) 仅用于 Metrics 信号,暴露 /metrics 端点供 Prometheus 抓取
Stdout Exporter 标准输出 开发调试阶段,直接打印到控制台查看原始数据

Exporter 的配置通常在应用初始化时完成。以下是一个 Go 中使用 OTLP Exporter 的典型代码片段:

import (
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
)

func initExporter() (*otlptrace.Exporter, error) {
    // 创建 OTLP gRPC 客户端,默认连接 localhost:4317
    client := otlptracegrpc.NewClient(
        otlptracegrpc.WithInsecure(),
        otlptracegrpc.WithEndpoint("otel-collector:4317"),
    )
    exporter, err := otlptrace.New(ctx, client)
    if err != nil {
        return nil, err
    }
    return exporter, nil
}

2.6 概念关系总结

下图展示了 Signal、Span、Trace、Context Propagation 与 Exporter 之间的整体关系:

OpenTelemetry 核心概念关系图 应用进程 Span A (HTTP) Span B (DB) Span C (Cache) Trace Trace ID: abc123 Context Propagation traceparent Header Exporter OTLP / Jaeger / Zipkin 后端存储 / 分析系统 Jaeger / Tempo / Datadog Prometheus / Grafana Elasticsearch / Loki
学习路径建议: 先掌握 Span 的创建与属性设置,再理解 Trace 的组装方式,然后学习 Context Propagation 的注入与提取,最后配置 Exporter 将数据可视化。本章后续的代码实战将严格遵循这个顺序。