1. 破局的起点:为什么分布式追踪一直这么“难搞”
做后端的人,大概率都有过这种经历:线上突然告警,某个接口的 P99 延迟从 80ms 飙到 800ms,你打开监控大盘,发现 CPU、内存、QPS 全部正常,Prometheus 指标看起来一片祥和。这时候你会特别想骂人——指标是有了,但指标只告诉你“哪里慢了”,它不告诉你“为什么会慢”。于是你只能一台台服务器登上去,翻日志、看慢查询、猜依赖,运气好半小时定位,运气差搞到凌晨三点。
这就是典型的“可观测性陷阱”:监控(Monitoring)告诉你系统坏了,日志(Logging)告诉你系统发生了什么,但只有追踪(Tracing)能告诉你一次请求到底走了哪些服务、每段花了多长时间、瓶颈卡在哪个环节。分布式追踪不是锦上添花,而是微服务架构下定位问题的最后一块拼图。
但问题来了——分布式追踪系统本身也“很难搞”。用过 Jaeger 的朋友应该深有体会:要么自建 Cassandra 或 Elasticsearch 集群,要么上云托管的 ES,存储成本随采样量线性飙升,运维复杂度直接拉满。ZIPKIN 也好不到哪去,内存存储扛不住生产压力,换到 ES 又是一轮折腾。中小团队往往会在“采全量”和“省成本”之间反复纠结,最终妥协成只采 5% 的采样率,结果就是真正出问题时,关键链路恰好没被采到,追踪系统形同虚设。
Grafana Tempo 就是在这样的背景下杀出来的。它对存储层做了彻底重构,直接使用 S3、GCS、Azure Blob 这类对象存储作为后端,用 Parquet 列式格式压缩 trace 数据,把存储成本打到了 Jaeger 方案的十分之一甚至更低。同时它不需要额外的索引数据库,查询时通过 TraceID 直接检索对象存储里的 Parquet 文件,再配合 Grafana 全家桶实现“指标-日志-追踪”三箭齐发。
这篇文章,我想从 Tempo 的诞生背景、架构设计、落地实操和问题排查四个维度展开,把我这半年在生产环境里摸爬滚打的经验一次性倒出来。如果你正在 Jaeger 的高运维成本和全量追踪的梦想之间反复摇摆,这篇文章应该能帮你把账算明白。
2. 从需求倒推设计:Tempo 到底解决了什么问题
2.1 既有追踪系统的三大痛点
在聊 Tempo 的设计哲学之前,必须先搞清楚一件事:为什么 Jaeger 这类老牌追踪系统在 2020 年之后逐渐显得力不从心。我把它总结为三个痛点。
第一是存储成本失控。Jaeger 的主流生产部署方案是 ES 后端 + 高磁盘 IOPS 的节点,ES 为了支撑 trace 的 tag 查询,需要建大量索引,而索引本身的内存和磁盘开销甚至比原始数据还要大。数据量上来之后,你不得不做冷热分层、索引生命周期管理,这些复杂度最终都会转化成人的工作量。我见过一个日请求量在亿级的团队,Jaeger 集群光 ES 就用了 12 台 16C64G 的机器,每个月的存储成本够再招一个初级运维。
第二是采样率和问题定位之间的矛盾。为了控制成本,大家习惯把采样率调到 10% 甚至更低。但分布式系统的故障往往是小概率事件,低采样率意味着故障链路大概率没被采到,真正想排查的时候数据是缺失的。而高采样率(尤其是 100% 全采)对 Jaeger 来说存储和计算压力又太大,直接进入成本无底洞。
第三是查询体验割裂。Jaeger 的 UI 虽然能用,但它和 Prometheus 指标、Loki 日志是分离的。你在 Grafana 大盘上看到一个延迟异常的服务,想顺着 traceID 查这条请求的内部调用链,得切换到 Jaeger 的独立页面,重新输入 traceID,来回切换的效率极低。可观测性讲究“从指标到日志再到追踪”的顺滑跳转,老牌系统在这一块做得并不好。
2.2 Tempo 的产品哲学:把复杂度从“存储”挪到“查询”
Tempo 的设计者做了一个很聪明的决定:既然 trace 数据的查询路径绝大多数情况下是“先知道 traceID,再去查这条 trace 的细节”(比如从日志里捞到 traceID,或者在 Grafana 的指标面板里点进去),那为什么不干脆围绕这个核心场景把存储简化到极致?
于是 Tempo 直接砍掉了索引数据库,把 trace 数据以 Parquet 列式格式写入对象存储。对象存储本身就足够便宜(S3 的存储单价大约是 EBS 的十分之一),而且不需要你运维任何数据库集群。查询的时候,Tempo 会根据 traceID 计算出一个范围,直接去对象存储拉取对应的 Parquet 文件块,然后做过滤和合并,最后把完整的 trace 树返回给前端展示。
这就好比传统方案是建了一座图书馆(ES),每本书都要登记编号、作者、主题标签,查询的时候走检索系统找到书;Tempo 则把书直接扔进一个巨大的仓库(对象存储),按区域编号摆放,取书的时候直接按编号去对应区域翻,翻到哪本算哪本。关键在于——图书管理员不需要维护一本巨大的索引目录了,仓库足够便宜,所以你可以把所有书都扔进去,不用再纠结“采全量还是采 5%”。
当然,这个设计也带来一个代价:Tempo 的查询必须依赖 traceID,不支持“按服务名+时间段”去模糊搜索所有 trace。这是它在设计上主动做出的取舍——因为模糊搜索场景交给 Prometheus 指标和 Loki 日志去完成,Tempo 只负责“拿到 ID 之后把全链路捞出来”。在实际使用中,这个取舍完全站得住脚。
2.3 TraceQL:追着 TraceID 跑的“进阶查询语言”
Tempo 在 2.0 版本引入了 TraceQL,这算是它区别于所有老牌追踪系统的一大杀器。TraceQL 允许你通过结构化查询语言直接对 trace 数据结构做检索,而不只是靠 traceID 点对点查询。比如你想找所有“payment service 调用了 database 且耗时超过 2 秒”的 trace 样本,可以用类似 PromQL 风格的表达式直接搜,比如:
{ resource.service.name = "payment" && span.db.system = "postgres" } && duration > 2sTraceQL 解决的场景是“不知道 traceID,但想通过属性组合条件找到一批可疑 trace”。这就补上了 Tempo 文件名系统在“模糊查询”上的短板,同时仍然保持存储层的极简架构——因为 TraceQL 实际上是扫描 Parquet 文件的列数据,在 Parquet 本身的列式存储之上做过滤,不需要额外建索引库。
我在实际测试里用 TraceQL 搜过“错误码为 500 且持续时间超过 1 秒”的 trace,返回速度在千万级 trace 规模下大概是秒级,虽然不如 ES 的倒排索引那么快,但完全够用。关键是——你省的可是整整一个 ES 集群的运维成本,这笔账怎么算都划算。
3. 核心架构拆解:Tempo 的每个组件都在干什么
3.1 从数据流入到可查询的完整链路
Tempo 的架构从数据流的角度看非常清晰,它把传统 tracing 后端拆分成五个核心角色:Distributor、Ingester、Query Frontend、Querier、Compactor。我按一条数据从采集端到可查询的完整生命周期来讲解。
客户端通过 OpenTelemetry Collector 或 Jaeger Agent 把 trace 数据推给 Distributor。Distributor 负责对数据做校验和哈希路由,它根据 traceID 算出应该由哪个 Ingester 实例处理这份数据。这里注意:Tempo 采用一致性哈希环,同一个 traceID 的所有 span 会被路由到同一个 Ingester,这是保证后续查询能拿到完整 trace 树的前提。
Ingester 是数据写入的内存缓冲区,它会把接收到的 span 在内存中按 traceID 聚合,形成完整的 trace 结构。当满足一定条件(比如超过 2 万条 span 或者 15 秒时间窗),Ingester 会把内存中的 trace 块 Flush 到对象存储。所以从数据层面看,Tempo 的实时性取决于 Ingester 的 Flush 周期,最多延迟几十秒,完全可接受。
查询侧则是另一条链路:用户通过 Grafana 发出查询请求,Query Frontend 负责接收请求、做并行化拆分,然后交给 Querier 去对象存储里拉取 Parquet 文件块。如果 trace 数据还很新、还驻留在 Ingester 内存里,Querier 也会去 Ingester 查询一遍。这种“内存 + 对象存储”的双层检索设计和老牌系统的纯数据库查询形成鲜明对比。
Compactor 是后台任务角色,它定期扫描对象存储里的块文件,把小文件合并成大文件(比如把所有块合并成每 2 小时一个的大块),并清理已经过期的数据(TTL)。这个合并动作可以显著降低对象存储中的小文件数量,减少查询时的文件拉取开销,属于 Tempo 在成本控制上的又一个暗招。
3.2 存储层的关键魔法:vParquet 格式为什么能省这么多钱
Tempo 最开始用的是 JSON 格式存 trace,后来在 1.x 版本引入 Parquet 方案以为替代者,2.x 全面转向 vParquet。我在这里用最通俗的方式解释 Parquet 在 trace 数据上为什么这么香。
trace 数据的本质是一堆结构相同的 span,每个 span 有固定的字段集合:traceID、spanID、parentSpanID、开始时间、结束时间、服务名、操作名、标签集合。这种强结构化的数据天然适合列式存储。Parquet 把同一列的数据连续存放在一起,查询只取需要的列,过滤时能直接跳过无关数据块。
举个例子:你想查 traceID 为abc123的所有 span,用 Parquet 格式存储时,只需要读取 traceID 这一列的索引信息,定位到具体的数据块,再反查其他列。行式存储(比如 JSON)则必须把整个文件都读进来才能找到目标。Tempo 实测可以把 trace 数据的存储体积压缩到原始 JSON 格式的 1/10 到 1/20,加上对象存储本身就便宜,最终的成本优势就是数量级的差距了。
还有一点容易被忽略:Parquet 支持内嵌压缩算法(Snappy、Zstd)。Tempo 默认用 Zstd 压缩,我在生产环境里对比过,同样的 span 数据量,Zstd 比默认配置再节约大约 30% 的存储。设置里有一个storage.trace.block.parquet.compression参数,别用默认的 Snappy,直接改成zstd,一劳永逸。
3.3 可扩展性与高可用设计
Tempo 的集群模式是无状态设计,所有组件都可以水平扩展。Distributor 和 Query Frontend 是无状态的,随便加副本;Ingester 虽然是有状态的(内存数据),但可以通过一致性哈希环实现平滑扩缩容;Compactor 则是典型的后台任务型工作负载。
我搭过的最小生产集群是三节点:每台机器同时跑 Ingester + Querier + Compactor,再单挂一台 Distributor。在日均 5000 万 span 的条件下,三台 8C16G 的机器绰绰有余,对象存储走 S3。整个集群的 CPU 使用率维持在 20% 左右,内存大头被 Ingester 的写缓冲占着。这个资源消耗水平比 Jaeger 的 ES 方案至少降了一个量级。
高可用方面值得一提:因为所有数据最终都持久化在对象存储里,Ingester 挂了最多丢失 Flush 周期内的少量数据,不会影响历史 trace 的查询。这比 Cassandra 节点挂了要修复数据、ES 集群要重新分片的高危操作简单得多。Tempo 的架构容错性,本质上是把“状态”外包给了对象存储,这是它的核心逻辑。
4. 落地实操:从零部署一套生产级 Tempo
4.1 部署模式选型:单体、微服务还是 Helm Chart
Tempo 提供了三种部署模式:单体模式(single binary)、微服务模式(各组件独立部署)和无头模式(serverless 化)。单体模式适合开发环境和每日亿级 span 以内的小规模生产;微服务模式适合大规模集群,需要按组件分别扩容;无头模式则适合极端弹性场景,通过 Lambda 等 serverless 平台跑 Querier 和 Compactor。
对于大多数团队,我强烈建议从“单体模式 + 多副本”起步。Tempo 的单体二进制同时跑所有组件,但内部走 gRPC 通信,对外看起来是一个完整服务。你在 Kubernetes 里部署两个副本加上 Service,就已经具备基本的高可用能力。等规模真正上来,再拆组件也不迟——迁移成本并不高,因为存储层是共享的对象存储。
如果你用的是 Kubernetes,直接上 Grafana 官方维护的 Helm Chart 最省事。它的 values.yaml 写得很清楚,把存储配置、副本数、资源限制都暴露出来。我用的是以下核心配置片段:
tempo: storage: trace: backend: s3 s3: bucket: my-tempo-data endpoint: s3.cn-north-1.amazonaws.com.cn region: cn-north-1 access_key: ${S3_ACCESS_KEY} secret_key: ${S3_SECRET_KEY} querier: frontend_worker: frontend_address: tempo-query-frontend:9095 compactor: compaction: block_retention: 720h compact: - block_encoding: vparquet3 store: trace: block: parquet: compression: zstd注意上面这段配置我特意标注了block_retention: 720h,这是 trace 数据保留 30 天。存储桶的 lifecycle 策略建议同时配置,在 S3 侧把超过 35 天的对象自动清理掉,双保险,防止 Compactor 异常时存储无限增长。
block_encoding在存储路径上其实不需要手动配置(新版本默认就是 vparquet3),但如果你从旧版升级,一定要确认 block 格式没有混用。混用不同 Parquet 版本的块会导致查询报错,我在升级时就被坑过一次。
4.2 数据接入:OpenTelemetry Collector 的配置与注意事项
Tempo 不直接对接你的应用,而是通过 OpenTelemetry Collector 接收数据。这意味着你需要在应用侧接入 OTel SDK 或 Jaeger Agent,然后把数据发送给 Collector,再由 Collector 做批量转发到 Tempo。
应用侧的 OTel SDK 配置直接写在环境变量里就够了:
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel-collector:4318/v1/traces OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,deployment.environment=production OTEL_TRACES_SAMPLER=always_on这里要特别强调:always_on意味着 100% 采样,这是 Tempo 方案的核心卖点。因为存储成本已经降下来了,你完全没必要再做概率采样。如果你担心极端流量下的成本,可以用 OTel 的parentbased_traceidratio采样器,但不要用传统的固定比例采样——记住,Tempo 的价值就在于全量数据可查。
Collector 侧的最小配置如下,开启 OTLP receiver、批量处理、导出到 Tempo:
receivers: otlp: protocols: grpc: http: processors: batch: timeout: 5s send_batch_size: 10000 exporters: otlp/tempo: endpoint: tempo-distributor:4317 tls: insecure: true service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [otlp/tempo]Batch 处理器是性能关键。send_batch_size调的太高会导致内存压力,太低则浪费网络开销。5 秒超时 + 1 万 batch 是我测试过相对平衡的配置,在每秒约 2 万 span 的流量下没有出现积压。
4.3 Grafana 数据源集成:把 Trace 和 Metrics 打通
Tempo 的查询入口默认是 Grafana,你需要在 Grafana 里添加 Tempo 数据源。这一步看着简单,但有个隐藏的坑:Grafana 的 Tempo 数据源配置页里,需要额外填一个“Trace to Metrics”和“Trace to Logs”的关联配置。
Trace to Metrics 的作用是:当你在 Tempo 面板里选中某个 span 时,Grafana 能自动跳转到 Prometheus 查询这个服务相关的指标。它的配置方式是填一个 Prometheus 数据源和一组标签映射,比如:
- name: 'Trace to Metrics' datasourceUid: 'prometheus' tags: - key: 'service.name' value: 'service_name'Trace to Logs 的作用则相反:从 span 跳转到 Loki 里的相关日志,通过 traceID 关联。配置里填 Loki 数据源和 traceID 的标签名。这两组关联配置的价值在你真正排查问题时才会体会到——看到一笔慢 trace,一键就能跳到慢服务的 CPU 指标和相关日志,不用再切来切去。我曾经在处理一个缓存穿透问题的时候,就是通过 Tempo 面板点到 Redis 慢查询日志,瞬间定位到热点 key 的。
Grafana 数据源配置完成之后,强烈建议先验证端到端链路:从应用侧手动发一笔测试请求,生成一个 trace,然后在 Grafana 的 Explore 页面用 TraceID 搜索。如果搜不到,先别怀疑 Tempo,去查 Collector 有没有报错、Tempo 的 Distributor 有没有收到数据,按照“应用 → Collector → Tempo → 对象存储”的顺序排查。
4.4 生产环境的基本监控与告警
Tempo 自身会暴露 Prometheus 指标,端口默认是 3200。你需要把这些指标接入 Prometheus 并配置基础告警。我总结的核心指标有三个:
tempo_distributor_spans_received_total:每秒接收的 span 总量,用来判断流量是否突增。tempo_ingester_blocks_flushed_total:Flush 成功率,如果这个指标长时间不增长,说明数据卡在 Ingester 内存里没写进对象存储,这是数据丢失的前兆。tempo_request_duration_seconds_count:查询延迟,如果 P99 超过 3 秒,说明对象存储的读取性能不达标,或者查询的 trace 太大。
告警规则我建议重点盯tempo_ingester_blocks_flushed_total,因为其他指标异常最多是查询慢,Flush 失败直接就是数据丢失。有一个比较隐蔽的问题:Ingester 的内存上限如果设置的太低,Flush 会频繁触发,但小文件太多又拖慢 Compactor 的合并效率。我建议给 Ingester 留足内存,让块尽量攒大一点再刷盘。
5. 填坑实录:这半年我在生产环境踩过的 6 个坑
5.1 对象存储 Endpoint 配错导致写入失败
这个坑很基础,但实在太多人踩了。如果你用的是 AWS S3,endpoint 可以直接默认;但如果用的是 MinIO、华为云 OBS 或者私有对象存储,endpoint 必须写成服务地址(比如http://minio:9000)。我当时部署测试环境时把 endpoint 写成了http://minio:9000/bucket,结果把 bucket 路径混进了 endpoint,导致所有写入请求都返回 404 Not Found。排查了大半天,最后在 Tempo 日志里看到The specified bucket does not exist才反应过来。
补充一个经验:Tempo 对 S3 兼容性做得很好,大部分 S3 兼容存储都能正常用。但在选型时,尽量避开那些“S3 协议支持不完整”的小众存储,尤其是读取性能差的。因为 Tempo 的查询路径高度依赖对象存储的 GET 延迟,存储选不好,查询 P99 会直接飙到 5 秒以上。
5.2 gRPC 与 HTTP 端口混淆
Tempo 的 Distributor 默认同时监听4317(gRPC)和4318(HTTP)端口用于接收数据。如果你在 Collector 里配置 exporter 的时候用了4318却写了 gRPC 协议,或者反过来 HTTP 却写了4317,数据就是传不进去。这类问题表面症状是 Grafana 里搜不到任何 trace,但日志里也没有明显报错。
我的排查习惯是:先看 Collector 的 metrics 里exporter_sent_spans有没有增长,再看 Tempo 这边tempo_distributor_spans_received_total有没有增长。前者增长后者不增长,说明 Collector → Tempo 这段链路配置有误,重点检查协议和端口。
5.3 Ingester Flush 周期过长引起查询延迟
Tempo 默认的 Ingester Flush 周期是 30 秒,意味着刚产生的 trace 最多半分钟后才能查询到。这看起来没什么,但如果你在 Grafana 里点了“Live Tail”,希望实时看 trace,就会觉得特别慢。我把 Flush 周期调到了 10 秒,代价是对象存储里的小文件数量增加了,Compactor 的合并压力稍大。对于大多数场景,30 秒默认值足够了,别为了实时性牺牲存储整洁度,除非你确实有实时 trace 展示的业务需求。
5.4 内存压力与 OOM 的边界控制
Ingester 是内存大户。Tempo 的 Ingester 在把数据刷到对象存储之前,会把 span 按 traceID 在内存中聚合,极端情况下(比如某个大巨型 trace 有几十万 span),单个 trace 就能吃掉几个 GB 内存。我遇到过 OOM Killed 重启的情况,最后是做了两层防范:一是给 Ingester 设置合理的内存 limit,二是给每个 trace 的 span 数设置上限(在 OTel Collector 的memory_limiter处理器里控制)。
memory_limiter是 OTel Collector 的常用处理器,配置如下:
processors: memory_limiter: check_interval: 1s limit_mib: 2048 spike_limit_mib: 512这玩意能有效避免把超大 trace 无脑灌给 Tempo,保护 Ingester 不被打爆。
5.5 TraceQL 查询慢:到底该不该用
TraceQL 确实是神器,但不是万能的。我在跑 TraceQL 查询时发现,它实际上是去扫描 Parquet 文件里的列数据,查询条件越复杂、扫描范围越大,耗时越长。一次按status = error扫描一个月数据的查询,跑了将近 20 秒。所以我的建议是:TraceQL 适合“小范围快速检索”,比如最近 30 分钟内按某个错误状态过滤;跨天甚至跨月的大范围检索,别指望秒出,给用户做好等待预期,或者尽量先用 Prometheus 指标缩小范围再进 TraceQL。
5.6 成本预估:全量采样到底会不会爆预算
很多团队不敢上全量采样,怕成本失控。我这边的实测数据:单 span 平均大小约 300 字节(压缩后),日均 5000 万 span 大概产生 15GB 压缩数据,一个月是 450GB。S3 标准存储大约 $23/月,加上请求费用,总共不到 $50/月。这比 Jaeger + ES 三节点一个月的成本低了 80% 以上。所以结论很明确:全量采样在大规模生产环境是可行的,前提是存储介质真的用对象存储而不是自建数据库。
6. 与 Grafana 生态的化学反应:Tempo + Prometheus + Loki 实战联动
6.1 指标 → 日志 → 追踪的“一键下钻”实践
Tempo 最强的地方,从来不是它单独好用,而是它和 Grafana 生态的深度整合。我实际排查过这样一个问题:某天下午订单成功率突然下降,我先在 Grafana 的 Prometheus 大盘上看到order-service的错误率从 0.1% 跳到了 5%,点击错误率图表选择“View in Explore”,切到 Tempo 数据源,输入同一时间段,用 TraceQL 搜{ resource.service.name = "order-service" } && status = error,秒级返回了所有错误 trace 列表。
点进其中一个 trace,看到完整的调用链:order-service 调用了 inventory-service,inventory-service 又调用了 MySQL。瓶颈出现在 order-service 和 inventory-service 之间的 gRPC 调用上,耗时 1.8 秒,错误信息显示context deadline exceeded。再点击这一段 span 的“Logs”按钮,跳转到 Loki 里对应 traceID 的日志,发现是 inventory-service 的数据库连接池打满了,慢查询堆积。
整个过程从发现问题到定位根因,大约花了 15 分钟。放在以前我用 Jaeger + 独立日志平台的组合,至少需要 1 小时起步。这就是“指标发现问题 → 追踪定位链路 → 日志确认根因”的可观测性黄金闭环,Tempo 把这个闭环的跳转成本降到了最低。
6.2 用 Grafana Cloud 还是自建:一次理性的选择
如果你问我 Tempo 应该自建还是直接用 Grafana Cloud,我会根据团队规模给出完全不同的答案。如果你是一个 20 人以下的团队,没有专职的运维人员,直接上 Grafana Cloud 的免费或付费方案是更明智的选择——你不需要管 Ingester 的内存、Compactor 的合并策略,只需要在界面上点几下配置数据源。
但如果你已经有比较成熟的 Kubernetes 基础设施,或者出于数据安全考虑必须私有化部署,自建 Tempo 也并非什么难事。Helm Chart 一条命令就能装起来,对象的存储配置好之后,日常维护几乎为零。我这边把 Tempo 部署在 EKS 上运行了半年,除了升级版本和调整配额,基本上没人专门盯它,和当年维护 ES 集群的精力投入完全不是一个量级。
6.3 Serverless 化的探索:对象存储作为唯一状态
最后聊一个让我觉得很有意思的方向:Tempo 的架构天然适合 Serverless 化。因为所有数据都在对象存储里,查询时按需拉取文件块,Compactor 也可以用事件驱动的方式触发。Grafana Labs 本身就在推广 Tempo 的 serverless 模式,用 Lambda 跑无状态的 Querier 和 Compactor。
这个设计对成本控制的意义在于:查询频率低的时候,你不需要为 Querier 预留常驻资源。实际场景里,大部分时间根本没人查 trace,只有出故障的那几分钟才会高频查询。用 Serverless 架构,那几分钟的冷启动和并发开销才需要付费,闲时成本趋近于零。我最近在测试环境试了一把,把 Querier 换成了 Lambda 函数,整个测试期内 Tempo 的“服务器成本”降到了几乎可以忽略不计,只有对象存储在持续产生少量费用。
不过这玩法对网络配置和权限管理有一定要求,建议先在测试环境验证,别直接上生产。如果你喜欢折腾基础设施,这会是个挺有意思的玩具;如果你只关心业务稳定性,那老老实实用常驻部署也不丢人。
根据我个人的经验,Tempo 最打动我的不是某个单独的特性,而是它“做减法”的设计哲学——砍掉索引、砍掉数据库、砍掉不必要的复杂度,把 trace 数据的存储和查询简化到对象存储这个单一依赖上。全量采样不再是一个奢侈的梦想,而是默认配置;故障定位的体验也从“大海捞针”变成了“按图索骥”。如果你正处于 Jaeger 存储成本逼着你反复调采样率的阶段,我真心建议你花一个周末的时间,把 Tempo 跑起来试试——配置一次,你会发现过去纠结的那些存储成本和采样率问题,很大概率就这么不复存在了。