2. Tempo 架构:核心组件与数据流
在生产环境中运维分布式追踪系统,首先需要理解 Tempo 的内部构造。Tempo 采用微服务架构,各组件职责清晰、可独立扩缩容。本章从组件职责与数据流两个维度拆解其工作原理。
2.1 核心组件一览
Tempo 由四个主要组件构成:Distributor、Ingester、Querier 和 Compactor。它们协同完成从数据接收、持久化到查询响应的全流程。
| 组件 | 职责 | 关键特性 |
|---|---|---|
| Distributor | 接收 Trace 数据,进行校验、采样与负载分发 | 无状态、可水平扩展;支持 Hash Ring 一致性路由 |
| Ingester | 将 Trace 数据写入后端存储(如 S3/GCS)并构建 Bloom Filter 索引 | 有状态、按 Tenant + 时间分块(Block);负责数据持久化 |
| Querier | 接收查询请求,从 Ingester 或后端存储中检索 Trace | 无状态、支持 TraceQL 查询语言;利用 Bloom Filter 加速 |
| Compactor | 合并小 Block 为大 Block,清理过期数据,优化存储布局 | 后台异步运行;降低存储碎片,提升查询效率 |
运维要点: Distributor 和 Querier 是无状态组件,适合使用 Kubernetes Deployment 部署并配置 HPA 自动扩缩容;Ingester 是有状态组件,建议使用 StatefulSet 并绑定稳定的 PVC 或本地 SSD。
2.2 数据流与工作流程
一条 Trace 从应用程序发出到最终被查询,经历以下完整生命周期:
- 数据注入:应用通过 OpenTelemetry SDK 或 Jaeger Agent 将 Span 数据发送至 Distributor 的 gRPC/HTTP 端点。
- 负载分发:Distributor 根据 TraceID 进行 Hash 计算,通过一致性 Hash Ring 将数据路由到对应的 Ingester 实例。
- 内存缓冲与索引构建:Ingester 在内存中缓冲接收到的 Span,按 Tenant 和时间窗口组织成 Block。同时为每个 Block 构建 Bloom Filter 索引,记录 Block 内包含的 TraceID 集合。
- 数据持久化:当 Block 达到预设大小或时间阈值时,Ingester 将 Block 刷写到后端对象存储(如 S3、GCS、Azure Blob),并更新元数据。
- 后台合并:Compactor 定期扫描存储中的小 Block,将其合并为更大的 Block,减少存储碎片并优化 Bloom Filter 的查询效率。
- 查询响应:Querier 收到查询请求后,先向 Ingester 查询内存中的最新数据,再根据 Bloom Filter 快速判断哪些 Block 可能包含目标 TraceID,最后从对象存储中读取匹配的 Block 并返回结果。
关键路径延迟: 数据从 Distributor 到 Ingester 的传输延迟通常 < 10ms;Ingester 刷写 Block 到对象存储的延迟取决于 Block 大小和存储性能,一般在 1-5 秒之间。查询路径中,Bloom Filter 的假阳性率直接影响需要读取的 Block 数量,是查询性能的核心指标。
2.3 组件间通信与关键协议
组件之间的通信协议和端口是运维排障的基础信息:
| 通信路径 | 协议 | 默认端口 | 说明 |
|---|---|---|---|
| SDK → Distributor | gRPC / HTTP (Protobuf) | 4317 / 4318 | OTLP 协议,支持 Jaeger Thrift 兼容 |
| Distributor → Ingester | gRPC | 9095 | 内部 Push 接口,基于 Hash Ring 路由 |
| Ingester → 对象存储 | HTTPS (S3/GCS API) | 443 | Block 数据持久化,支持多区域 |
| Querier → Ingester | gRPC | 9095 | 查询内存中的最新数据 |
| Querier → 对象存储 | HTTPS | 443 | 读取持久化 Block |
| Compactor → 对象存储 | HTTPS | 443 | 读写 Block 元数据与数据 |
常见问题: 如果 Distributor 与 Ingester 之间的 gRPC 连接超时,通常是因为 Ingester 负载过高或网络延迟增大。建议在 Distributor 侧配置 gRPC 健康检查,并设置合理的超时时间(默认 5s)。另外,对象存储的访问延迟会直接影响 Block 刷写和查询性能,建议将存储桶部署在与 Tempo 集群相同的区域。
2.4 数据模型:Block 与 Bloom Filter
Tempo 的核心数据单元是 Block。每个 Block 包含以下结构:
- 元数据文件 (meta.json):记录 Block 的 Tenant ID、时间范围、起始/结束 TraceID、Span 数量、Bloom Filter 参数等。
- 数据文件 (data.parquet):以 Parquet 列式格式存储所有 Span,支持高效的列裁剪和压缩。
- Bloom Filter 文件 (bloom):为 Block 内所有 TraceID 构建的布隆过滤器,用于快速判断某个 TraceID 是否存在于该 Block 中。
- 索引文件 (index):可选,用于加速按时间范围或 TraceID 范围的扫描。
性能指标: Bloom Filter 的假阳性率(False Positive Rate)默认配置为 0.01(1%)。降低假阳性率会增大 Bloom Filter 文件体积,但能减少查询时需要读取的 Block 数量。在写入密集场景下,建议将假阳性率调整为 0.05 以降低存储开销;在查询密集场景下,调整为 0.001 以减少 I/O。
2.5 组件部署建议
基于生产环境经验,以下部署配置可作为初始参考:
| 组件 | 推荐副本数 | CPU / 内存 | 存储要求 | 关键配置项 |
|---|---|---|---|---|
| Distributor | 2-4 | 2C / 4GB | 无 | distributor.receiver-port |
| Ingester | 3-6 | 4C / 16GB | 本地 SSD 或高性能 PVC | ingester.max-block-duration |
| Querier | 2-4 | 4C / 8GB | 无 | querier.max-concurrent-queries |
| Compactor | 1-2 | 2C / 4GB | 无 | compactor.compaction-interval |
扩容策略: 当 Trace 写入量超过 10GB/h 时,优先扩容 Ingester 副本数并增加其内存配额。Ingester 的内存直接决定 Block 在内存中的缓冲大小,影响刷写频率和 Bloom Filter 的构建效率。Querier 的扩容通常由查询并发量驱动,建议与查询 QPS 增长同步。