资讯动态

开源可观测性栈能否替代商业APM?Prometheus+Grafana+Tempo实战选型报告

发布时间:2026/9/15 9:32:32 来源:尧图企业网站定制
在过去的三个月里我陆陆续续把手上几个核心业务系统的可观测性方案从商业 APM 迁移到了 Prometheus Grafana Tempo 这套开源组合上过程中踩了不少坑也做了一轮比较完整的对比测试。这篇文章不是给你抄作业的部署教程而是一份选型报告重点回答一个问题这套开源栈到底能不能替掉你正在付费的商业 APM先说结论能但有边界。如果你的核心诉求是链路追踪 指标监控 日志关联并且团队里有至少一个人能扛得住自建运维成本那这套组合完全可以替代商业 APM 的主体功能。如果你的业务高度依赖事务级的调用链下钻、代码级性能剖析Continuous Profiling、或者需要像 New Relic 那样开箱即用的分布式追踪不需要业务埋点那开源栈会做得比较吃力方案上有妥协空间后面我会详细讲。这篇文章主要面向正在做技术选型的后端工程师、SRE 和架构师尤其是那些觉得商业 APM 一年几十万授权费肉疼、但又担心开源方案撑不住生产环境的团队。我会把整体架构怎么搭、关键组件怎么选、埋点和采样怎么做、以及替换过程中踩过的坑全部摊开来讲尽量让你在动手之前就对成本和收益有一个清晰的判断。1. 开源可观测栈的整体架构与设计思路1.1 三大核心组件的职责边界先把这套栈的主角理清楚。Prometheus、Grafana、Tempo 三个组件在可观测性领域各自负责的东西完全不同搞混了后面配置会很难受。Prometheus 负责的是指标Metrics—— 也就是你在 Grafana 上看到的时间序列曲线、告警规则的触发数据源。它通过 Pull 模式定时抓取目标暴露的 /metrics 端点然后把数据存到自带的 TSDB 里。Prometheus 不能干链路追踪的活也别指望它去接收 Trace 数据它的存储模型是为高基数标签设计的但不是为多维度事件数据设计的。Tempo 负责的是链路追踪Tracing—— 也就是一条请求从网关到下游服务再返回数据库这一路上经过了哪些节点、每段花了多少时间。Tempo 背后的核心设计决策是它不采集数据只存储和检索数据。Trace 数据不是由 Tempo 主动抓的而是由应用里的 OpenTelemetry SDK 或者 Agent 通过 OTLP 协议推送到 Tempo 的接收端。Tempo 的存储后端既支持本地磁盘也支持 S3 兼容的对象存储这点在做生产部署时非常重要。Grafana 则是前端展示层 —— 把 Metrics 和 Tracing 统一呈现出来再加上 Loki日志、Alertmanager告警等数据源构成一个完整的可观测界面。Grafana 本身不存储任何监控数据它只是数据的搬运工和渲染器但它的插件生态和数据源接入能力是这套栈的核心粘合剂。这套组合的核心设计思路是指标、日志、链路三者分离存储但在查询层面通过标签和 Trace ID 关联起来。每个组件都做自己最擅长的事不搞大而全的 All-in-One这正是它和商业 APM 在架构理念上最大的差异。1.2 为什么选这套组合而不是别的开源方案我在选型早期其实考虑过几套替代方案包括纯 Loki Grafana 的方案把日志当作唯一的可观测性数据源、以及直接全量上 OpenTelemetry Jaeger 的方案放弃 Prometheus 的指标能力。最后敲定这套组合核心原因有三点Prometheus 是事实标准。不管你在考虑 VictoriaMetrics、Mimir 还是 ThanosPrometheus 的标签数据模型和 PromQL 查询语言都是整个开源可观测生态的地基。Grafana 几乎所有面板和告警规则都围绕这个模型设计。选择 Prometheus 本身不是因为它功能最强而是因为它生态最成熟出了问题能找到的解决方案最多。Tempo 和 Grafana 是深度集成的关系。当你从 Grafana Explore 界面向下钻一条 Trace 时Tempo 的响应能直接把每一个 Span 的时间线渲染出来还能配合服务图Service Graph展示服务之间的依赖关系。Jaeger 虽然 UI 也不差但在和 Grafana 的整合深度上明显不如 Tempo。一个典型的例子是在 Grafana 里查看指标曲线时如果发现某个服务延迟升高我可以直接切换到对应的 Trace 视图看得清清楚楚是哪一段调用变慢了这就是 Combine 之后的威力。这套方案允许你渐进式落地。先接 Prometheus 监控基础设施和业务指标成本很低再逐步接入 OpenTelemetry 埋点把关键链路的数据推到 Tempo最后再考虑 Loki 作为日志聚合层。每走一步都能产生可量化的收益不需要一次性投入大量人力。商业 APM 都是打包卖给你的装上去就得全面铺开从落地节奏上看开源方案灵活得多。1.3 商业 APM 到底贵在哪里贵得有没有道理要判断能不能替掉商业 APM首先得理解商业产品收的钱到底花在哪些地方。拆开来看商业 APM无论是 Datadog、New Relic 还是国内的一众厂商的核心价值集中在三层第一层是数据采集和自动化。商业 APM 有成熟的 Agent 生态能自动识别 Redis、MySQL、Kafka 这类常见中间件几行配置就能把链路和调用关系完整地抓出来大量场景不需要业务代码做任何改动。开源方案用的是 OpenTelemetry需要你自己在框架层和中间件层做埋点接入部分组件需要额外写 SDK 集成代码这是个不小的人力支出。第二层是数据存储和查询引擎。商业 APM 的存储层是分布式、高可用的每天几 TB 的 Trace 数据进去查询仍然能在秒级返回。开源方案从这个角度看差距比较大Tempo 在默认配置下本地磁盘存储查询超大规模数据时性能并不理想需要搭配对象存储和缓存层来做优化这块运维成本是实打实的。第三层是分析能力和服务保障。商业 APM 自带事务分组、Apdex 评分、异常检测、代码级 Profile、甚至 AI 辅助根因分析这些是开箱即用的功能。开源栈需要你通过 PromQL 自己定义业务指标通过 Grafana 自己编排面板告警规则也得手工维护。不是做不出来而是每一件事都要花时间。所以商业 APM 的定价逻辑本质上是在为工程人力买单。如果你的团队时间充裕、技术能力强开源方案完全可以支撑起这些功能如果团队连一个能全职负责可观测平台的人都没有那我建议还是继续买商业产品更划算。2. 部署方案与关键参数设计2.1 服务发现与指标采集路径设计这一章节开始进入实操层面。先看 Prometheus 侧的部署设计。生产环境我建议直接用 Kubernetes Operator 部署Prometheus Operator 能帮你在 K8s 里声明式地管理采集配置ServiceMonitor 和 PodMonitor 自定义资源避免手工维护 scrape_configs 文件导致配置漂移。核心采集路径分三块基础设施层 - 节点指标通过 node-exporter 暴露 /metrics采集 CPU、内存、磁盘、网络等主机基础指标 - K8s 控制面指标通过 kube-state-metrics 采集 Pod、Deployment、HPAs 等资源状态 - K8s 组件指标应使用 kube-prometheus-stack 自带的组件采集配置覆盖 kubelet 等控制面组件 中间件层 - MySQL部署 mysqld-exporter采集连接数、慢查询数、InnoDB 状态等 - Redis部署 redis-exporter采集命中率、内存使用、命令延迟 - Kafka部署 kafka-exporter采集消费延迟、分区状态、请求速率 业务应用层 - Java 应用micrometer-registry-prometheus 依赖 Actuator 暴露 /actuator/prometheus - Go 应用prometheus/client_golang 自带的 DefaultRegisterer暴露 /metrics - 其他任意 HTTP 服务只要按 Prometheus 文本格式暴露 metrics 端点就能被采集每个采集目标都要配置合理的scrape_interval。节点和中间件层设置 15s 就够了业务指标如果涉及到实时性要求高的告警比如 P99 延迟可以缩短到 10s但要注意采集频率越高对 Prometheus 服务器的压力越大也容易把业务应用拖慢。对于 QPS 很高的服务我习惯给它们的 /metrics 端点加一层反向代理做聚合减少 Prometheus 直接连接的并发压力。Prometheus 的存储容量估算有一个实用公式可以记一下所需磁盘空间约等于样本数/秒 × 4字节 × 保留天数 × 1.5压缩系数。比如一个每秒产生 50000 样本的 Prometheus保留 15 天大约需要 50000 × 4 × 15×24×3600 × 1.5 ≈ 4TB 左右。这只是个粗略估算实际还得看标签基数、chunk 编码效率但能帮你判断单机 TSDB 能不能扛得住。2.2 Tempo 的生产级配置与存储选型Tempo 在测试环境用单机模式跑通很容易一条 docker run 命令即可但生产环境必须做两件事启用对象存储配置合理的采样策略。我推荐的生产配置用multifactor或者基于遥测信号的采样即 Tempo 1.10 版本之后重点推荐的方式这里说明一个核心原则全量采集 Trace 数据在生产环境几乎必然把存储成本推爆必须用采样来控制数据规模。Tempo 支持 head-based sampling在应用 SDK 里控制采样比例和 tail-based sampling在数据收集端根据规则决定哪些 Trace 保留完整。我的落地经验是对于高 QPS 的无状态服务采用比例采样rate-limiting比如每个服务每秒最多发出 10 条 Trace。对于核心支付链路或者下单链路采用 Tail Sampling规则里保留错误 Trace 和慢 Trace比如超过 2 秒的请求这些才是排查问题的黄金数据。Tempo 的配置核心点在于多个 receiver 的启用。一个典型配置如下target: all http_server_address: 0.0.0.0 http_server_port: 3200 distributor: receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 storage: trace: backend: s3 s3: bucket: tempo-traces endpoint: minio.minio.svc.cluster.local:9000 access_key: tempo secret_key: tempo-secret insecure: true pool: max_workers: 100 queue_depth: 10000 wal: path: /var/tempo/wal local: path: /var/tempo/blocks这里的关键点是 S3 存储。Tempo 会把 Trace 数据先写入本地 WALWrite-Ahead Log然后异步批量写入 S3 兼容对象存储用对象存储做持久化层。MinIO 或 Ceph 集群都可以作为后端。这样设计的好处是存储容量可以靠对象存储的水平扩展来兜底不需要为 Tempo 单独维护一套存储集群。查询性能调优方面Tempo 默认支持 Configmap 里的storage.trace.s3配置建议开启cache配置配合 Redis 做索引缓存。max_bytes_per_trace建议设成 50MB能避免单条异常 Trace 把存储涨爆max_traces_per_user如果是多团队共用一套集群最好也设置配额防止一个团队把资源占完。2.3 Grafana 数据源接入与面板组织Grafana 的部署相对简单Docker 容器或者 Helm Chart 都行。接入数据源这一步有坑需要注意在 Grafana 的configuration.yaml或 datasource 环境变量里一次性把所有数据源都配好不然每次新建环境都要手工添加一遍。我在生产环境里的数据源配置有四个Prometheus默认数据源承载所有指标查询Tempo作为 Trace 数据源和 Prometheus 做衍生关联Loki可选日志数据源用于日志索引和检索CloudWatch 或阿里云监控可选用于云资源指标的统一入口Grafana 面板的组织方式我的做法是按服务维度建文件夹而不是按监控类型分。比如order-service文件夹下面放这个服务的 JVM 监控面板、Redis 调用面板、数据库连接池面板、错误率面板infra文件夹放 K8s 节点水位、Pod 资源使用率、网络吞吐这类基础设施面板。这样做的原因是排查问题时通常是以服务为出发点把和某个服务相关的所有可观测视图放在一处下钻路径最短。有一个比较实用的技巧是使用 Grafana 的变量Template Variables功能做面板的全局过滤。比如在订单服务面板里定义一个名为$instance的变量query 来自 Prometheus 的标签值然后所有依赖实例维度的面板都引用这个变量。这样切换实例时整块面板的数据会自动联动和商业 APM 中服务视图的切换体验非常接近。3. 链路追踪接入与 OpenTelemetry 实践3.1 从零到一应用埋点的方案选择很多人提到接入链路追踪第一反应就是给业务代码加注解、加拦截器、手动埋点。但 OpenTelemetry 里其实有更省力的方案——零代码接入Zero-Code Instrumentation。对 Java 应用来说最省力的方案是使用opentelemetry-javaagent。它是一个 Java Agent启动时通过-javaagent:opentelemetry-javaagent.jar指定就能自动为 Spring Boot、Dubbo、gRPC、JDBC、RestTemplate 等主流框架生成 Span。你的业务代码完全不用改动运维层面在启动命令里加一段参数就能接入。这是我强烈推荐的接入方式——接入成本极低、覆盖率极高、可回滚。唯一要注意的是 Agent 版本要和你的框架版本兼容父版本升级时在灰度环境先跑上一两天观察一下。对于 Go 应用OpenTelemetry 没有 Java Agent 这种运行时织入机制必须在代码里显式使用 SDK。但好在社区已经有非常好的 instrument 包你在main函数里初始化一个 tracer provider 之后用otelhttp包把 HTTP Server 包一层中间件就完成自动埋点了import ( go.opentelemetry.io/otel go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc go.opentelemetry.io/otel/sdk/resource semconv go.opentelemetry.io/otel/semconv/v1.21.0 otelhttp go.opentelemetry.io/otel/instrumentation/net/http ) func initTracer() (*sdktrace.TracerProvider, error) { exporter, err : otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(tempo-collector:4317), otlptracegrpc.WithInsecure(), ) if err ! nil { return nil, err } tp : sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName(order-service), )), ) otel.SetTracerProvider(tp) return tp, nil } // 在 HTTP Server 初始化时 handler : otelhttp.NewHandler(mux, order-service) http.ListenAndServe(:8080, handler)这段代码的核心是初始化一个 OTLP gRPC exporter 把 Trace 数据发送到 Tempo 的接收端然后用otelhttp.NewHandler包一层自动埋点的中间件每次 HTTP 请求进来都会自动生成一个 Span包含了 method、route、status code、duration 等关键属性。3.2 采样、Tag 与关联字段的完整实践接入埋点之后真正的难点在于采样策略和数据质量管理。采样策略方面对于 Java Agent 接入的应用我建议先在环境变量里配置最低限度的采样率确保数据量可控OTEL_TRACES_SAMPLERparentbased_traceidratio OTEL_TRACES_SAMPLER_ARG0.1这里的设计思想是parentbased_traceidratio—— 只要父 Span 被采样了子 Span 不管自己的采样率是多少都会跟随父 Span 一起采样这保证了整条 Trace 的完整性不会出现同一请求的 Span 在存储里缺胳膊少腿的情况。生产环境的建议配置是 10% 起步等 Tempo 集群稳定后再根据存储成本和查询速度逐步调整到 20% 或 30%。对于错误请求和慢请求需要通过 Tail Sampling 单独保 100% 采样否则线上出故障时你想查的那条 Trace 刚好没存下来会非常被动。Tag 和数据质量是另一个重要问题。OpenTelemetry 默认生成的 Span 属性其实比较精简为了在排查问题时能快速缩小范围我建议在业务的关键入口统一注入一组资源属性deployment.environment区分生产/预发/测试环境service.version服务版本号发布新版本后能快速区分是回归问题还是新引入问题host.ip和k8s.pod.name定位到具体的物理机/容器自定义业务标签比如order.id、user.id、payment.channel方便在 Trace 查询时按业务维度过滤这些 Tag 在 Tempo 里叫Resource和Span Attributes。配置得当的话你在 Grafana 的 Tempo 数据源查询框里按service.nameorder-service order.id12345就能直接定位到一整条业务链路上的所有请求这种粒度是纯日志方案很难达到的。3.3 服务拓扑依赖和错误注入的观测判断Tempo 的一个隐藏功能是 Service Graph也就是服务间的拓扑依赖关系图。这个功能默认关闭需要显式在 Tempo 的配置里开启。开启之后Tempo 会自动从 Span 数据中提取 service name 之间的调用关系在 Grafana 的 Tempo 数据源面板下选择 Service Graphs就能看到整个微服务架构的流量拓扑。拓扑图的价值在于两个场景一是架构梳理当你接手一套没有文档的微服务系统时拓扑图可以直接告诉你服务之间的真实调用关系省去了看代码梳理依赖的时间二是故障影响面评估某个服务出现异常时拓扑图上能一眼看出哪些下游服务会被牵连这对于告警时的优先级排序很有帮助。我踩过的一个坑是Service Graph 依赖 rpc 的span.kind属性来判断调用方向如果埋点的 SDK 版本太老或者配置不完整会看到拓扑图上出现一堆不明方向的无指向性连线。排查方式是在 Tempo 的指标数据里检查traces_service_graph_request_total这个指标是否正常如果不增长多半是埋点的问题。4. 告警、日志与接口联动的一体化设计4.1 基于 Prometheus 的告警规则设计与 Alertmanager 集成Prometheus 的告警能力是整个开源栈里最成熟的但要让告警真正好用规则设计得花心思。我分享一下自己在生产环境的告警规则分类法第一类是直接告警规则用 PromQL 定义阈值即可。比如订单服务错误率groups: - name: order-service-alerts rules: - alert: OrderServiceHighErrorRate expr: | sum( rate(http_server_requests_seconds_count{serviceorder-service, status~5..}[5m]) ) / sum( rate(http_server_seconds_count{serviceorder-service}[5m]) ) 0.01 for: 5m labels: severity: critical annotations: summary: 订单服务错误率超过1% description: 订单服务最近5分钟错误率已达到 {{ $value | humanizePercentage }}这里最值得注意的参数是for: 5m。它的作用是只有当这个告警条件连续持续 5 分钟才会触发告警通知。为什么要设置这个值因为线上很多瞬间的抖动比如一次突发流量、一次网络抖动、一次发布过程中的重启都会让错误率瞬时超过阈值但这种抖动通常几十秒就恢复了完全不需要打扰值班人员。for的存在是对告警信号进行一个去抖滤波大幅减少无效告警。第二类是趋势类告警针对慢速劣化问题。比如磁盘使用率增长趋势分析predict_linear(node_filesystem_avail_bytes{fstype!~tmpfs|overlay}[6h], 24*3600) 0这个公式的意思是基于过去 6 小时的磁盘可用空间变化趋势预测未来 24 小时后是否会用完。能提前一天发现磁盘即将写满的问题比定值的 80% 阈值告警更有价值。Alertmanager 的集成是告警体系的终点。配置上要重点留意两个点一是告警聚合策略把相似的通知合到一条消息里发出来二是路由分组把同一服务的告警放到一个 Group 中避免一个故障触发十几条微信 / 邮件轰炸。如果想接企微或钉钉在 Alertmanager 的 webhook 配置里写一个转发函数即可社区有很多现成实现可以抄。4.2 Loki 接入日志补全可观测栈的最后一环既然指标和链路都有了日志这环也得补上。Loki 和 Prometheus 的架构非常相似都是 pull 标签索引但 Loki 不解析日志内容只做索引和聚合因此存储成本比 Elasticsearch 低很多。接入方式推荐使用 Grafana Alloy 作为日志采集端。Alloy 是 Grafana Labs 推出的统一采集器支持从 Kubernetes、Docker 或者文件系统采集日志并自动加上 K8s 标签namespace、pod、container等。一个最小可用的 Alloy 配置大概长这样lokiwrite default { endpoint { url http://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push } } discovery.kubernetes pods { role pod } discovery.relabel pods { targets discovery.kubernetes.pods.targets rule { source_labels [__meta_kubernetes_pod_label_app] regex (.*) target_label app } } loki.source.kubernetes default { targets discovery.relabel.pods.output forward_to [lokiwrite.default.receiver] }这套是 Alloy 的标准写法核心思路是通过 K8s 服务发现找到所有 Pod然后采集容器日志并附加 Pod 标签推送到 Loki。Loki 接入之后可观测栈的查询链路就完整了你在 Grafana 中看到某个服务错误率升高 → 点击对应的指标面板右上角 → 选择“关联的日志”直接跳转到 Loki 的查询页面按apporder-service和status500过滤出所有错误日志 → 再从日志条目中复制 Trace ID → 在 Tempo 数据源里打开对应的 Trace完整链路一目了然。这个流程非常接近商业 APM 的“一键跳转关联数据”体验只是需要你手动多点击几次。4.3 指标-链路-日志三者的 Trace ID 关联实践要实现前面说的指标 → 日志 → 链路跳转关键点在于统一 Trace ID 的传递链路。从 OpenTelemetry 的设计来看Trace ID 是通过 HTTP Headertraceparent在服务间传递的。如果你的每个服务都通过 OpenTelemetry SDK 接入上游服务生成的 Trace ID 会自动通过traceparent头传到下游服务全程数据的关联是自动完成的。但日志这边就没那么自动了。应用打印日志时默认并不知道当前请求属于哪个 Trace ID需要在日志输出格式里强制带上这个字段。以 Java 的 logback 为例你需要一个 Tracing MDC 注入机制pattern %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [trace_id%X{trace_id} span_id%X{span_id}] - %msg%n /pattern这样每条日志都会带上trace_id和span_idLoki 里查询的时候可以直接通过这个字段过滤出整个请求链路产生的所有日志。我曾经在线上一台 Redis 故障时用这个能力把出问题的所有请求日志全部筛了出来然后一条条对过去快速确认了慢请求的根因效率比纯看指标排查高得多。需要注意的是OpenTelemetry 的 Java Agent 会自动把 Trace ID 注入到 MDC 里字段名分别是trace_id和span_id但 Go 应用的日志埋点需要自己从 context 里取trace.SpanFromContext(ctx).SpanContext().TraceID()再手动附加到日志字段这块没有现成的自动能力。5. 替换后的对比实测结果5.1 核心指标对比性能开销、可观测性覆盖度、成本做完整套迁移之后我对这套开源栈和商业 APM 做了一轮平行对比测试用同一套业务应用分别接入商业 APM Agent 和开源埋点对比维度包括性能开销、功能覆盖度、查询延迟和成本。性能开销方面我在订单服务上用 JMeter 压了 300 并发持续 10 分钟测到的数据如下指标商业 APM AgentOpenTelemetry Java Agent Tempo差异单请求 P99 延迟增幅7.8%5.2%开源方案优CPU 使用率增幅6.5%4.1%开源方案优JVM 内存额外开销512MB210MB开源方案优这一轮对比里开源方案反而更轻量原因在于商业 APM 的 Agent 默认会采集大量数据事务分组、调用链完整保留、内存 profiling 等内存开销自然更高。OpenTelemetry Java Agent 因为你没有强制开启 profiling默认只做链路采集所以开销反而小。查询性能方面差距明显。商业 APM 的 Traces 查询接口基本都是毫秒级响应Tempo 在本地磁盘存储模式下查询一小时前的数据还能接受但如果跨天查询带复杂条件响应时间会跑到 3~8 秒。能达到的优化极限是配合对象存储和缓存之后做到 2 秒内返回。如果你的场景是那种“误伤了某个大促流量要快速回溯过去 12 小时所有慢请求”的查询商业产品的体验明显更流畅。5.2 功能覆盖度差异详解哪些能替代哪些只能凑合我把日常使用商业 APM 的功能全部列出来做了逐项对比功能模块商业 APM开源组合替代程度指标监控与自定义指标自带Prometheus PromQL完全替代链路追踪与拓扑自带Tempo Service Graph完全替代日志聚合自带或集成Loki Alloy完全替代分布式追踪自动埋点全自动 AgentOpenTelemetry Java AgentJava自动Go需手工配置90% 替代告警与通知内置告警引擎Alertmanager完全替代代码级性能剖析自带 Continuous ProfilingPyroscope可集成到 Grafana70% 替代事务级 Apdex 评分自动计算需用 PromQL 自定义计算60% 替代AI 异常检测与根因分析自带需要自己写规则或接入第三方案件30% 替代云服务集成AWS/Azure/阿里云开箱即用需手动配置 exporter50% 替代这部分结果可以总结成三句话统计型指标和链路追踪开源组合已经可以做到完全替代日志聚合替代也没有问题有偏差的是需要深度分析能力的部分代码级 profile、Apdex 评分、异常检测以及云资源的自动化接入这些需要额外的搭建成本和工程投入。5.3 三个“高危场景”实测这些情况别急着替换我实测过程中遇到三个“高危场景”如果你刚好处于这些情况我建议你谨慎考虑替换第一个场景是高并发批处理服务。比如每日几亿条消息的 Kafka 消费组这类服务的 QPS 极高、Span 数量巨大如果采样率设置不当Tempo 的 WAL 和存储层会迅速被写爆。我踩过的坑是刚开始没做限流直接把全量 Trace 推到 Tempo结果 MinIO 集群的带宽被打满连正常的日志查询都受影响。解决方案是必须做严格的头采样 尾采样的双重限流并设置 Tempo 的分发器限流和超时。第二个场景是多团队共用一套监控平台。商业 APM 的多租户隔离做得很完善A 团队的数据完全不会影响 B 团队也不会出现 B 团队的大查询占满查询资源导致 A 团队卡死的情况。Tempo 虽然支持租户tenant隔离但配置复杂度高默认还是单租户模式。如果有超过三个团队共用强烈建议为每个团队拆一套独立的 Tempo 实例否则查询体验会相互污染。第三个场景是低频调用链下的深度排查。如果你负责的系统是一个低频交易系统比如金融风控每天只有几千请求链路数据本身就少采样率可以设置到 100%倒是问题不大。但如果你的系统是高频互联网 API每秒上千请求想要通过 Trace 查询定位某个特定用户 ID 或特定订单的调用链那对存储和查询性能的要求就很高了。我在实测中对某高频服务做 ID 级链路检索Tempo 的查询响应时间从 800ms 直接飙到 7 秒以上这种场景商业 APM 是有更大优势的有专门的索引和全文检索优化。6. 常见问题与故障排查技巧实录6.1 链路缺失Trace 在 Tempo 里查不到这是接入过程中最常遇到也是排查成本最高的一类问题。我总结了三种典型情况第一种是应用侧数据压根没发出来。排查思路在应用里打开 OpenTelemetry 的调试日志或者直接检查 Tempo 的 distributor 端口 4317 是否收到了 gRPC 连接。如果应用日志里出现了“Failed to export traces”之类的错误99% 是网络不通或者 Tempo 地址配置错误。用 telnet 或 nc 测试一下连通性最直接。第二种是数据发出去了但没写进存储。这种情况 Tempo 本身不会报错因为 Tempo 是异步批量写存储的。你可以在 Tempo 的 metrics 端点prometheus 格式上查tempo_distributor_spans_received_total和tempo_ingester_traces_created_total这两个指标前者代表收到的 Span 量后者代表实际创建并写入的 Trace 数量。如果前者在增长但后者不长基本可以确定写入环节出了问题优先检查对象存储的权限和网络。第三种是勉强写进去了但查询不到。多发生在 S3 存储刚配置完时因为 Tempo 默认的查询通过索引扫描块文件索引更新的延迟可能长达几分钟。新写完的 Trace 查不到是正常现象不用慌等几分钟再查。如果一直查不到就到 Tempo 的 admin 页面检查blocks的状态。6.2 数据量大导致 Tempo 存储膨胀的治理Tempo 存的数据量失控是生产环境最常见也是危害最大的问题。存储一旦膨胀查询变慢、成本飙升运维会非常难受。我的治理策略集中在三个方向采样限制是第一道闸门。在应用侧把采样率从 100% 降到合理的比例一般 10%~20%能立刻让数据量下降一个数量级。在 Tempo 侧重有三重防护distributor 的max_bytes_per_trace限制单条 Trace 大小ingester 的max_traces_per_user限制用户配额compactor 侧设置保留周期存 7 天或 15 天自动清理老数据。第二个方向是调整粒度。在 OpenTelemetry SDK 里对不必要的 Span 直接禁用比如健康检查接口、Metrics 接口、静态资源请求这些噪音数据在采样之前就把它过滤掉能省下大笔存储。在 Java Agent 里可以通过otel.instrumentation.http.capture-http-server-headers这样的配置控制采集范围。第三个方向是升级存储层。Tempo 的本地磁盘存储是最容易爆的生产环境务必切换到 S3 缓存。事件数据的压缩比例很高切到 S3 之后每一 TB 原始数据实际落盘的存储体积只有 300~400GB成本和容量压力都大幅下降。6.3 Grafana 面板和告警接口的迁移体验从商业 APM 的看板切换到 Grafana迁移过程中最大的痛苦点在于面板的复刻。商业 APM 自带一堆开箱即用的标准面板服务总览、依赖拓扑、Apdex 分布、错误率 TOP N 等这些在 Grafana 里都需要你用 PromQL 手工逐步构建。我的建议是不要追求一次性复刻所有面板。迁移的第一个月我优先构建了四个核心面板服务可用性总览HTTP 状态码分布 错误率 P99 延迟、中间件健康度Redis/MySQL/Kafka 的连接数和延迟、基础设施水位CPU/内存/磁盘/网络、告警规则聚合视图。这四个面板覆盖了平时 80% 的排查场景。剩下的功能面板比如 SQL 慢查询分析、JVM GC 详情等在故障真正发生时再按需补全。关于 Grafana 告警接口的对接你可以在 Grafana 的 Alerting 模块里直接配置通知渠道也可以让 Prometheus 的 Alertmanager 作为统一出口。我的做法是Prometheus 负责规则计算Alertmanager 负责路由和通知Grafana 只负责展示告警历史各司其职避免告警逻辑耦合在 UI 层。整体来说从商业 APM 迁移到这套开源栈我真实感受到的边界在于埋点和数据源配置全面铺开的成本大多集中在一个月的迁移周期迁移之后持续的成本主要是存储和日常运维。如果你能接受这个前提开源栈完全值得一试。最后分享一个从实际运维中总结的经验这套组合在定位线上问题时价值其实比商业 APM 更容易被低估。因为 Prometheus 的指标查询完全按照你的业务逻辑定制PromQL 的灵活性比商业 APM 内置查询器高出一截。当出现一次复杂故障时比如数据库连接池打满、下游依赖延迟抖动、热点数据导致缓存穿透我用 PromQL 写几个临时查询再用 Trace 把关键请求串起来往往十几分钟就能定位到根因。复盘时想想这个效率比过去在商业 APM 里点几百次菜单的体验反而更快。如果你正在为商业 APM 的高额授权费发愁且团队有人能扛住这套自建体系可以放心迈出这一步。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价