Tempo 架构与组件:核心设计理念
Grafana Tempo 的架构设计围绕“低成本、高可用、易扩展”三大目标展开。与传统的分布式追踪系统(如 Jaeger、Zipkin)不同,Tempo 不依赖应用程序注入特定 SDK,而是通过接收标准协议(OTLP、Jaeger、Zipkin 等)的 span 数据,实现与语言无关的追踪数据采集。其核心架构由五个关键组件构成:Distributor、Ingester、Querier、Compactor 以及可选的 Query Frontend。
架构要点:Tempo 采用“写时无索引、读时构建索引”的设计哲学。写入路径仅做简单哈希分发和临时存储,不构建全局索引;查询时通过 Querier 从后端存储(如 S3、GCS、Azure Blob)中按块扫描并重建 trace。这种设计大幅降低了写入开销,适合高吞吐量场景。
2.1 组件职责与数据流
下图展示了 Tempo 的核心组件及其数据流转关系。从 span 数据接收、分发、暂存、持久化到最终查询,每个组件承担明确的职责。
2.2 组件职责详解
| 组件 | 核心职责 | 关键特性 |
|---|---|---|
| Distributor | 接收来自 SDK 或采集器的 span 数据,按 trace ID 进行哈希分发 | 无状态、水平扩展、支持 gRPC/HTTP 协议 |
| Ingester | 将 span 数据暂存于内存中,按块(block)组织并定期刷新到后端存储 | 有状态、内存暂存、块刷新策略可配置 |
| Querier | 接收查询请求,从后端存储和 Ingester 内存中检索 span 数据并组装完整 trace | 无状态、支持 TraceQL 查询语言 |
| Compactor | 合并后端存储中的小块为大块,删除重复数据,优化存储效率 | 后台运行、可配置合并策略 |
| Query Frontend | 可选组件,提供查询缓存、分片、合并等高级功能 | 提升大规模查询性能 |
性能瓶颈关注点:在实战中,Ingester 的内存使用率和块刷新频率是写入路径的常见瓶颈;Querier 的并发查询能力和后端存储的 IOPS 是查询路径的关键限制因素。Compactor 的合并速度会影响存储成本和查询效率。
2.3 部署模式对比
Tempo 支持两种部署模式:单二进制模式和微服务模式。选择哪种模式取决于你的规模、运维能力和性能需求。
| 对比维度 | 单二进制模式 | 微服务模式 |
|---|---|---|
| 部署复杂度 | 低,一个进程即可运行 | 高,需管理多个服务实例 |
| 资源隔离 | 差,组件共享内存/CPU | 好,每个组件独立资源 |
| 扩缩容能力 | 只能整体扩缩 | 按组件独立扩缩(如 Ingester 可单独扩容) |
| 故障影响 | 单进程崩溃导致全服务不可用 | 单个组件故障不影响其他组件 |
| 适用场景 | 开发测试、小规模(< 100GB/天) | 生产环境、大规模(> 100GB/天) |
| 配置方式 | 单一配置文件 | 每个组件独立配置,或共享配置 |
生产环境建议:对于日均追踪数据量超过 100GB 或对可用性要求较高的场景,强烈建议使用微服务模式。单二进制模式虽然部署简单,但在高负载下容易出现 OOM 或 GC 停顿,且无法针对瓶颈组件(如 Ingester)单独扩容。
2.4 数据流详细说明
理解数据在 Tempo 中的完整生命周期,是定位性能瓶颈的基础。下面按写入和查询两条路径详细拆解。
写入路径(Write Path)
- 应用发送 span:应用通过 OTLP、Jaeger 或 Zipkin 协议将 span 数据发送到 Distributor 的 gRPC 或 HTTP 端点。
- Distributor 哈希分发:Distributor 根据 trace ID 进行一致性哈希计算,将属于同一 trace 的所有 span 发送到同一个 Ingester 实例。这一步保证了 trace 的局部性。
- Ingester 内存暂存:Ingester 将接收到的 span 按 trace ID 组织成“块”(block),暂存在内存中。每个块包含多个 trace 的 span 数据。
- 块刷新(Flush):当块达到配置的大小或时间阈值时,Ingester 将块刷新到后端存储(如 S3、GCS)。刷新后的块变为不可变对象。
- Compactor 后台合并:Compactor 定期扫描后端存储中的小块,将它们合并为更大的块,并删除重复或过期的数据,优化存储布局。
查询路径(Read Path)
- Grafana 发起查询:用户通过 Grafana 的 TraceQL 查询界面或 API 发起 trace 查询请求。
- Query Frontend(可选):如果部署了 Query Frontend,它会将查询请求分片、缓存,并转发给 Querier。
- Querier 扫描存储:Querier 根据 trace ID 或查询条件,扫描后端存储中的所有相关块。同时,Querier 也会向所有 Ingester 实例查询内存中尚未刷新的 span 数据。
- 结果合并:Querier 将从存储和 Ingester 获取的 span 数据合并,组装成完整的 trace 结构,返回给 Grafana 展示。
性能瓶颈关键点:
- 写入瓶颈:Ingester 的内存使用率过高会导致 OOM 或频繁 GC;Distributor 的哈希不均会导致 Ingester 负载倾斜。
- 查询瓶颈:Querier 扫描大量小块(未合并)会导致查询延迟高;后端存储的 IOPS 不足会限制并发查询能力。
- 存储瓶颈:Compactor 合并速度跟不上块生成速度,会导致存储中小块数量激增,影响查询效率。
2.5 组件配置示例
以下是一个微服务模式下各组件的最小配置示例,展示如何独立启动每个组件。
# Distributor 配置 (distributor.yaml)
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
ring:
kvstore:
store: memberlist
# Ingester 配置 (ingester.yaml)
ingester:
lifecycler:
ring:
kvstore:
store: memberlist
replication_factor: 3
blocks_storage:
backend: s3
s3:
bucket: tempo-traces
endpoint: s3.amazonaws.com
block:
bloom_filter_false_positive: 0.05
encoding: zstd
# Querier 配置 (querier.yaml)
querier:
frontend_worker:
frontend_address: query-frontend:9095
search:
external_hedge_requests_at: 2s
external_hedge_requests_up_to: 3
# Compactor 配置 (compactor.yaml)
compactor:
compaction:
block_retention: 48h
compacted_block_retention: 1h
max_block_bytes: 107374182400 # 100GB
配置建议:在生产环境中,建议将
replication_factor 设置为 3 以保证数据高可用;block_retention 根据数据保留策略设置,通常为 24h-72h;max_block_bytes 控制单个块的最大大小,过大会增加查询扫描开销,过小会增加 Compactor 压力。