Prometheus + Grafana:搭建可观测
服务跑着,但你不知道它健康不健康、慢不慢——缺可观测。本文讲清 Prometheus + Grafana 这套经典组合:指标(metric types)、pull 模型、PromQL 基础、给应用埋指标、Grafana 画看板 + 告警,以及日志/链路如何补全可观测三支柱。
本文要点
- 主题聚焦:DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
服务上线那天,运维最常被问的一句话不是"架构图发我看看",而是"现在到底健康不健康?"。你登机器敲 top、df -h、kubectl get pods,一堆数字摆出来,对方还是不满意——因为"CPU 50%"说明不了"用户请求慢不慢","内存没用满"也回答不了"有没有人在报错"。没有可观测,你只是在猜。
Prometheus + Grafana 这套组合,过去八年几乎是云原生可观测的事实标准。这篇文章把它讲透:数据模型、指标类型、埋点、PromQL、看板和告警,最后补全日志和链路,凑齐可观测三支柱。
一、可观测三支柱:指标、日志、链路
可观测(Observability)这个词被滥用了,但它的内核很朴素——从系统的外部输出,推断系统内部状态。它有三个支柱,各管一件事:
- 指标(Metrics):带时间戳的数值序列。"过去 5 分钟 QPS 多少""p95 延迟多少""错误率多少"。聚合的、便宜的、适合报警。这是 Prometheus 的主场。
- 日志(Logs):带时间戳的事件记录。"这次请求报了什么错""用户 ID 是谁""栈是什么"。离散的、贵的、适合排查。
- 链路(Traces):一次请求经过的所有服务的树状记录。"这笔慢订单,慢在支付服务还是库存服务"。跨服务的、因果的、适合定位。
三者关系不是替代,是配合:指标告诉你"有问题"(错误率从 0.1% 涨到 5%),链路告诉你"问题在哪一段"(支付服务调外部网关超时),日志告诉你"具体为什么"(网关返回了某条 4xx)。本文聚焦指标,但一定要记着:只看指标做不好可观测,后文第七节会讲怎么补全。
二、Prometheus 的模型:pull、时间序列、标签
理解 Prometheus,先忘掉"日志上报"那套思路。它的核心模型是时间序列(time series):一个指标名 + 一组标签,在时间轴上不断有新的浮点数值落下来。
http_requests_total{method="POST", path="/api/order", status="200"} 1432
http_requests_total{method="POST", path="/api/order", status="500"} 7
每一行就是一个独立的时间序列。标签的每一种组合都是一条新序列——这点很关键,后面埋点时要避免标签维度爆炸(千万别把 user_id、order_id 当标签,那会瞬间产生几十万条序列,直接把 Prometheus 撑爆)。
第二个关键是 pull 模型。Prometheus 不等你推数据,而是它主动来**抓(scrape)**你的 /metrics 接口。这跟 StatsD、Datadog Agent 的 push 模型正好相反。pull 的好处很明显:
- 应用不需要知道监控系统的地址——它只要暴露 HTTP 接口,Prometheus 来不来拉是监控的事。应用和监控解耦。
- Prometheus 主动感知应用死活。如果一个实例拉不到了,Prometheus 自己就知道,不用额外做心跳。
- 拉取频率可控,采集压力可预测,不会因为某个应用突然疯狂打点把后端打挂。
代价是需要服务发现:Prometheus 得知道"哪些实例需要拉、地址是多少"。在 Kubernetes 里这天然方便(kubernetes_sd_configs 自动发现 Pod);在静态部署里写几行 static_config 也很简单。短任务(life cycle 太短、来不及被拉)是 pull 模型的盲区,这时要用 Pushgateway 做中转——但这是例外,不是常规。
三、四种指标类型:Counter / Gauge / Histogram / Summary
埋指标前必须分清四种类型,选错类型,后面 PromQL 怎么写都不对。
Counter(计数器):单调递增,只能加不能减(重启归零)。代表"累计发生的事"。比如 http_requests_total(累计请求数)、errors_total(累计错误数)。注意它本身不能直接当 QPS 看——它一直涨是正常的,要看"涨得多快"得用 rate()。计数器突然下降,要么重启了,要么负数,都是异常信号。
Gauge(瞬时值):可增可减,代表"当前状态"。比如 memory_usage_bytes(当前内存)、active_connections(当前连接数)、queue_depth(队列长度)、temperature_celsius(温度)。看板直接画它的值就行,不用 rate。
Histogram(直方图):把数据分桶统计分布。这是测延迟的正确姿势。比如设桶 [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] 秒,每个请求按耗时落到对应桶里,累计计数。配合 histogram_quantile() 能算任意分位(p50/p95/p99)。桶的选择是 Histogram 设计的核心——桶太粗,p95 误差巨大;桶太细,序列爆炸。一般按业务 SLA 的量级定,比如 HTTP API 用 [0.01 ... 10] 这套默认桶基本够用。
Summary(摘要):在客户端直接算好分位数再上报。看起来比 Histogram 省事,但它有个致命缺陷:多个实例的分位数不能聚合。你有 10 台机器,各自报了 p95,你没法从这 10 个 p95 算出"全局 p95"——数学上不存在这种聚合。所以生产上几乎无脑选 Histogram,Summary 只在单实例场景或固定标签聚合下用。
一句话记忆:累计的用 Counter,瞬时的用 Gauge,分布的用 Histogram。
四、给应用埋指标:client 库 + /metrics + RED 方法
理论落地,就是给代码埋点。以 Node.js 为例,官方推荐 prom-client:
import { Registry, Counter, Histogram, collectDefaultMetrics } from 'prom-client');
const registry = new Registry();
collectDefaultMetrics({ register: registry }); // 自动采集 Node 进程的 CPU/内存/GC 等
const httpRequestTotal = new Counter({
name: 'http_requests_total',
help: 'Total HTTP requests',
labelNames: ['method', 'path', 'status'],
registers: [registry],
});
const httpDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration',
labelNames: ['method', 'path', 'status'],
buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
registers: [registry],
});
// 中间件里埋点
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const labels = { method: req.method, path: routePattern(req), status: String(res.statusCode) };
httpRequestTotal.inc(labels);
httpDuration.observe(labels, (Date.now() - start) / 1000);
});
next();
});
// 暴露 /metrics
app.get('/metrics', async (req, res) => {
res.set('Content-Type', registry.contentType);
res.end(await registry.metrics());
});
三个细节决定埋点质量:
- path 要用路由模板,别用真实 URL。
path="/api/order/:id"而不是path="/api/order/12345"。后者会把每个订单 ID 变成一条新序列,几小时就把内存撑爆。这是新手第一大坑。 - label 数量要克制。
method、path、status这种基数低的可以;user_id、session_id这种基数高的坚决不行。标签的基数乘上序列数,要能装进一张表。 - 必埋的三类指标按 RED 方法:Rate(请求速率)、Errors(错误数)、Duration(延迟分布)。这是 Google SRE 书里给微服务定的最小集,几乎所有 HTTP 服务都适用。再加一个 Saturation(饱和度,如连接池占用),就是 USE 方法,适合基础设施。RED 管服务,USE 管资源,两条线一起上,可观测的骨架就立起来了。
五、PromQL 基础:rate / sum by / histogram_quantile
指标埋好了,接下来是查询。PromQL 是声明式的,初看别扭,掌握三个函数能解决 80% 场景。
查 QPS(每秒请求数):
sum(rate(http_requests_total[5m])) by (path)
rate() 把 Counter 的累计值变成"每秒增量"——这是看 Counter 的唯一正确姿势。[5m] 是窗口,窗口太短抖动大,太长反应慢,5 分钟是常用值。sum by (path) 把所有 method、status、实例的序列按 path 聚合。
查错误率:
sum(rate(http_requests_total{status=~"5.."}[5m])) by (path)
/
sum(rate(http_requests_total[5m])) by (path)
分子是 5xx 的速率,分母是全部速率,两个 sum by (path) 维度对齐才能除。status=~"5.." 是正则匹配 500–599。错误率比绝对错误数更有信息量——QPS 涨了错误跟着涨是正常的,错误率窜上来才是真问题。
查 p95 延迟:
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
这是 Histogram 的杀手锏。注意几点:必须用 _bucket 结尾的序列(分桶数据),by (le) 不可省(le 是桶上界),rate 在前 sum 在中 histogram_quantile 在后,顺序错了结果就错。这条查询能跨所有实例聚合出全局 p95——这正是 Histogram 相对 Summary 的核心优势。
把这三条查询画成三个面板,一个服务的健康度就一目了然了。
六、Grafana:数据源、看板、告警
Prometheus 自己的 UI 只够调试用,真正给人看的是 Grafana。
接数据源:Grafana 里加 Prometheus 数据源,填地址(http://prometheus:9090),保存即用。Prometheus 本身存的是本地 TSDB,默认 15 天左右,数据量大了要接远程存储(VictoriaMetrics、Thanos、Mimir)做长期保存和高可用。
画看板:每个 Panel 写一条 PromQL,选可视化类型(时序图、Stat、Gauge、 heatmap)。Hero 建议:一个服务的看板顶部放四个大数字——QPS、错误率、p95 延迟、实例数。下面再画各维度的时序曲线。这样的看板自上而下"总量→结构→趋势",人一眼能读出健康度。
告警:Grafana 内置告警(managed alerting),但生产里更常见的是 Prometheus 自带的 Alertmanager——它跟 Prometheus 解耦,负责"告警去重、分组、抑制、路由到不同渠道"。一条告警规则长这样:
groups:
- name: api-alerts
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (path)
/ sum(rate(http_requests_total[5m])) by (path) > 0.05
for: 10m # 持续 10 分钟才触发,避免抖动误报
labels:
severity: page
annotations:
summary: "{{ $labels.path }} 错误率超 5%"
description: "当前错误率 {{ $value | humanizePercentage }},持续 10 分钟"
for: 10m 这条很重要——没有 for 的告警会在瞬时抖动时狂响,把你和值班同事轰炸到麻木,最后所有告警都被忽略。这是"告警疲劳"的源头,务必克制。
告警设计有几条实战铁律:只报需要人介入的(自动恢复的别报)、按 SLO 报而不是按阈值报(错误率影响用户才报,不是 CPU 过 80% 就报)、分级(page / ticket / slack,别所有事都打电话)。一个健康的告警系统,每天每值班人不超过 5 条,超过了说明阈值或规则该改。
七、补全三支柱:日志(Loki)+ 链路(OpenTelemetry / Jaeger)
指标只是可观测的第一根柱子。当你看到"错误率涨了",下一步必然是"去看具体错在哪"——这时候需要日志和链路。
日志侧:Loki。Grafana 自家的日志系统,设计哲学跟 Prometheus 一脉相承——只索引标签,不索引日志正文,因此写入和查询都便宜。Loki 的标签最好和 Prometheus 的标签对齐(同一个 service、instance),这样在 Grafana 里可以从一个指标面板直接下钻到对应日志——这就是"指标和日志关联"的体感:看到 p95 飙高的那个时间点,点一下就能把那台机器那段时间的日志拉出来。
链路侧:OpenTelemetry + Jaeger / Tempo。OpenTelemetry(OTel)是 CNCF 推的可观测数据采集标准,一套 SDK 同时产出 metrics、logs、traces 三种信号,且能通过 trace context(traceparent HTTP 头)把跨服务的请求串起来。Tempo 是 Grafana 的链路后端,跟 Loki、Prometheus 一起能做一件事:从指标 → 链路 → 日志的三段式下钻。
举个具体场景:Grafana 上看到 POST /api/order 错误率从 0.2% 涨到 6%。
- 点错误率面板,下钻出这段时间的 trace 列表。
- 选一条慢 trace,看到它经过了
api → payment → external_gateway,external_gateway 这一段花了 4.8 秒。 - 再点这一段,下钻出当时的日志,看到网关返回了
connection reset。
三支柱用同一个 trace_id、同一组标签串起来,排障路径从"猜"变成了"顺着走"。这才是"可观测"该有的样子——不是三个孤立的系统,是一张能来回跳的网。
小结
- 指标、日志、链路缺一不可:指标告诉你"有问题",链路告诉你"在哪段",日志告诉你"具体为什么"。Prometheus + Grafana 解决指标这一支柱,但别止步于此。
- Prometheus 的灵魂是 pull + 时间序列 + 标签:应用只管暴露
/metrics,采集交给 Prometheus;标签是维度,但高基数标签(user_id、order_id)是序列爆炸的元凶,坚决不埋。 - 四种指标类型选对再写 PromQL:Counter 用
rate,Gauge 直接看,Histogram 算分位数,Summary 几乎别用(跨实例不能聚合)。一句话:累计用 Counter、瞬时用 Gauge、分布用 Histogram。 - RED 是微服务最小指标集:Rate / Errors / Duration 三条线,配上
histogram_quantile的 p95,一个服务的健康度看板就立住了。 - 告警要克制,看板要分层:
for持续时间防抖动,按 SLO 报而非按阈值报,每天每值班人不超过 5 条才算健康。
一句话收尾:可观测不是堆工具,是让你在凌晨三点被告警叫醒时,十分钟内定位到那台机器那行代码。Prometheus + Grafana 这套经典组合,加上 Loki 和 OpenTelemetry,把这件事从玄学变成了工程。
Related
相关推荐
文章
Docker 部署 Next.js 社区项目的注意事项
围绕 DevOps、Next.js 的技术趋势、工程实践和独立开发视角。
文章
OpenTelemetry 如何帮助你观察 AI 应用?
围绕 DevOps、AI 的技术趋势、工程实践和独立开发视角。
文章
长运行工具调用:Operation、Poll 与 Cancel 的终态协议
初始请求超时后,工具可能仍在服务端运行;发出取消也不等于副作用已停止。本文为单次长运行工具调用建立 operation 身份、轮询水位、取消竞态和唯一终态接受点。
文章
Agent 记忆删除传播:从用户遗忘请求到可验证清除
删除主表一行不会清掉向量、摘要、缓存和恢复后的备份。本文用删除状态机、代际墓碑、派生血缘与端到端 canary 验证真正遗忘。