news 2026/9/20 8:16:22

Grafana Tempo 架构深度解析:写入路径、Parquet 列式存储与 TraceQL 查询的组件体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Grafana Tempo 架构深度解析:写入路径、Parquet 列式存储与 TraceQL 查询的组件体系

Grafana Tempo 架构深度解析:写入路径、Parquet 列式存储与 TraceQL 查询的组件体系

【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo

Grafana Tempo 是一个面向高吞吐、低依赖场景的分布式链路追踪后端。本文基于仓库中的 Tempo architecture 文档,系统讲解 Tempo 如何完成"接入链路数据 → 写入对象存储 → 按 TraceID 或 TraceQL 检索"的全过程,并结合源码剖析各组件职责、单体/微服务两种部署模式以及存储设计。读完本文,你将掌握 Tempo 的写入与读取生命周期、每个组件的运行方式与适用模式,以及如何通过-target参数组织实际部署。

设计目标:塑造 Tempo 架构的四个原则

Tempo 的架构围绕以下四个目标展开,它们直接决定了数据如何被接入、存放和查询:

  • 低成本的存储:所有链路(trace)数据全部存放在对象存储(object storage)中,不依赖自建的高成本本地存储集群。
  • 读写独立伸缩:在微服务模式下,Tempo 将写入路径(write path)与读取路径(read path)彻底分离,可分别按需扩缩容。
  • 无需额外复制的持久性:微服务模式下,一个 Kafka 兼容的消息队列充当写入预写日志(write-ahead log, WAL)。只要 Kafka 确认写入,数据即视为持久化,因此 Tempo 在写入路径上不做跨实例复制,可以以复制因子 1(RF1)运行,显著降低成本。
  • 高效的属性查询:数据块以 Apache Parquet 列式格式存储,查询时只读取所需列,而非扫描整条 trace,从而加速基于属性的搜索。

这四个目标在源码中都有对应体现:PushSpansToKafkaConsumeFromKafka等开关决定了数据在进程间是走 Kafka 还是进程内直推(见下文源码分析),而 tempodb/encoding/vparquet5 目录则承载了 Parquet 编码的具体实现。

部署模式:一个二进制,两种运行形态

Tempo 的所有组件都被编译进同一个二进制文件,通过-target参数(命令行形式--target)决定当前进程运行哪些组件。这带来了两种部署形态:

  • 单体模式(Monolithic)-target=all(默认值),所有必需组件运行在单个进程中,无需 Kafka;distributor 通过进程内调用把数据直接推送给 live-store 与 metrics-generator。
  • 微服务模式(Microservices):每个组件以独立进程、独立-target运行,需要 Kafka 兼容系统,是生产环境推荐模式。

无论哪种模式,生产环境都建议使用对象存储;本地文件系统后端仅用于开发与测试。

从 cmd/tempo/app/modules.go 可以看到这一设计的直接实现:文件顶部定义了全部目标常量(modules.go),包括alldistributormetrics-generatorquerierquery-frontendblock-builderbackend-schedulerbackend-workerlive-store等;模块管理器(setupModuleManager)随后注册了每个模块及其依赖关系,SingleBinary目标的依赖列表把BackendSchedulerBackendWorkerQueryFrontendQuerierDistributorMetricsGeneratorLiveStore全部串起来(modules.go),对应"一个进程跑所有组件"的单体形态。

-target的具体取值与说明汇总如下(来源:command-line-flags.md):

Target说明
all单体模式,单进程运行全部组件(默认)
distributor接收并分发 trace 数据给下游组件
metrics-generator从摄入的 trace 数据生成指标
querier查询后端存储中的 trace 与指标
query-frontend提供搜索 API,并把查询拆分为并行任务
block-builder从 Kafka 消费数据并写入后端存储块
backend-scheduler在多个 worker 间调度、协调后端查询任务
backend-worker执行 backend scheduler 分配的任务
live-store消费 Kafka 中近期数据,服务实时查询

注意:Tempo 3.0 移除了旧的ingestercompactorscalable-single-binary(SSB)目标,此前被称为 scalable monolithic mode 的形态已不再存在,写入与压缩职责分别由 live-store、block-builder 和 backend scheduler/worker 接管。

单体模式:何时使用与注意事项

单体模式适合快速上手、开发环境以及中低 trace 量的场景,此时运维简单性优先于独立伸缩。它的局限在于:所有组件共享同一资源池,查询负载的突增会影响写入吞吐,反之亦然;随着数据量增大,live-store 与 querier 等组件的内存压力集中在一台实例上,可能引发 OOM。部署时需为实例预留足够内存,以同时容纳 live-store 的内存 trace 缓冲、querier 的并发任务执行以及 backend worker 合并块时的内存开销。

微服务模式:独立伸缩与故障隔离

微服务模式适用于生产环境、高 trace 量、对高可用有要求的场景。其优势包括:block-builder、querier、live-store 等组件可独立扩缩容;故障域相互隔离(querier OOM 不影响数据接入,block-builder 重启不影响查询可用性);live-store 可跨可用区部署以提升高可用性;每个组件按需分配资源,避免单体模式的过度供给。

各组件的扩展策略参考如下(来源:deployment-modes.md):

组件扩展策略说明
Distributor水平无状态,按摄入速率扩展
Block-builder水平受 Kafka 分区数约束,按数据量扩展
Live-store水平受 Kafka 分区数约束,按近期数据查询量与内存扩展
Query frontend垂直保持 2 个副本,优先提升 CPU/RAM 而非加副本
Querier水平按查询并发与延迟要求扩展
Backend worker水平处理压缩与保留,按块数量与压缩滞后扩展
Metrics-generator水平按 trace 量与生成的序列数量扩展

在微服务模式中,Kafka 是写入路径的主干通信通道:distributor 写入 Kafka,block-builder、live-store、metrics-generator 各自独立消费;querier 通过 gRPC 访问 live-store 获取近期数据,所有组件则统一访问对象存储获取块数据。新增或下线任一组件实例都无需重配其他组件(除 Kafka 分区管理外)。从单体迁移到微服务时,只需为各组件指定相应-target、让所有组件指向同一套 Kafka、对象存储与 memberlist,然后缩容单体实例即可;由于 Kafka 已保证持久性,迁移过程不丢数据,live-store 启动时从 Kafka 重放,block-builder 从上次提交的偏移量继续消费。

两种模式下的组件差异速查

组件配置块单体模式微服务模式
Distributordistributor进程内直推 live-store 与 metrics-generator写入 Kafka
Ingestingest不使用写入路径的 Kafka 连接设置
Block-builderblock_builder不使用消费 Kafka,构建 Parquet 块并刷入对象存储
Live-storelive_store直接从 distributor 接收数据从 Kafka 消费
Live-store clientlive_store_clientquerier 到 live-store 的客户端(进程内)querier 到 live-store 的 gRPC 客户端
Query frontendquery_frontend进程内运行独立进程
Querierquerier进程内运行独立进程
Backend scheduler / workerbackend_scheduler/backend_worker进程内运行独立进程
Metrics-generatormetrics_generator可选,进程内运行可选,独立进程
Storagestoragetrace 数据存储后端trace 数据的对象存储

数据流转:写入路径与读取路径

Tempo 将"把数据写入存储"的写入路径与"服务查询"的读取路径明确分离,两条路径的生命周期如下。

一条写入的生命周期

以下步骤描述微服务模式的写入路径(单体模式下,distributor 以进程内方式把数据推给 live-store 与 metrics-generator,而非写入 Kafka):

  1. 追踪管线(如 OpenTelemetry Collector、Grafana Alloy)把 trace 数据发送给distributor
  2. distributor 校验请求,按 trace ID 对 trace 分片,并写入Kafka
  3. Kafka 确认写入后,distributor 向客户端返回响应。此后的下游消费都是异步的:
  4. Live-store从 Kafka 消费数据,使近期数据可被查询。
  5. Block-builder从 Kafka 消费数据,为长期对象存储构建数据块。
  6. Metrics-generator(可选)从 Kafka 消费数据,从 trace 派生指标。

源码印证:在 initDistributor 中,t.cfg.Distributor.PushSpansToKafka = !singleBinary(modules.go),即只有非单体模式才写 Kafka;单体模式下则注册两个进程内目标函数localPushTargets.GeneratorlocalPushTargets.LiveStore,把数据直接 push 给同进程的 metrics-generator 与 live-store。类似地,initLiveStore 中t.cfg.LiveStore.ConsumeFromKafka = !IsSingleBinary(t.cfg.Target)(modules.go)决定 live-store 是否从 Kafka 消费,initGenerator 中ConsumeFromKafka同理控制 metrics-generator。

一条读取的生命周期

  1. Query frontend接收查询,将其拆分为"近期数据"与"长期存储"两类任务。
  2. Querierlive-store获取近期数据。
  3. 对更早的数据,querier 从对象存储拉取数据块。
  4. 结果聚合后返回给用户。

读取路径在模块依赖图中同样清晰:Querier模块依赖{Common, Store, LiveStoreRing, PartitionRing},而QueryFrontend依赖{Common, Store, OverridesAPI}(modules.go);initQuerier 还会在单体模式下自动把 worker 地址指向本进程 gRPC 端口(modules.go),保证单体部署开箱即用。

组件总览

组件职责使用模式
Distributor接收并校验 span,路由到写入路径两种模式
Kafka 兼容队列distributor 与下游消费者之间的持久化 WAL微服务模式
Block-builder构建 Apache Parquet 块并刷入对象存储微服务模式
Live-store服务近期 trace 数据的查询两种模式
Query frontend接收查询并拆分为并行任务两种模式
Querier针对 live-store 与对象存储执行查询任务两种模式
Backend scheduler 与 worker处理压缩、保留与块列表维护两种模式
Metrics-generator可选;从 trace 派生 RED 指标与服务图两种模式
对象存储所有 trace 数据的长期存储两种模式

组件详解

Distributor

distributor 是 trace 数据的入口。它接收 OTLP(推荐)、Jaeger 和 Zipkin 三种格式的 span,并按照每租户的接入限制(per-tenant ingestion limits)进行校验。微服务模式下,它按 trace ID 分片并写入 Kafka;单体模式下则进程内推送给 live-store 与 metrics-generator。其实现位于 modules/distributor/distributor.go,分片与转发逻辑可以追溯到PushSpansToKafkaLocalPushTargets的分支处理。

Kafka 兼容队列

微服务模式下,Tempo 使用 Kafka 兼容系统(如 Apache Kafka 或 WarpStream)作为 distributor 与下游消费者之间的持久化 WAL。由于 Kafka 在确认写入后即保证持久性,Tempo 可以以复制因子 1(RF1)运行,无需在写入路径做额外复制。单体模式不使用 Kafka。Kafka 的接入配置在ingest配置块下,可参考 configure-kafka 了解完整设置。

Block-builder

block-builder 从 Kafka 消费 trace 数据,把 span 组织成 Apache Parquet 数据块,并刷入对象存储长期保留。它只在微服务模式运行;单体模式下由 live-store 直接向对象存储刷块。对应实现见 modules/blockbuilder/blockbuilder.go,其块配置与 WAL 版本始终取自storage.trace.block(见 initBlockBuilder)。

Live-store

live-store 服务近期 trace 数据:trace 保存在内存和本地 WAL 中,因此在摄入后数秒内即可查询。微服务模式下它从 Kafka 消费数据;单体模式下直接从 distributor 接收。为保障高可用,live-store 可以跨可用区部署。实现位于 modules/livestore/live_store.go,它对外以 gRPC 暴露 Querier 与 Metrics 服务(modules.go)。

Query frontend

query frontend 是查询入口:接收 TraceQL 查询与 trace ID 查询,把每条查询拆分为并行任务分发给 querier,再合并结果返回最终响应。它在 modules/frontend/frontend.go 中实现;initQueryFrontend 为其注册了 trace by ID、search、search tags、metrics query、MCP 等一系列 HTTP 端点。

Querier

querier 执行 query frontend 分发的任务:从 live-store 获取近期数据,从对象存储获取历史数据,然后返回给 query frontend 合并。实现见 modules/querier/querier.go,其注册的 HTTP 处理器覆盖 TraceByID、Search、SearchTags、SearchTagValues、QueryRange 等(modules.go)。

Backend scheduler 与 worker

backend scheduler 与 backend worker 共同负责对象存储数据的压缩(compaction)、保留(retention)与块列表(blocklist)维护:scheduler 创建任务并分派给 worker,worker 把小块压缩成更大的块,并按保留期过期数据。两者取代了旧版的 compactor。相关代码见 modules/backendscheduler/backendscheduler.go 与 modules/backendworker/backendworker.go。保留策略的具体过期逻辑可参考 tempodb/retention.go。

Metrics-generator

metrics-generator 是可选组件,它从 trace 派生速率(rate)、错误(error)、耗时(duration)指标与服务图(service graphs),并通过 remote write 写入 Prometheus 或 Grafana Mimir 等指标后端。实现位于 modules/generator/generator.go,支持span-metricsservice-graphs等处理器。

对象存储

对象存储是所有 trace 数据的长期存储层。Tempo 支持三种主流对象存储 API,并提供本地文件系统后端用于开发与测试:

  • Amazon S3(以及 MinIO 等 S3 兼容系统)
  • Google Cloud Storage(GCS)
  • Microsoft Azure Blob Storage
  • 本地文件系统(开发/测试)

各后端实现分别位于 tempodb/backend/s3、tempodb/backend/gcs、tempodb/backend/azure、tempodb/backend/local。后端 worker 在保留期过后使对象存储中的数据过期,从而强制执行保留策略。

存储:Parquet 列式块与保留

Tempo 把全部 trace 数据存放于对象存储,以 Apache Parquet 列式格式组织成数据块。列式存储的关键收益是:查询时只读取所需属性对应的列,而不是扫描整条 trace。Tempo 当前的 Parquet 编码版本为 vParquet5,实现位于 tempodb/encoding/vparquet5。保留策略通过 backend worker 在对象存储中过期数据实现。

你能查询什么

Tempo 回答两类读取请求:按 trace ID 查询特定 trace,以及使用 TraceQL(Tempo 的 trace 查询语言)跨 trace 搜索。TraceQL 的解析器、AST 与执行引擎位于 pkg/traceql。此外,你还可以直接基于 trace 数据计算 TraceQL 指标(例如 span 速率与延迟分位数),并使用可选的 metrics-generator 产出 RED 指标与服务图。

接入方面,Tempo 支持 OpenTelemetry(OTLP)、Jaeger 与 Zipkin 三种格式。同时 Tempo 是多租户的:trace 数据在存储层按租户隔离。

命令行与配置实操

关键命令行标志

Tempo 的全局与部署相关标志(完整参考见 command-line-flags.md):

Flag说明默认值
--config.file要加载的配置文件
--config.expand-env在配置文件中展开环境变量false
--config.verify校验配置后退出false
--target要运行的目标模块all
--multitenancy.enabled启用多租户false
--http-api-prefix所有 HTTP API 端点的字符串前缀""
--shutdown-delaySIGTERM 与关闭之间的等待时间0
--log.level日志级别:debug/info/warn/errorinfo
--log.format日志格式:logfmt/jsonlogfmt
--server.http-listen-portHTTP 监听端口3200
--server.grpc-listen-portgRPC 监听端口9095
--health/ready端点执行健康检查后退出false
--health.url健康检查 URLhttp://localhost:3200/ready

常见用法示例:

# 以配置文件启动(默认单体模式) tempo --config.file=/etc/tempo/config.yaml # 以指定目标启动(微服务模式下的 distributor) tempo --target=distributor --config.file=/etc/tempo/config.yaml # 只校验配置、不启动 tempo --config.file=/etc/tempo/config.yaml --config.verify # 打印版本信息 tempo --version # 在配置文件中展开环境变量(便于注入密钥与环境相关值) tempo --config.file=/etc/tempo/config.yaml --config.expand-env # 微服务部署中,为 distributor 指定自定义 HTTP 端口并以 JSON 格式输出日志 tempo --target=distributor \ --config.file=/etc/tempo/config.yaml \ --server.http-listen-port=3200 \ --log.format=json # 单体模式启用多租户并设置 30 秒优雅关闭延迟 tempo --config.file=/etc/tempo/config.yaml \ --multitenancy.enabled \ --shutdown-delay=30s

--health标志专为无 shell 的 distroless 容器镜像设计:由于镜像内没有curl/wget,可直接用它编写 Dockerfile 健康检查:HEALTHCHECK CMD ["/tempo", "--health"]。Kubernetes 用户通常改用 httpGet 探针直接探测/ready端点。

配置加载与单体模式默认行为

从 cmd/tempo/main.go 的loadConfig(main.go)可以看出配置加载顺序:先解析命令行标志,注册默认值,再叠加配置文件(YAML,严格解析),最后用 CLI 覆盖。有趣的是,当目标为单体模式时,代码会强制把 Generator、LiveStore 的 ring KVStore 设为inmemory、地址设为127.0.0.1,并清空 BackendWorker 的 ring KVStore 以进入"无分片"模式(main.go)——这正是单体模式无需额外 KV 存储即可运行的原因。

单二进制(单体)模式配置示例

仓库中的 example/docker-compose/single-binary/tempo.yaml 是一个完整的单体模式示例(默认-target=all,故未显式写出 target):

stream_over_http_enabled: true server: http_listen_port: 3200 log_level: info distributor: receivers: otlp: protocols: grpc: endpoint: "tempo:4317" http: endpoint: "tempo:4318" #log_received_spans: # enabled: true # log_discarded_spans: # enabled: true metrics_generator: registry: external_labels: source: tempo cluster: docker-compose storage: path: /var/tempo/generator/wal remote_write: - url: http://prometheus:9090/api/v1/write send_exemplars: true query_frontend: mcp_server: enabled: true storage: trace: backend: local wal: path: /var/tempo/wal # where to store the wal locally local: path: /var/tempo/blocks overrides: defaults: metrics_generator: processors: ["span-metrics", "service-graphs"] generate_native_histograms: both usage_report: reporting_enabled: false

该示例使用local本地文件系统后端(适合开发/测试,生产请改用对象存储),配置了 OTLP gRPC/HTTP 接收器、metrics-generator 及其到 Prometheus 的 remote write、以及 query frontend 的 MCP server。微服务模式的完整多组件示例可参考仓库中的 example/docker-compose/distributed 目录。

三条必须记住的事实

关于 trace 数据,有三点值得特别留意:

  • trace 没有"结束"概念:一条 trace 可以从任何一个携带全新 trace ID 的 span 开始,未来任意时刻都可能有新 span 追加进来。
  • 按 trace ID 查询返回的是完整关系图:Tempo 返回当前已存储/接入的所有属于该 trace 的 span,并以映射后的响应呈现(即 span 之间的父子/兄弟关系图),例如 Grafana 可以据此对 trace 做可视化渲染。
  • TraceQL 查询不保证时间序:返回的是所有匹配过滤条件的 trace,但可能不是按时间顺序——因为某些 querier 返回结果比其他 querier 更快。当 TraceQL 查询达到最大 trace 数限制时,query frontend 会先返回已收集到的 trace 与 span,并停止等待尚未返回的 querier。

下一步

  • 规划与部署 Tempo:参见 set-up-for-tracing 下的部署文档。
  • 深入了解部署模式细节:见 Deployment modes。
  • 学习 span 的组成部分与可查询字段:见 Trace structure。
  • 掌握完整配置项:见 configuration 文档。
  • 深入源码:模块装配逻辑见 cmd/tempo/app/modules.go,各组件实现位于 modules 目录,Parquet 编码在 tempodb/encoding/vparquet5,TraceQL 引擎在 pkg/traceql。

【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 5:10:43

Pico 快速上手:RP2040 固件烧录与 MicroPython 外设实战

手里捏着一块刚从防静电袋里拆出来的 Raspberry Pi Pico,桌上摆着 USB 线和一堆杜邦线,然后呢?我见过太多人卡在这一步——插上电脑,指示灯亮了,设备管理器里多出来一个串口,然后就没有然后了。"Raspb…

作者头像 李华
网站建设 2026/9/20 5:03:30

PyCharm远程连接服务器:SSH+SFTP+远程调试完整配置指南

先把结论摆出来:PyCharm连远程服务器这招,用好了是真的能让你从“本地改一行、上传、服务器跑、报错、再改一行”这种原始模式里彻底解放出来。本地写代码,远程解释器执行,断点调试也直接在本地IDE里看变量、看调用栈,…

作者头像 李华
网站建设 2026/9/20 7:20:03

改 pplx-search-sdk 的模型入口,TaoToken Key 生效

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华