想象你是一家大型综合医院的体检中心主任。每天清晨,护士们带着体检设备到各个病房测量病人的体温、心率、血压,把数据记录在病历系统里;主治医生打开电脑,查看某位病人过去24小时的体温曲线;检验科发现某项指标超标,立刻呼叫相关科室准备会诊;大屏幕上滚动着各病区的实时床位状态。这套流程,就是Prometheus在云原生世界里做的事——只不过它监测的不是病人,而是一个个微服务程序的生命体征。本文会用“医院体检中心”这个比喻,带你从零理解Prometheus如何采集指标、编写查询、配置告警,最终搭建出一套完整可靠的可观测性体系。

岗位关联

对AI从业者而言,可观测性是保障在线推理服务稳定性的基石。学懂Prometheus,你就能用数据说话、预判故障,让模型服务从“能用”升级为“可信赖”。

目录
  1. 可视化演示
  2. 数据模型核心
  3. 四大指标类型
  4. 采集配置方法
  5. PromQL查询
  6. 告警规则编写
  7. 告警通知路由
  8. 可视化与高可用
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Prometheus Pull架构组件协同、四种指标类型特征及告警规则评估与Alertmanager路由机制,交互理解监控告警闭环。

本节配有交互动效(进入视口后自动加载,约 3KB)。

'+modeData.architecture.caption+'

数据模型核心

体检中心的病历系统里,每位病人都有一张唯一的体检表:表头写着“姓名:张三,科室:心内科”,表格里记录着每次测量的血压值和测量时间。Prometheus 的数据模型也是如此——每个被观测的服务都会产生一系列“体检表”,这张表的身份标识由指标名和标签共同决定。

指标名描述“测的是什么”,比如 http_requests_total 表示 HTTP 请求总数,node_memory_usage 表示节点内存使用率。标签则是体检表上的“筛选维度”,比如 method="GET"、status="200"。一个指标名配上完整标签集合,就构成一条独一无二的时间序列——同一病人同一时刻的血压只能有一个读数,同一序列在同一时间戳也只能有一个样本值。

每条时间序列在某个时刻记录的数据叫样本,它由值和时间戳组成。所有同名指标统称为一个指标家族。

理解这个模型,就像记住“体检表 = 病人姓名 + 科室 + 测量时间 + 测量值”。你后续写的所有 PromQL 查询、告警规则,本质上都是在和这样的时间序列打交道。

series={指标名, label_key=label_value}→v(t)\text{series} = \{\text{指标名},\ \text{label\_key}=\text{label\_value}\} \rightarrow v(t)
  1. 指标名描述观测对象的标准命名,如 http_requests_total,推荐使用基础单位加 _total 后缀。
    metric_name\text{metric\_name}
  2. 标签集合一组键值对,如 method="GET",把同一指标拆分成多个业务维度。
    label_key=label_value\text{label\_key} = \text{label\_value}
  3. 时间戳与值每个样本是带时间戳的数值,v(t) 表示在时刻 t 的瞬时值。
    v(t)v(t)

四大指标类型

如果体检中心只有一种测量仪器,就分不清“累计心跳次数”和“当前体温”的区别。Prometheus 也为不同场景准备了四种指标类型。

Counter 计数器像健身房门口的打卡机,只增不减,记录服务启动以来累计处理的请求数。Gauge 仪表盘像温度计,可以上下浮动,适合当前 CPU 使用率、在线连接数。Histogram 直方图像体检时的血压测量分布表,把大量样本分桶统计,方便计算“95% 的请求耗时低于多少”。Summary 摘要则在客户端直接算出分位数,像护士在床边统计好后直接填表,服务端拿来就能用。

Counter 的值会越滚越大,直接看它没有意义,所以通常配合 rate() 函数计算每秒变化率。

选对指标类型,就像体检时选对仪器:想看累计量用 Counter,想看瞬时量用 Gauge,想看分布用 Histogram 或 Summary,仪器选错,后面的告警和查询都会变形。

rate=v(t2)−v(t1)t2−t1\text{rate} = \frac{v(t_2) - v(t_1)}{t_2 - t_1}
  1. 计数器末期值 v(t2)当前时刻的累计值。
    v(t2)v(t_2)
  2. 计数器初期值 v(t1)查询窗口起始时刻的累计值。
    v(t1)v(t_1)
  3. 时间差 t2 - t1窗口长度,比如 5 分钟就是 300 秒。
    t2−t1t_2 - t_1
  4. 速率结果一段时间内的增长量除以时长,得到每秒均值。

采集配置方法

体检中心的护士不能凭空知道病人状态,她们要定期走到病床前测量。Prometheus 也采用“主动拉取”模式:由 Prometheus 服务端按配置的 scrape_interval 访问目标服务的 /metrics 端点,把暴露的指标拉回来存储。

每个被监控的服务就是抓取目标(target),它的 /metrics 端口如同一张体检窗口,应用通过客户端库在这个窗口展示自己的健康数据。scrape_interval 是采集周期,默认 15 秒——这会直接决定数据精度:采集周期越短,数据越精细,但存储和计算压力也越大。

配置方式有两种。静态配置适合固定不变的机器,直接在配置文件中写死 IP 和端口,如同给老病号固定床位。服务发现适合 Kubernetes 这类动态环境,Prometheus 通过 API 自动找到新启动的 Pod,如同护士从住院系统实时获取新入院的病人清单。

这个公式帮你理解为什么窗口查询的曲线是锯齿状——间隔越短,曲线越平滑,但存储开销越大。合理选择采集周期,是监控体系设计的第一个权衡点。

N样本点=T时间范围TscrapeN_{\text{样本点}} = \frac{T_{\text{时间范围}}}{T_{\text{scrape}}}
  1. 时间范围 T_时间范围你想回溯查看的时长,比如 5 分钟。
    T时间范围T_{\text{时间范围}}
  2. 采集周期 T_scrape两次抓取之间的间隔,通常 15 秒。
    TscrapeT_{\text{scrape}}
  3. 样本点数量窗口内的样本数 = 300 秒 ÷ 15 秒 = 20 个点。

PromQL查询

体检数据存进系统后,主治医生需要一个查询工具:既能查单个病人 24 小时体温曲线,也能统计全科高血压患者比例。PromQL(Prometheus Query Language)就是这套病历查询语言。

先看选择器:http_requests_total{method="GET"} 只选标签 method="GET" 的序列。如果查询“过去 5 分钟的请求速率”,则写 rate(http_requests_total[5m]),把 5 分钟内的所有样本装进“窗口”再算变化率。这种带时间窗口的向量叫范围向量,不带窗口只取最新值的叫即时向量。

PromQL 还提供了丰富的聚合运算:sum by (service) 表示按服务维度汇总,avg 表示求平均。配合 histogram_quantile() 函数,可以从直方图中估算出 P95、P99 分位数,解决“大多数请求很快,但少数请求特别慢”这类长尾问题。

PromQL 就好比医生的查询笔记本:先选病人范围,再加时间窗口,最后做统计运算。写得越准确,诊断结论越可信。

qps=∑servicerate(http_requests_total[5m])\text{qps} = \sum_{\text{service}} \text{rate}(\text{http\_requests\_total}[5m])
  1. 范围向量 [5m]把过去 5 分钟内每个样本装进时间窗口。
    [5m][5m]
  2. rate 函数把 Counter 的区间增长换算成每秒速率,消除累计量影响。
    rate(...)\text{rate}(...)
  3. sum by 聚合按 service 标签分组求和,得到全站每秒请求量。
    ∑service\sum_{\text{service}}

告警规则编写

医生不会时刻盯着每一张体检表,而是依赖“诊断标准”:体温超过 37.3℃ 持续 30 分钟,就需要通知值班医生。Prometheus 的告警规则就是这套诊断标准,写在 YAML 规则文件中。

一条告警规则由三部分组成:告警名称(什么问题)、告警表达式(什么条件)、持续时间 for(条件要持续多久)。for 字段很关键,如果要求 CPU 高负载持续 15 分钟才告警,就能过滤掉偶发的瞬时毛刺,避免“狼来了”式的告警风暴。

表达式就写在 expr 字段里,例如“最近 15 分钟平均负载大于 2”:

规则文件还支持标签模板:通过 {{ $labels.instance }} 把触发告警的实例 IP 动态写入告警信息,这样值班人员收到通知时能看到“哪台机器、哪个服务、当前值是多少”。告警规则本身不负责通知,它更像体检中心的“诊断标准”,一旦触发就把异常病例转交给急诊调度中心——也就是 Alertmanager。

avg_over_time(node_load1[15m])>2\text{avg\_over\_time}(\text{node\_load1}[15m]) > 2
  1. 指标项 node_load1系统 1 分钟内平均负载的 Gauge 指标。
    node_load1\text{node\_load1}
  2. 时间窗口 [15m]只取过去 15 分钟的样本参与计算。
    [15m][15m]
  3. avg_over_time 函数对窗口内的值求平均,抹平瞬间抖动。
    avg_over_time(...)\text{avg\_over\_time}(...)
  4. 阈值判断 > 2平均负载超过 2 时满足触发条件。
    >2> 2

告警通知路由

急诊调度中心收到大量病例后,不会电话轰炸每个医生,而是按科室、按严重等级分类处理。Prometheus 的 Alertmanager 就是这样的调度中心,负责把告警变成有条理的通知。

它有三大核心能力。分组:把同一时间、同一服务的多个告警合并成一条通知,像把同一病房的病例装进同一个文件夹。抑制:如果已经发出“数据库不可用”,那么由数据库引发的“订单服务异常”这类二级告警就不必再发,避免雪崩式消息。静默:预知到凌晨 2-4 点做版本升级时,可以提前设置静默时间窗口,让这段时间的告警不打扰值班人员。

分组后的告警要发给谁,由路由树决定:路由规则按告警标签匹配不同接收器(邮件、钉钉、Webhook)。告警对象 a 经过分组机构聚合,再被路由到接收器:

Alertmanager 把“告警”和“通知”解耦,就像调度中心不负责诊断,只负责把病人安排到正确的科室。这样一来,即使同一时刻触发 100 条告警,值班人员收到的可能是 3 条经过分组合并的高质量通知。

a→group_byG→routera \xrightarrow{\text{group\_by}} G \xrightarrow{\text{route}} r
  1. 告警对象 aPrometheus 规则触发的某条告警,带有一组标签。
    aa
  2. 分组聚合 group_by按 alertname 或 service 标签把相似告警合并。
    group_by\text{group\_by}
  3. 路由分发 route根据路由树选择对应的接收器。
    route\text{route}
  4. 接收器 r最终通知渠道,邮件、钉钉、Webhook 等。
    rr

可视化与高可用

体检中心再忙,也要在大屏幕上滚动显示各区病患情况,还要保证系统停电了也能继续运转。Prometheus 生态里,Grafana 就是那块大屏幕。把 Prometheus 配置为 Grafana 的数据源后,你就能拖拽绘制折线图、柱状图、热力图,把零散的查询组织成一张张可视化面板(Dashboard),让团队一眼看清系统整体状态。

高可用方面,Prometheus 单实例部署存在单点故障风险。最简单的方案是部署主从副本:两个 Prometheus 实例采集同样的目标,通过 Alertmanager 的 deduplication 机制去重,主挂掉后从节点继续工作。更复杂的是联邦集群:每个数据中心部署一套 Prometheus 采集本地指标,根节点 Prometheus 只负责聚合子节点的结果,像体检中心总部汇总各分院数据,实现分层扩展。

从数据量的角度看,单实例能承载的序列数是有限度的:

当你的服务规模增长到上万条指标序列时,不提前规划副本和联邦,Prometheus 会成为新的故障点。记住:体检中心要服务好病人,也得先保证自己不“生病”。

C序列=M目标×S平均序列数C_{\text{序列}} = M_{\text{目标}} \times S_{\text{平均序列数}}
  1. 目标数量 M_目标被监控的服务实例数。
    M目标M_{\text{目标}}
  2. 单目标序列数 S_平均序列数每个实例暴露的指标序列数量。
    S平均序列数S_{\text{平均序列数}}
  3. 总序列数 C_序列两者乘积决定了存储和查询压力。
    C序列C_{\text{序列}}

AI 应用场景

在线推理服务监控

阶段:模型服务部署与运维

解决的问题:传统方式靠人工 SSH 登录服务器敲 top 命令看 CPU,故障发生时无法回溯历史曲线。用 Prometheus 自动采集推理服务的 QPS、延迟、GPU 利用率后,异常波动一眼可见。

具体案例:某 NLP 服务的模型推理延迟从 50ms 飙升到 800ms。通过 Grafana 查看 P99 延迟曲线,发现高峰时段 GPU 显存接近耗尽,定位到是并发队列堆积,随后通过限流和扩容解决了问题。

告警驱动的自动扩容

阶段:弹性伸缩与成本控制

解决的问题:基于阈值的告警规则让服务在流量突增时自动触发扩容,把“被动救火”变成“主动预防”。

具体案例:推荐系统在大促前流量翻倍,运维配置了规则:container_cpu_usage_ratio > 0.8 持续 5 分钟触发 Webhook,通知 K8s 自动扩容 Pod 副本数,整体响应时间保持稳定。

业务健康度大盘

阶段:业务效果可视化

解决的问题:产品经理也要看模型效果和业务指标,Grafana 大屏把“DAU、推荐点击率、API 错误率”汇聚到同一屏,打破数据孤岛。

具体案例:电商平台搭建“双十一作战大屏”,用 Prometheus 采集网关和推荐模型指标,Grafana 实时渲染每秒订单数、支付成功率、推荐服务 P95 耗时,技术团队和业务团队共享同一份数据结论。

Python 演示

python21 行
from prometheus_client import start_http_server, Counter, Gauge, Histogramimport randomimport time REQUEST_COUNT = Counter("web_requests_total", "累计处理的请求数")REQUESTS_IN_PROGRESS = Gauge("web_requests_in_progress", "当前正在处理的请求数")REQUEST_DURATION = Histogram("web_request_duration_seconds", "请求耗时分布")  def simulate_request():    REQUESTS_IN_PROGRESS.inc()    time.sleep(random.uniform(0.02, 0.15))    duration = random.uniform(0.05, 0.8)    REQUEST_COUNT.inc()    REQUEST_DURATION.observe(duration)    REQUESTS_IN_PROGRESS.dec() if __name__ == "__main__":    start_http_server(8080)    print("metrics endpoint: http://localhost:8080/metrics")    while True:        simulate_request()
目的:在 8080 端口暴露 /metrics 端点,供 Prometheus 抓取模拟的业务指标。
  1. Counter("web_requests_total", ...)定义只增不减的计数器,记录累计请求数
  2. Gauge("web_requests_in_progress", ...)定义上下浮动的仪表盘指标,记录并发数
  3. Histogram("web_request_duration_seconds", ...)定义直方图,自动分桶记录请求耗时分布
  4. REQUEST_COUNT.inc()每模拟一次请求,计数器加 1
  5. REQUEST_DURATION.observe(duration)把耗时样本投入直方图
  6. start_http_server(8080)启动指标暴露服务,等 Prometheus 来拉取
  7. while True:无限循环模拟业务流量的持续产生

常见误区

误区我以为 `web_requests_total` 的值直接展示在 Grafana 上就能反映请求量,结果曲线像爬山一样一直往上走,根本看不出当前速度。

正解Counter 是累计量,它的绝对大小没有意义,必须用 rate() 或 increase() 转换为速率后再画图。就像看一辆车跑了多远没用,要看它的速度(每秒跑多少米)才有意义。

为何常见:初学者容易把“数值”和“速率”混为一谈,忽视了 Counter 的设计初衷是累计。

误区我配置告警时 `for: 0s`,觉得越灵敏越好,结果一个抖动就让告警疯狂轰炸,值班手机响个不停。

正解for 表示条件要持续多久才触发告警,应该根据业务容忍度设置,比如 CPU 高负载持续 10 分钟才告警。它像医生的诊断标准:一次咳嗽不能说明得了感冒,连续打喷嚏加发热才值得警惕。

为何常见:新手追求“即时告警”,却没考虑瞬时抖动导致的误报率。

误区我觉得 Histogram 和 Summary 都能算分位数,那它们应该可以相互替代吧?

正解Summary 在客户端直接算好分位数,无法在服务端跨实例聚合;Histogram 只存分桶计数,可以在 Prometheus 端用 histogram_quantile() 灵活计算任意分位数。两者类比分桶存储的原材料和在窗口直接卖半成品的区别。

为何常见:两者都能回答“P95 是多少”,导致初学者忽略了聚合能力的本质差异。

误区告警信息只写“服务异常”,没有实例 IP 和当前数值,值班人员收到通知后还要手动查是哪个机器出了问题。

正解用 {{ $labels.instance }}、{{ $value }} 把实例地址和当前值动态带入告警信息,让通知自带上下文。体检报告若只写“指标异常”而不写病人姓名和数值,医生根本没法干活。

为何常见:规则文件写完后很少有人主动测一条真实告警,等到故障时才暴露信息不完整的问题。

误区我写了一个脚本把指标 POST 到 Prometheus 服务器,结果发现它根本不接收,以为 Prometheus 坏掉了。

正解Prometheus 是拉取模式,它主动去访问目标暴露的 /metrics 端点。就像医院规定护士去病房测体温,而不是让病人自己跑来检验科交数据。Push 模式可以用 Pushgateway 做特殊补充,但常规采集必须由服务端发起。

为何常见:很多监控系统(如 StatsD)是推送式的,带着固有思维迁移就踩了坑。

面试题

Q1. Prometheus 采集指标使用哪种模式?

单选题 · easy

  • A.服务端主动拉取← 正确答案
  • B.客户端主动推送
  • C.双向建立长连接
  • D.定期复制日志文件

答案:A。Prometheus 使用服务端主动拉取(Pull)模式,定时访问目标的 /metrics 端点。就像体检中心护士主动到病房测量,而不是病人跑去找检验科。虽然 Pushgateway 支持推送,但那是特殊补充,不是主模式。

Q2. 网站接口“累计被调用总次数”适合用哪种指标类型?

单选题 · easy

  • A.Counter 积分递增类← 正确答案
  • B.Gauge 仪表实时类
  • C.Histogram 分布统计类
  • D.Summary 分位摘要类

答案:A。累计调用次数只会不断增长,符合 Counter「只增不减」的特性。Gauge 适合可上下浮动的当前值(如 CPU 使用率),Histogram 和 Summary 适合耗时分布,都不匹配“累计总量”的语义。

Q3. 下面哪条表达式能计算每秒请求速率?

单选题 · mid

  • A.rate(http_requests_total[5m])← 正确答案
  • B.sum(http_requests_total[5m])
  • C.avg(http_requests_total[5m])
  • D.count(http_requests_total[5m])

答案:A。rate() 专门把 Counter 在时间窗口内的增长量换算为每秒速率。sum、avg、count 都是聚合函数,无法直接作用于范围向量计算速率,就像只统计病人总数无法得知心跳速度。

Q4. 关于 Prometheus 时间序列的说法正确的是?

单选题 · mid

  • A.指标名加标签构成唯一序列← 正确答案
  • B.指标名不同也可互相合并
  • C.标签值只能使用固定数字
  • D.标签数量越多查询越快

答案:A。同一指标名配合不同标签值,形成各自独立的时间序列,如 method="GET" 和 method="POST" 是两条序列。标签是键值对、可为任意字符串;标签越多基数越高,查询反而更慢。

Q5. Histogram 与 Summary 的核心差异是什么?

单选题 · hard

  • A.直方图可在服务端聚合求分位← 正确答案
  • B.摘要可以在服务端灵活聚合
  • C.直方图与摘要可以完全替代
  • D.摘要必须依赖外部存储不可用

答案:A。Histogram 在客户端只做分桶统计,服务端能用 histogram_quantile() 跨实例聚合求 P95/P99;Summary 在客户端就算好分位数,无法再跨实例聚合。这就像医院把原始测量单汇总给总院分析,而不是让每个分院各自下结论。

Q6. 下列哪些指标类型适合度量“请求耗时”这类分布型数据?

多选题 · mid

  • A.耗时分布用 Histogram← 正确答案
  • B.耗时分布用 Summary← 正确答案
  • C.耗时分布用 Counter
  • D.耗时分布用 Gauge

答案:AB。Histogram 和 Summary 都是为分位数、分布类数据设计的。Counter 只增不减无法体现波动分布,Gauge 只能记录当前值,无法还原耗时长尾。项目里通常优先用 Histogram,因为它支持服务端聚合。

Q7. Alertmanager 的“分组”主要解决什么问题?

单选题 · mid

  • A.把同类告警合并一起发送← 正确答案
  • B.把所有告警永久沉默处理
  • C.替代 Prometheus 存储时序
  • D.自动修复故障并恢复服务

答案:A。分组是按 group_by 标签把相似告警聚合成一条通知,避免 100 条告警轰炸值班手机。它不负责存储、不修补故障、更不是永久静默,而是减少通知噪音的手段。

Q8. 以下哪些属于 Prometheus 高可用常用方案?

多选题 · hard

  • A.主从副本双实例部署← 正确答案
  • B.联邦集群分层聚合采集← 正确答案
  • C.Alertmanager 告警去重兜底← 正确答案
  • D.Grafana 双活替代抓取存储

答案:ABC。主从副本做冗余,联邦集群做横向扩展,Alertmanager 的 deduplication 可让多副本只发出一次告警。Grafana 只是可视化工具,不具备指标抓取和存储能力,无法替代 Prometheus 本体。

Q9. 告警规则中 for: 5m 字段表示?

单选题 · easy

  • A.条件需持续 5 分钟才触发← 正确答案
  • B.告警最多发送 5 次通知
  • C.指标每隔 5 分钟采集一次
  • D.查询表达式超时时间设为 5 分钟

答案:A。for 表示告警条件要连续满足多久才真正触发,用于过滤瞬时抖动。它不控制通知次数、采集周期或超时。就像医生要求“发烧超过 1 小时才用药”,而不是一量体温偏高就立刻下诊断。

Q10. Prometheus 可以通过哪些方式发现抓取目标?

多选题 · hard

  • A.Kubernetes API 自动发现← 正确答案
  • B.Consul 注册中心动态发现← 正确答案
  • C.静态配置直接写 IP 端口← 正确答案
  • D.修改 Prometheus 源代码支持

答案:ABC。服务发现机制包括 Kubernetes、Consul、DNS 等,静态配置也是标准方式之一,适合固定机器。修改源代码显然不是合理的运维做法,而且每次版本升级都要重写,完全不可维护。