1. 分布式追踪基础

1.1 分布式追踪核心概念

在微服务架构中,一个用户请求往往需要跨越多个服务节点才能完成。分布式追踪的核心目标就是还原这个请求在分布式系统中的完整调用路径。理解以下三个核心概念是掌握分布式追踪的基石:

Trace(追踪)

Trace 代表一个请求在分布式系统中的完整生命周期。它由一系列具有因果关系的 Span 组成,通过一个全局唯一的 TraceID 进行标识。Trace 描述了请求从入口到出口经过的所有服务、组件和操作。

Span(跨度)

Span 是 Trace 中的最小工作单元,代表一个具体操作或服务调用的执行片段。每个 Span 包含以下关键属性:

  • SpanID:当前 Span 的唯一标识
  • ParentSpanID:父 Span 的标识,用于构建调用层级关系
  • OperationName:操作名称,如 HTTP GET /api/orders
  • StartTime / EndTime:操作的开始和结束时间戳
  • Tags:键值对形式的元数据,如 http.status_code=200
  • Logs:时间戳关联的事件记录,如异常堆栈

Context Propagation(上下文传播)

上下文传播是分布式追踪的核心机制,确保 TraceID 和 SpanID 能够在服务调用链中正确传递。传播方式分为两种:

传播方式 适用协议 传递载体 典型场景
显式传播 HTTP / gRPC 请求头(如 traceparent) REST API、微服务间调用
隐式传播 消息队列 消息元数据 异步事件、Kafka 消息
实践要点:在 Golang 中,上下文传播通常通过 context.Context 携带 Span 信息;在 Java 中则通过 MDC 或 ThreadLocal 实现。确保所有 RPC 框架和消息中间件都正确集成传播逻辑,否则会出现 Trace 断裂。

1.2 为什么需要分布式追踪

传统单体应用可以通过日志和单机 profiling 工具定位性能瓶颈。但在分布式系统中,以下问题变得极为棘手:

  • 调用链不可见:一个请求经过 10 个服务,无法快速判断哪个服务最慢
  • 错误根因难定位:500 错误可能由下游某个服务超时引起,但日志分散在各节点
  • 性能瓶颈模糊:数据库查询慢、网络延迟高、CPU 瓶颈,难以量化每个环节的耗时
  • 依赖关系复杂:服务间调用关系网状交织,人工梳理几乎不可能

分布式追踪通过以下能力解决上述问题:

能力 解决的问题 典型输出
调用拓扑还原 服务依赖关系可视化 服务调用拓扑图
耗时分布分析 定位慢调用和瓶颈服务 Span 耗时瀑布图
错误链路追踪 快速定位错误根因 错误 Span 详情及日志关联
SLA 监控 量化服务响应时间与可用性 延迟百分位(p50/p95/p99)
关键指标:在性能瓶颈定位场景中,重点关注 Span 耗时 和 服务间网络延迟。通常 p99 延迟超过 500ms 的 Span 需要优先排查。

1.3 Tempo 在可观测性中的定位

Grafana Tempo 是一款高性价比的分布式追踪后端,其核心定位是:以对象存储为底座,提供低成本、高可用的追踪数据存储与查询能力。在可观测性三大支柱(指标、日志、追踪)中,Tempo 专注于追踪领域,并与 Grafana 生态深度集成。

以下架构图展示了 Tempo 在可观测性体系中的位置:

Tempo 在可观测性体系中的定位 数据源层 应用程序 / 微服务 / 基础设施(通过 OpenTelemetry SDK 或 Jaeger 客户端上报 Trace) 采集与协议层 OTLP / Jaeger / Zipkin / OpenCensus 协议接收 Grafana Tempo 核心 分布式追踪存储 & 查询引擎(对象存储 + 微服务架构) 可视化与消费层 Grafana Explore / 服务拓扑图 / 指标关联(与 Prometheus、Loki 联动)

Tempo 与其他可观测性组件的协作关系如下:

组件 职责 与 Tempo 的集成方式
Grafana 统一可视化面板 通过 Tempo 数据源直接查询 Trace,支持 Trace 与日志、指标联动
Prometheus 指标采集与告警 通过 Exemplar 将指标与 Trace 关联,实现从高延迟指标下钻到具体 Trace
Loki 日志聚合 通过 TraceID 在日志中注入 Span 上下文,实现从日志跳转到 Trace
OpenTelemetry 数据采集标准 Tempo 原生支持 OTLP 协议,是推荐的 Trace 上报方式
架构选型提示:Tempo 采用 对象存储(如 S3、GCS、Azure Blob) 作为后端存储,而非传统数据库。这种设计使得存储成本极低,但查询延迟相对较高。在性能瓶颈定位场景中,建议配合 TraceQL 查询语言进行精确过滤,避免全量扫描。

1.4 Trace 与 Span 的关系模型

为了更直观地理解 Trace 和 Span 的层级关系,下图展示了一个典型的分布式请求调用链:

Trace 与 Span 层级关系示例 Root Span: HTTP GET /api/checkout (TraceID: abc123) Child Span 1: 调用 用户服务 (UserService.GetUser) Child Span 2: 调用 订单服务 (OrderService.CreateOrder) Child Span 3: 数据库查询 (MySQL: INSERT INTO orders) Child Span 4: 消息队列发送 (Kafka: order.created) 耗时 120ms 耗时 30ms 耗时 50ms 耗时 20ms 耗时 15ms

从上图可以看出:

  • 一个 Trace 包含多个 Span,Span 之间通过 ParentSpanID 形成树形结构
  • 根 Span 的耗时等于所有子 Span 耗时之和加上自身处理时间(如序列化、网络开销)
  • 性能瓶颈通常出现在耗时占比最高的 Span 上,如上例中 Child Span 2(50ms) 和 Root Span 自身处理(120 - 30 - 50 - 20 - 15 = 5ms) 之外的等待时间
性能瓶颈定位思路:在 Tempo 的 Span 瀑布图中,优先关注 耗时最长 的 Span 以及 Span 之间的间隙(网络延迟或队列等待)。如果某个 Span 的耗时远大于其子 Span 耗时之和,说明该服务内部存在阻塞操作(如锁竞争、CPU 密集型计算)。