☰
基于OpenTelemetry与Prometheus构建全链路可观测性实践
2026/10/6 4:53:04 网站建设 项目流程

某个周六凌晨两点,值班群里的消息把我从睡梦中叫醒:下单接口的失败率从 0.2% 一路飙到 35%。我打开公司那套用了多年的监控看板,CPU、内存、JVM 堆、磁盘占用全是绿的,唯独看不到那条业务链路到底卡在哪个环节。那一刻我意识到,传统按资源维度做的监控,根本回答不了“这一次请求为什么慢”。

后来我们把 OpenTelemetry 铺到所有服务里,用 Prometheus 收指标,Grafana 做可视化,Loki 合日志,Tempo 存链路,这才算把全链路可观测性真正落地。这篇内容把这套组合从 0 到可上线的完整过程讲一遍,后面提到的所有配置和步骤,都是我实际跑过的方案。

如果你正在为线上故障排查发愁,或者刚接手微服务监控,想搭一套“指标、日志、链路”都不偏科的体系,这篇文章可以直接当操作手册用。我会按项目推进的顺序,把每一步为什么这么做、踩过什么坑、怎么排查都交代清楚,尽量让只接触过单机监控的人也能照着做下来。

1. 先理清楚:全链路可观测性到底要解决什么问题

1.1 传统监控到底缺在哪

我见过不少团队的监控体系,表面上什么都有:Zabbix 看机器资源,Prometheus 看容器指标,Elasticsearch 收应用日志,甚至每个中间件还有自己的一套控制台。但真出问题的时候,这些数据是割裂的。

举个例子。一次支付回调突然大量超时,资源监控显示所有节点都很健康,日志里只有零星几条Connection reset,因为没有统一的 trace_id,你不知道一条请求是从哪里开始变慢的,是网关、Redis、下游订单服务,还是某个第三方接口。你只能一台一台登录服务器,在日志文件里 grep,运气好十几分钟找到一条报错,运气不好崩到周末天亮。

传统监控的核心问题在于:它站在“单机/单组件”的视角看问题,而现代故障是“链路”上的故障。一台服务器掉没掉、磁盘满没满,这些当然重要,但业务问题的根因往往藏在跨服务调用链上,藏在某一次请求的耗时分布里。要回答这类问题,必须有全链路的视角。

1.2 可观测性三支柱:指标、日志、链路

行业里把可观测性拆成三个信号:Metrics、Logs、Traces,也就是指标、日志、链路。这三者解决的问题不一样,但组合起来就能还原一次故障的全貌。

信号代表工具回答的问题典型场景
指标 MetricsPrometheus系统现在“正不正常”错误率升高、CPU 飙升、队列积压
日志 LogsLoki系统里“发生了什么”具体报错、异常堆栈、业务日志
链路 TracesTempo一次请求“经历了什么”哪个服务慢、哪次调用失败、耗时分布

指标适合触发告警,日志适合事后排查,链路适合定位瓶颈。三者单独用都有盲区,连在一起才是可观测性。这也是为什么现在大家普遍接受“三支柱”的说法:缺了任何一个,故障定位都会像少了一条腿。

1.3 为什么是这五件套,而不是全家桶

确定技术选型时,我们其实对比过几套方案。Elastic Stack 全家桶功能确实全,但资源占用高,日志冷热存储做起来不轻量;SkyWalking 的 APM 很强,但指标和日志能力相对弱,和 Grafana 生态打通也不顺畅;商用 APM 像 Datadog 体验最好,但费用对大团队来说非常敏感。

最后选定 OpenTelemetry + Prometheus + Grafana + Loki + Tempo,主要是三个理由:

第一,全部开源,且都是 CNCF 孵化的项目,社区活跃度足够,不会出现“作者不维护了”的断档风险。第二,Grafana 作为统一入口,同时接 Prometheus、Loki、Tempo,三个信号在同一个界面上联动,符号负担小。第三,OpenTelemetry 是事实上的可观测性数据标准,以后就算后端换成别的存储,SDK 侧不用重写。

这套组合不是“最强”的,但这个阶段对我们来说,平衡性是最好的。

2. 环境准备与组件选型

2.1 最小可上线组件清单

动手之前,我先列一下需要部署的组件和端口规划。端口规划很重要,因为 OpenTelemetry Collector、Tempo、Prometheus 这些组件都占用固定端口,踩坑多数是从端口冲突开始的。

组件角色关键端口建议版本
OpenTelemetry Collector数据接入与转发4317 gRPC,4318 HTTPotelcol-contrib 0.104.0 左右
Prometheus指标存储与查询9090v2.53.0
Grafana统一可视化300011.1.0
Loki日志存储与查询31003.1.0
Tempo链路存储与查询3200 查询,4317 gRPC 接收2.5.0

注意:Tempo 也支持用 OTLP 接收 trace,默认端口同样是 4317/4318。如果在同一台机器上和 Collector 一起跑,端口必须错开。我的做法是 Collector 对外占用 4317,Tempo 的接收端口对外映射成 4320/4321,容器内部仍然走标准端口。

版本这块我建议直接参考官方最新稳定版,别用太旧的。OTLP 协议本身向后兼容,但 Collector 组件、Grafana 数据源插件更新速度快,老版本容易出现配置格式不兼容的问题。

2.2 Prometheus 和 Grafana 的安装启动

先做最简单的单机安装,不需要一开始就上 K8s,否则排查问题时维度太多。Prometheus 官方镜像启动方式很简单:

docker run -d --name prometheus \ -p 9090:9090 \ -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \ prom/prometheus:v2.53.0

启动后访问http://localhost:9090/targets,就能看到 Prometheus 当前抓取的所有目标状态。这一步是后续所有指标能否入库的关键:只要 target 显示 UP,说明抓取链路没问题。

Grafana 的安装启动同样一行命令:

docker run -d --name grafana \ -p 3000:3000 \ grafana/grafana:11.1.0

默认账号密码是 admin/admin,第一次登录会强制改密码。Grafana 本身不存监控数据,它更像一个统一前端,通过数据源插件去读 Prometheus、Loki、Tempo。所以安装 Grafana 只是第一步,真正重要的是后面的数据源配置。

刚开始调试的时候,建议先用docker logs -f grafana把日志挂起来,Grafana 启动失败多半是权限或配置问题,日志里会有明确提示。

2.3 Loki 与 Tempo 的最小部署

Loki 推荐用单二进制模式跑,也就是一个 Loki 进程同时承担读写和查询。虽然官方也提供了读写分离的微服务架构,但那是数据量大到一定程度才需要考虑的事。最小可用配置如下:

docker run -d --name loki \ -p 3100:3100 \ -v $(pwd)/loki-config.yaml:/etc/loki/loki-config.yaml \ grafana/loki:3.1.0 \ -config.file=/etc/loki/loki-config.yaml

Loki 3.x 的默认配置里一般会开启本地文件系统存储,不需要额外依赖对象存储。数据量可控的中小团队,先跑单实例完全够用。

Tempo 的部署方式和 Loki 很像,我可以先用单实例本地存储跑通流程,存储目录挂一个本地 volume 就行。

docker run -d --name tempo \ -p 3200:3200 \ -p 4320:4317 \ -v $(pwd)/tempo-config.yaml:/etc/tempo/tempo-config.yaml \ grafana/tempo:2.5.0 \ -config.file=/etc/tempo/tempo-config.yaml

Tempo 会把接收到的 trace 数据写入本地磁盘,查询时通过 3200 端口供 Grafana 读取。正式上线时,本地磁盘存储肯定不够,要换成 S3/GCS/对象存储,但那是后话。第一版先把链路跑通,比什么都重要。

2.4 存储与资源怎么估

很多团队部署完才发现磁盘不够,因为 trace 和日志的数据增长是“非线性”的。我习惯在动手前做一个粗暴估算,避免后面被存储打脸。

假设一个订单系统每天有 3000 万次请求,trace 采样率 10%,也就是 300 万 trace 入库。一条 trace 平均 8 个 span,每个 span 编码后大约 1KB,压缩比按 5 倍算,一天的 trace 存储量大概是 300 万 × 8 × 1KB / 5 ≈ 4.8GB。日志按每请求 200 字节、全量采集计算,一天 3000 万 × 200B ≈ 6GB,压缩后约 2GB。Prometheus 的指标数据量相对小,一台 40 节点规模的集群,默认 15 秒抓取间隔,大约一天 1GB 左右。

这样算下来,如果保留 15 天,大约需要 120GB 到 150GB 的存储空间。别只看单机磁盘,容器 volume 的容量要提前规划好。

数据单日估算15 天保留建议存储
Trace(采样 10%)约 5GB75GB本地盘/对象存储
日志全量约 2GB(压缩)30GB本地盘
指标约 1GB15GBPrometheus 本地 TSDB

3. 用 OpenTelemetry 把数据接进来

3.1 为什么要塞一个 Collector

初学 OpenTelemetry 的人最容易问:应用 SDK 直接发给 Prometheus、Loki、Tempo 不就行了,为什么非要中间再加一个 Collector?

直接通确实能跑通,但上了规模就会出现问题。第一,你不可能让所有开发都记住三个后端的地址和认证信息,一次后端迁移就要改所有服务配置。第二,指标数据需要做聚合和单位转换,日志需要做脱敏和过滤,trace 需要做采样,这些集中处理放在 SDK 里不合适。第三,多语言团队每个语言的 SDK 都要维护一套导出逻辑,工作量翻好几倍。

Collector 的存在,就是把“数据生产”和“数据消费”解耦。应用只需要把 OTLP 数据发给 Collector,后面的路由、过滤、采样、转换都由 Collector 统一处理。你可以把它理解成物流中转站:商家只管把货发到中转站,具体怎么分拣、发往哪些城市,是中转站的事。

3.2 应用侧接入:Agent 和 SDK 怎么选

OpenTelemetry 接入方式有两类,一类是自动插桩,一类是手动 SDK 集成。

Java 服务最推荐自动插桩,因为 Java Agent 可以在不改业务代码的情况下,自动捕获 HTTP 调用、数据库操作、日志上下文。启动命令只需要加一段 javaagent:

java \ -javaagent:opentelemetry-javaagent.jar \ -Dotel.service.name=order-service \ -Dotel.exporter.otlp.endpoint=http://127.0.0.1:4317 \ -Dotel.metrics.exporter=otlp \ -Dotel.logs.exporter=otlp \ -jar app.jar

这段配置做完,服务启动后就会自动上报 metrics、logs、traces 三类数据,对开发人员几乎零侵入。我实际工作中接触到的大部分 Java 后端,都能靠这种方式接入,落地阻力最小。

Node.js、Python、Go 这类服务,自动插桩能力相对弱一些,通常需要自己在程序入口初始化 SDK,比如 Node.js 项目:

const { NodeSDK } = require('@opentelemetry/sdk-node'); const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); const sdk = new NodeSDK({ serviceName: 'cart-service', traceExporter: { url: 'http://collector:4317' }, metricExporter: { url: 'http://collector:4317' }, instrumentations: [getNodeAutoInstrumentations()] }); sdk.start();

我的经验是:老服务能用 Agent 就用 Agent,新服务在写代码时直接引入 SDK,从源头就把可观测性当成业务的一部分来做。

3.3 核心配置:OTLP Pipeline 分流

Collector 的配置是整个链路的核心。它定义了接收什么、怎么处理、发给谁。我这份配置实现了最简单的三类数据分流:

receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: batch: timeout: 5s memory_limiter: check_interval: 1s limit_percentage: 80 spike_limit_percentage: 10 exporters: otlp/tempo: endpoint: tempo:4317 tls: insecure: true prometheus: endpoint: 0.0.0.0:8889 loki: endpoint: http://loki:3100/loki/api/v1/push service: pipelines: traces: receivers: [otlp] processors: [memory_limiter, batch] exporters: [otlp/tempo] metrics: receivers: [otlp] processors: [memory_limiter, batch] exporters: [prometheus] logs: receivers: [otlp] processors: [memory_limiter, batch] exporters: [loki]

关键点有两个。一是memory_limiter必须放在最前面,防止高并发时 Collector 自身内存被打爆。二是batch处理器一定要开,它会攒一批数据再统一转发,大幅降低网络开销和后端压力。

metrics 的导出稍微特殊一点。Collector 会暴露一个0.0.0.0:8889/metrics的 HTTP 接口,Prometheus 从这个接口抓取指标。logs 则通过 Collector 的 Loki exporter 直接推送到 Loki 的 push API。实际配置时把这份 yaml 挂载到容器/etc/otelcol/config.yaml即可。

3.4 Trace 上下文如何跨服务串起来

打通链路的关键,在于 trace_id 能不能在服务间自动传递。OpenTelemetry 使用的是 W3C Trace Context 标准,一套 trace 会有唯一 ID,通过 HTTP Header 的traceparent字段在服务间传递。

如果你用的是自动插桩,HTTP 客户端和服务端都会自动注入和解析这个 Header,链路天然就是串起来的。比如前端调用网关,网关调用订单服务,订单服务调用库存服务,只要每个服务都接入了 OTel,整条调用链就会自动串成一个 trace。

但有些老项目用的是自研 RPC 框架,不支持标准传播,这时候就需要在框架的调用上下文里手动放 trace id。我的做法是写一个简单的 filter 或拦截器,从请求里读出traceparent,放进自研框架的调用上下文;响应时再把 trace id 原样传回去。手动接入确实花时间,但一旦串起来,排查效率提升是肉眼可见的。

4. 核心配置:从采集到展示的关键细节

4.1 Prometheus 抓取规则:让指标找到家

Prometheus 是一个拉模式的监控系统,它主动去被监控端点拉取指标。在我这套架构里,Prometheus 抓取的是 Collector 暴露出来的:8889/metrics接口。最小配置文件如下:

global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: otel-collector static_configs: - targets: ['otel-collector:8889'] labels: instance: on-prem

这里有个容易忽略的细节。scrape_interval决定指标采集的频率,15 秒是默认值,日志显示误差率异常的时候,你最多会延迟 15 秒发现。如果是核心链路,可以单独建一个 job,把抓取间隔调到 5 秒。

Prometheus 的 target 状态页会直接显示每个 job 的健康状况。看到 DOWN 的时候,先检查网络连通性,再检查被抓取端点的/metrics接口是否能访问。我踩过的坑是容器编排把 Collector 的 IP 变了,targets 里还写的是旧 IP,导致指标抓不到。现在都用服务名做 target,IP 变化就不影响了。

4.2 告警规则和 Recording Rules

指标采集回来只是开始,可观测性要起作用,必须有告警。Prometheus 的告警规则用 PromQL 编写,比如监控下单接口 5 分钟错误率超过 10%:

groups: - name: business-alerts rules: - alert: OrderServiceHighErrorRate expr: | sum(rate(http_server_duration_seconds_count{status_code=~"5..",service_name="order-service"}[5m])) / sum(rate(http_server_duration_seconds_count{service_name="order-service"}[5m])) > 0.1 for: 5m labels: severity: page annotations: summary: "订单服务错误率超过 10%"

for: 5m表示错误率持续 5 分钟才触发,避免瞬时抖动导致告警轰炸。这里要注意:表达式里的标签必须是真实存在的指标标签,OpenTelemetry 的语义约定会自动带上service_name、status_code等字段,Prometheus 收到后可以直接用。

Recording Rules 适合把耗时的查询预先算好。比如每条业务线都要看翻倍成功率,每次都实时计算会加重压力,提前算成新指标会快很多。

- record: order:http_server_errors:ratio_5m expr: | sum(rate(http_server_duration_seconds_count{status_code=~"5..",service_name="order-service"}[5m])) / sum(rate(http_server_duration_seconds_count{service_name="order-service"}[5m])) - record: order:http_server_errors:ratio_30m expr: | sum(rate(http_server_duration_seconds_count{status_code=~"5..",service_name="order-service"}[30m])) / sum(rate(http_server_duration_seconds_count{service_name="order-service"}[30m]))

Recording Rules 的价值在于把“常用查询”变成“固定指标”,告警规则直接引用order:http_server_errors:ratio_5m > 0.1就行。否则每个告警都要从原始数据开始算,规则一多 Prometheus 的计算压力会很大。

4.3 Grafana 数据源配置与使用教程

Grafana 安装起来快,但真正让它发挥作用的是数据源配置。登录后,在左侧菜单点 Connections → Data sources → Add data source,分别添加 Prometheus、Loki、Tempo。

Prometheus 数据源填 URLhttp://prometheus:9090,Loki 填http://loki:3100,Tempo 填http://tempo:3200。三个数据源都提示绿色的“Data source is working”才算连上。

接下来是仪表盘。很多人问 Grafana 有没有现成的模板,答案是有的。Grafana 官网有 Dashboards 市场,社区维护了大量优秀的仪表盘,只需要导入模板 ID。比如 Java Spring Boot 服务直接用 19004,JVM 通用模板用 4701,这些模板会基于 Prometheus 里的指标自动渲染面板。输入模板 ID,再选择数据源,一份看起来相当专业的监控大屏就出来了。

如果你用的是 OpenTelemetry 默认的指标名,模板里的变量可能对不上,需要自己调整面板的查询。我的建议是先把 Prometheus 的 Explore 页面打开,用 PromQL 跑一遍指标,确认字段名存在再编辑面板。否则面板上全是“No data”。

4.4 Loki 标签设计与 LogQL

Loki 和 Prometheus 的数据模型非常像,都是通过标签来索引。但 Loki 的标签设计有个核心原则:标签越少越好,因为每个标签组合都对应一份索引,标签基数太高,查询和写入都会变慢。

我推荐的日志标签组合是固定的:service_name、level、trace_id。service_name用来筛选服务,level用来过滤错误级别,trace_id用来关联链路。业务字段不要都做成标签,它们应该留在日志文本里,通过 LogQL 的过滤器查询。

{service_name="order-service", level="error"} |= "order_timeout" != "batch"

这条查询的意思是:在 order-service 的错误日志里,找出包含order_timeout但不包含batch的日志行。LogQL 的|=表示包含,!=表示排除,文本型日志在查询时才做过滤,存储上不会额外建索引,性能影响可控。

日志里一定要打印 trace_id。我见过太多团队日志规范里没有这一项,结果链路和日志各查各的,白白浪费了所有接入工作。

4.5 Tempo 链路查询与三信号联动

Tempo 接好后,Grafana Explore 的数据源切到 Tempo,就能按服务名搜索 trace。Tempo 2.x 支持 TraceQL,这是一种专门查链路的查询语言,比如查 order-service 里耗时超过 200ms 的 span:

{ resource.service.name = "order-service" } && { duration > 200ms }

链路要和日志联动,必须在 Grafana 的 Tempo 数据源里配置 Trace to logs。我建议的配置是:在 Tempo 数据源设置里,把“Trace to logs”指向 Loki 数据源,Tag 映射里写service_name、trace_id,查询语句写成{service_name="${__tags.service_name}"} |= "${__trace.trace_id}"。这样做之后,从链路视图点一个 span,就能直接跳转到该 trace 对应的日志列表,反之从日志里的 trace_id 也能跳到链路详情。

这套联动就是全链路可观测性真正值钱的地方:一条请求从入口到出口,指标告诉你哪里变慢,链路告诉你哪一跳变慢,日志告诉你那一步为什么变慢。

5. 实操过程:一次从 0 到可上线的完整演练

5.1 用 docker-compose 拉起整套监控栈

下面是一份可以直接拿来跑通全部流程的 docker-compose 文件。我会把每一层都注释清楚,方便你按需裁剪。

services: otel-collector: image: otel/opentelemetry-collector-contrib:0.104.0 command: ["--config=/etc/otelcol/config.yaml"] volumes: - ./otelcol/config.yaml:/etc/otelcol/config.yaml ports: - "4317:4317" - "8889:8889" depends_on: - loki - tempo prometheus: image: prom/prometheus:v2.53.0 command: - --config.file=/etc/prometheus/prometheus.yml - --storage.tsdb.retention.time=15d volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml ports: - "9090:9090" grafana: image: grafana/grafana:11.1.0 environment: - GF_AUTH_ANONYMOUS_ENABLED=true - GF_SECURITY_ADMIN_PASSWORD=admin ports: - "3000:3000" loki: image: grafana/loki:3.1.0 command: ["-config.file=/etc/loki/local-config.yaml"] volumes: - ./loki/local-config.yaml:/etc/loki/local-config.yaml ports: - "3100:3100" tempo: image: grafana/tempo:2.5.0 command: ["-config.file=/etc/tempo/config.yaml"] volumes: - ./tempo/config.yaml:/etc/tempo/config.yaml ports: - "3200:3200" - "4320:4317"

这份编排里,Grafana 开了匿名访问,方便本地测试;正式环境一定要关掉匿名,并用配置文件管理登录认证。启动命令是docker compose up -d,全部状态正常后,端口 3000、9090、3100、3200 都能访问。

5.2 跑一个接入 OTel 的 Demo 服务

整个链路最核心的验证步骤,是让一个真实应用把数据发过来。我们可以用一个简单的 Java 服务演示,就是前面提到的那种启动方式。先下载 OpenTelemetry Java Agent,然后启动任意 Spring Boot 应用:

java \ -javaagent:opentelemetry-javaagent.jar \ -Dotel.service.name=demo-service \ -Dotel.exporter.otlp.endpoint=http://localhost:4317 \ -Dotel.metrics.exporter=otlp \ -Dotel.logs.exporter=otlp \ -jar demo.jar

启动后随便访问几次应用接口。只要应用里产生了 HTTP 请求和日志,OpenTelemetry Agent 会自动创建 trace 和指标,并上报到 Collector。

这一步建议多做一步验证:用curl http://localhost:4317是不通的,OTLP gRPC 不是普通 HTTP 接口。可以直接看 Collector 的日志,如果持续有数据进入,日志里会有 batch 处理的记录。刚开始调试时,也可以在 Collector 配置里临时加一个loggingexporter,把收到的数据直接打到标准输出,方便确认 SDK 侧是否已经上报成功。

5.3 验证数据链路:从请求到三块面板

数据上报后,按下面这个顺序去面板上验证结果。我建议不要跳步,每验证一步就记录一次结果,这能帮你快速定位问题到底出在哪一段。

第一步,打开 Prometheus 的http://localhost:9090/targets,确认otel-collectorjob 状态是 UP。如果 DOWN,先查 Collector 容器 IP 和端口映射。

第二步,打开 Prometheus 的 Graph 页面,查询http_server_duration_seconds_count。有数据说明指标链路通了。

第三步,打开 Grafana,添加 Prometheus 数据源,创建或导入一个 Dashboard,看到请求量、错误率、P95 延迟面板开始出图。

第四步,在 Grafana Explore 里切到 Tempo,按服务名demo-service搜索,能看到若干 trace,点进去能看到完整的 span 列表。

第五步,切到 Loki 数据源,执行{service_name="demo-service"},能看到应用日志。如果日志里带了 trace_id,从日志直接跳到 Tempo,验证联动。

这套流程走通,相当于整条可观测性管线从采集到展示全部打通了。

5.4 模拟一次故障,让告警、日志、链路一起工作

光有数据还不够,最好模拟一次小故障,验证告警和排查路径。我习惯在 Demo 服务里加一个故意报错的接口,比如访问/api/error会返回 500。

然后用脚本持续压这个接口,过几分钟看告警。Prometheus 里如果配好了错误率告警,Grafana 会触发 Alert;打开告警面板能看到触发的规则和当前值。接着去 Tempo 搜索这段时间内的demo-servicetrace,找到 status 为 error 的 trace,顺着 span 看到是哪一个接口返回了 500。再点“Trace to logs”,直接跳到 Loki 的错误日志,定位到具体异常堆栈。

整个过程顺序其实是固定的:指标告警提醒你出事,链路告诉你哪里出事,日志告诉你为什么出事。只有三块拼图都放在一起,排查效率才是真正可用的。

6. 常见问题与排查技巧实录

6.1 数据一直为空,先按顺序检查这五处

遇到“面板上全是 No data”,我基本按下面的顺序排查,命中率很高。

排查点检查方式常见原因
Agent/SDK 是否启动成功看应用日志有没有 OTel 初始化记录javaagent 路径错误、端口没放开
Collector 是否收到数据看 Collector 日志,临时加 logging exporterreceiver 端口没监听、配置没加载
后端是否收到数据看 Prometheus targets、Loki 标签、Tempo 搜索exporter 配置错了、地址写错
Grafana 数据源连通性数据源设置页面看状态URL 端口错、认证配置错
查询时间范围把时间范围调到 Last 15 minutes仪表盘默认 Last 6 hours,但数据刚写入

这里最容易被忽略的是 Grafana 时间范围。曾经有个同事折腾了一晚上,发现面板没数据,最后一查是时间范围选了“Last 12 hours”,而数据是 5 分钟前才写入的。这种问题算不上技术难,但真的会耗掉人一晚上的时间。

6.2 Prometheus 内存暴涨:十有八九是高基数标签

指标和日志不一样,指标里的 label 会组合成时间序列,label 的每个取值都占一份内存。如果某个 label 的取值是无限的,比如url标签直接存完整请求路径,那 Prometheus 很快就会内存暴涨。

判断高基数最直接的方式是用 promtool 分析:

promtool tsdb analyze --server-mode

这个命令会按 label 统计序列数量,你一眼能看到哪个 label 下面的序列最多。运营团队一旦把user_id、request_uuid这类字段加进了指标,基本上是灾难性的。

我的经验是:指标 label 只保留维度和有限取值,比如service_name、status_code、http.route模板化路由、method、environment。业务相关的详细属性属于 trace 和日志,不应该挤进指标系统。如果必须从指标里过滤某个业务维度,优先考虑 recording rule,而不是加标签。

6.3 Tempo 查不到链路:存储、采样、端口三个方向

Tempo 查不到 trace,我一般都从三个方向查。先确认数据真的到了 Tempo,看 Tempo 容器的日志,有没有持续接收 OTLP 的迹象。如果完全没有,说明 Collector 到 Tempo 的网络或端口有问题。我遇到过服务名写错导致 gRPC 连接失败的情况,日志里会显示connection refused。

再查采样率。如果应用侧配了 1% 采样,小流量下本来就很难查到 trace。开发环境我建议直接采样率调到 100%,否则误以为链路没有接入,实际上只是概率太低了。

最后查存储和数据保留。Tempo 虽然默认本地存储,但如果配置了持久化 volume,volume 没挂上,数据重启就丢了。检查方式很简单:产生一段时间流量后,看 Tempo 的数据目录有没有持续增长的文件。

6.4 团队铺开时最容易被忽略的三件事

技术落地往往不是配置问题,而是协作问题。我进了几个团队做可观测性推广,踩过不少管理上的坑。

第一,服务名必须全局唯一且规范。如果甲团队叫order-service,乙团队叫order_svc,Grafana 的仪表盘变量和告警规则都要分别配,链路聚合也聚合不到一起。我建议把服务名收敛成一个公司级规范,和 CMDB 里的服务名保持一致。

第二,统一用 semantic conventions 里的命名。OpenTelemetry 社区已经有完整的命名规范,比如http.response.status_code、service.name、url.path。开始就按规范来,后面接入云厂商或者切后端时,不至于因为字段名不统一而重复改造。

第三,上线验收清单里要包含可观测性。新服务上线前必须验证三件事:Prometheus 能看到进程指标、日志能检索到、trace 能串到首条调用链。只要把这三条写进发布流程,可观测性就不会是一阵风,补做起来成本也低很多。

7. 从能用到好用:我踩过几次坑后的体会

这套系统搭完,真正改变的是故障排查的姿势。以前查一次线上问题,要在监控、日志、APM 三个系统之间反复横跳,现在几乎都从 Grafana 一个入口开始。指标不对劲就顺着链路看,链路慢就顺着 trace_id 查日志,整个过程用不上五分钟。

我最大的体会是:不要一口气把几百个服务全部接入。最合理的路径是选一条核心业务链路,先从网关、订单、支付三个服务开始,把“指标、链路、日志”三条数据处理完整跑通,验证团队里的其他人也能独立使用,再横向铺开。先窄后宽,推广阻力会小很多。

还有一个非常实用的建议:从日志开始。日志接入成本最低,价值最快见效,团队也最容易接受。先把日志全部收进 Loki,再逐步引入 trace 和业务指标。用户一旦习惯了“一个框就查日志”,后续推进链路和指标时基本不用费口舌。可观测性落地不是技术问题,是让所有人把“打开面板”变成习惯的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询