☰
Prometheus监控体系实战:从Pull模型到生产避坑指南
2026/10/13 5:27:09 网站建设 项目流程

做监控这件事,绕不开的一个名字就是 Prometheus。不管你是刚入行的运维新人,还是已经在云原生环境里摸爬滚打多年的 SRE,最后都会在某个时刻打开那个自带查询界面的 9090 端口,或者在一堆告警规则里和它打交道。我最早接触 Prometheus 是在一个微服务拆分进入深水区的项目里,当时还在用传统监控方案,指标一多,加一次图表要等半天,改告警配置更是改一次想骂一次。后来全面切到 Prometheus 体系,才算把监控从"能看"做到了"能用"。

这篇内容不打算写成抄文档式的科普。我会按自己落地时的思路,把这个监控系统拆开讲清楚:它解决了什么问题、为什么这样设计、怎么搭起来、查询怎么写、告警怎么配,以及生产环境里最容易翻车的几个坑。无论你是第一次听说它,还是用了一阵子想补补底层逻辑,应该都能有点收获。

1. 先说清楚:Prometheus 到底是个什么东西

1.1 名字的来历和它在监控体系中的位置

Prometheus 这个名字来自希腊神话里的泰坦神普罗米修斯,他从神界盗取了火种送给人类,让人类有了文明和力量。监控系统取这个名字,寓意也差不多——从各台机器、各个服务里把运行状态的"火焰"采集出来,让开发者能看见系统内部到底在发生什么。这个项目最早由一家知名音频平台的工程师为内部监控需求而发起,2016 年加入云原生领域的顶级开源基金会后,和 Kubernetes 一起成长为云原生时代的事实标准。现在几乎所有主流服务网格、容器平台,都把 Prometheus 指标作为默认的监控输出格式。

它本质上是一套"抓取-存储-查询-告警"的完整链路。注意这里的关键词是抓取,不是推送。监控对象通过暴露一个 HTTP 接口,把自身状态用特定格式的文本暴露出来,Prometheus 定时去拉。这种设计在后面很多讨论里都会反复出现,理解了它,你就能理解为什么这个体系会成长成现在的样子。

1.2 它能解决什么问题,边界在哪里

先讲一个实际场景。假设你有几十个微服务,每个服务又有多个实例,部署在几十台机器上。传统监控方案通常要求每台机器装一个 agent,agent 再往中心服务器推数据,中心服务器负责汇总、画图、告警。这套模式在小规模时没问题,但服务一多、实例一变(比如弹性伸缩频繁加减副本),问题就来了:中心服务器不知道有哪些实例存在,需要手动维护清单;agent 数量和版本一多,运维本身又成了负担。

Prometheus 把这些问题拆成几块解决:

  • 服务发现:不需要手工维护机器清单,可以通过配置文件、接口或者直接对接容器平台,动态发现要抓哪些目标。
  • 统一的指标暴露格式:语言无关,只要提供一个文本接口,什么服务都能接,接入门槛被压到最低。
  • 标准化的数据模型:所有指标都是一条带标签的时间序列,查询和告警都在这个模型上操作,非常干净。

它能做的是三件大事:采集指标、处理告警、提供查询。它的可视化能力很弱,自带一个极简的图表页,主要用于调试,真正做仪表盘通常用生态里最出名的伙伴 Grafana。这一点建议新手一开始就记住,很多人把 Prometheus 和 Grafana 混为一谈,其实分工完全不同:一个是监控后端,一个是仪表盘前端。

2. 核心设计思路:Pull 模型和时间序列数据模型

2.1 为什么选 Pull 而不是 Push

监控数据的采集方式,业界长期有两大门派:一种是 agent 把数据主动推给中心,叫 Push;一种是中心主动上门拉取,叫 Pull。Prometheus 选择了 Pull,这个选择刚出来时争议很大,因为当时主流监控系统大多是 Push。

Pull 的好处很实在。第一,中心主动拉取,目标实例的可用性一目了然——拉不到就是挂了,不需要额外的心跳机制来判断存活。第二,抓什么、多频繁抓,由监控端说了算,变更采集策略时不需要去动所有被监控机器。第三,被监控端只需要暴露接口,不关心数据送到哪里,天然适合容器环境里实例频繁漂移的场景。

但 Pull 也有天生短板:如果监控端和被监控端之间有网络隔离,或者目标是生命周期很短的批处理任务(比如一个跑几十秒就退出的一次性脚本),Prometheus 根本来不及去拉。所以生态里有一个叫 Pushgateway 的中间组件专门解决这种场景。我见过很多团队一上来就无脑用 Pushgateway,结果把数据模型搞得一团糟,后面会单独讲到底什么时候该用它。

2.2 一切皆标签:时间序列模型

理解 Prometheus 的核心,是学会用"时间序列"的思维方式看监控数据。一条时间序列由三部分构成:指标名、标签集、采样值。

举个例子,http_requests_total{code="200", method="GET", instance="10.0.0.1:9090"}描述的是:某个实例上、GET 方法、返回码 200 的 HTTP 请求总数。标签的每一个组合都构成一条独立的时间序列。你可以把指标名理解为数据库里的表名,标签理解为这张表上的索引维度,采样值就是某一时刻这条记录的数值。

这个模型最大的好处是维度灵活。同样一份请求量数据,你可以随时按状态码聚合、按实例聚合、按接口聚合,不需要提前设计好多维表结构。查询时用 PromQL 一句话就能算出"所有实例的 P99 延迟"或者"按错误码分组的 QPS"。坏处是维度一旦失控,数据量会指数级膨胀,这就是业内常说的"高基数问题",后面避坑章节专门展开。

2.3 四种指标类型,用错会算错账

Prometheus 的指标在客户端库里分为四种类型,存储层面其实不区分类型(都是浮点数加时间戳),但语义和使用方式必须分清楚,否则写查询时很容易得出错误结论。

类型特点典型场景PromQL 里怎么用
Counter只增不减请求总数、累计错误数必须配 rate() 看速率,不能直接看当前值
Gauge可增可减CPU 使用率、内存、队列长度直接查当前值就有意义
Histogram按桶统计观测值分布请求延迟、响应体大小配 histogram_quantile() 算分位数
Summary客户端算好分位数延迟分位数的快速查看直接查 _quantile 后缀序列

Counter 的值本身通常没意义,关键是变化速率,所以要配合 rate() 函数。Gauge 是状态的快照,当前值就是答案。Histogram 和 Summary 都是处理延迟这类分布型数据的,区别在于一个把原始桶数据暴露给服务端算,一个在客户端就算好。业务指标上我建议优先用 Counter 和 Gauge,延迟类先用 Histogram,等分位数精度要求明确了再评估要不要换 Summary。

3. 架构与组件全景:哪些角色在干活

3.1 组件分工与数据流向

一个完整的使用体系通常由几块拼起来:

  • Prometheus Server:核心,负责抓取、存储、查询、告警规则评估。
  • 各类 Exporter:被监控端的指标暴露器,把第三方系统的内部状态翻译成标准指标格式。
  • Pushgateway:短生命周期任务的指标中转站。
  • Alertmanager:接收告警,做去重、分组、静默、路由,最终送到通知渠道。
  • 可视化层:通常是 Grafana,负责把数据画成仪表盘。

数据流很简单:Exporter 暴露接口,Prometheus 定时抓取,写入自带的时序数据库,查询时用 PromQL 去取,告警条件命中后推给 Alertmanager 分发。很多人会问,为什么不像传统方案一样接一个外部分布式存储?因为 Prometheus 默认把数据存在本地磁盘,单体架构,通过复制和远程存储协议解决高可用和持久化的扩展。对小团队来说,单机完全能跑,这也是它的一大优势:不像很多监控系统一开始就要设计集群,它可以先用最简单的方式跑起来,数据量上去了再考虑扩展。

3.2 Exporter:被监控对象的翻译官

Exporter 是很有意思的一层设计。被监控的对象往往自己不知道 Prometheus 的存在,比如数据库、消息队列、Web 服务器,它们各有各的状态接口,但格式五花八门。Exporter 做的事情就是在这两者之间做翻译:主动去读这些系统的状态接口,转成标准文本格式,然后开一个 HTTP 端口等待被抓取。

常见的 Exporter 有:监控宿主机系统指标的 Node Exporter(CPU、内存、磁盘、网络);针对数据库的连接数、慢查询等指标的数据库 Exporter;还有一堆针对中间件的现成 Exporter。对于自研的应用,一般不需要单独部署 Exporter,直接在代码里引入 Prometheus 客户端库,自己暴露/metrics接口就行。主流的 Java、Go、Python、Node.js 语言都有对应的库,用法也不复杂,就是在关键业务路径加指标埋点。

我习惯的做法是:每个微服务至少暴露三个基础设施——请求速率、错误率、延迟分布。这样无论哪个服务出问题,都能第一时间通过仪表盘定位到大方向,再顺着日志和链路追踪往下钻。

3.3 Pushgateway:用之前先想清楚

Pushgateway 是用来接收 Push 的中转站,但它是个争议组件。它解决的问题是 Pull 模型够不着的场景:批量任务、跑几分钟的定时脚本、防火墙隔离的临时端口任务。这些任务太短命,Prometheus 压根来不及抓,于是脚本把结果推到 Pushgateway,Prometheus 再从 Pushgateway 拉。

但很多团队把 Pushgateway 当成"一劳永逸"的接入方案,任何数据都想往它那里推。这几个隐患必须知道:第一,数据推过去后长期留在序列里,即使任务早就不跑了,序列也不会自己消失,指标垃圾越积越多;第二,因为 Push 是单向的,Prometheus 判断不了被监控对象是否真的活着——只要 Pushgateway 还活着,Prometheus 就以为数据有效;第三,多个实例往同一个 metric 推送时容易互相覆盖,数据一乱,告警也跟着误报。

我个人的经验是默认不用它。只有当任务是真正的短生命周期批处理,且没有别的办法时才接入,并且必须配合清理机制:任务跑完后主动调用删除接口把自己的序列清掉,或者定时清理过期数据。

4. 从零搭一套监控环境:部署与配置实操

4.1 最小闭环:启动一个 Prometheus

Prometheus 非常轻,解压即用。二进制包又一次体现了这个项目对易用性的偏执。以 Linux 为例,下载官方 release 的压缩包解压之后,目录里就是一个可执行文件和默认配置。

先写一个最简的配置文件prometheus.yml:

global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: prometheus static_configs: - targets: ['localhost:9090']

这里只有两个配置段。scrape_interval决定每隔多久抓一次目标;evaluation_interval决定每隔多久计算一次告警规则。scrape_configs定义要抓哪些目标,这里先用 Prometheus 自带的/metrics接口做自监控。

启动命令很简单:

./prometheus --config.file=prometheus.yml --storage.tsdb.retention.time=30d

然后浏览器打开localhost:9090,看到自带表达式浏览器后,在查询框输入prometheus_http_requests_total,点执行,就能看到这个服务自身的请求计数。到这里,最小环境已经起来。这个过程细节不多,但有一个点值得琢磨:为什么设计成解压即用?因为它不想在部署环节引入外部依赖,对新手友好,也方便生产里用进程管理器或容器快速跑起来。

4.2 配置文件里真正要精读的参数

配置是 Prometheus 最需要精读的部分。除了上面的两个 interval,还有几个关键参数要在真正部署前弄明白:

  • scrape_timeout:单次抓取的超时时间,必须小于scrape_interval,否则会导致抓取排队和重叠。
  • scrape_interval设多少合适?我的经验是 15 秒是大多数场景的黄金值。小于 5 秒会明显增加存储和网络开销;对于秒级波动很快的指标,比如需要精确到秒的 QPS,可以设 5 秒,但存储开销至少翻三倍。
  • rule_files:存放告警规则的文件列表。
  • alerting:Alertmanager 的地址配置。
  • storage.tsdb.retention.time:数据保留时间,默认 15 天。生产环境要根据磁盘容量和业务需求综合决定,如果接了远程存储,本地可以留短一些。

每个scrape_config里可以配置多组任务。组内除了静态目标,还有更常用的服务发现机制。一个典型是文件发现:

scrape_configs: - job_name: myapp file_sd_configs: - files: - /etc/prometheus/targets/*.json refresh_interval: 30s

只要运维往这个目录里放目标文件,Prometheus 每 30 秒自动刷新目标列表,完全不用重启服务。这在机器做弹性伸缩、实例 IP 不固定的场景里特别省事。很多第一次用的人不理解为什么配置文件里看不到全部机器列表,就是被这个机制卡过——记住,动态环境里目标是活的,不是写在配置文件里写到老的。

4.3 接入第一个真实监控目标

假设要监控一台 Linux 主机,做法是下载 node_exporter 解压运行:

./node_exporter --web.listen-address=:9100

这个进程会暴露宿主机的大量指标,比如 CPU、内存、磁盘、网络。然后在配置文件里加:

scrape_configs: - job_name: node static_configs: - targets: ['192.168.1.10:9100']

重启 Prometheus 后,在界面里选到node_cpu_seconds_total,就能看到该主机的 CPU 曲线。到这里,你已经完整走通了"暴露指标-抓取-查询"的主链路。这个例子的意义在于:从零到看到第一张曲线图,整个过程没有任何绕路。这也是它能在团队里快速推广的原因——学习成本的天花板不在部署,而在后面写查询和设计指标。

5. PromQL 查询实战:把数据变成答案

5.1 选择序列和过滤标签

PromQL 是 Prometheus 的查询语言,也是新手最容易觉得有门槛的地方。我总结的经验是:它不复杂,只是大部分人一开始被函数和聚合的排列组合吓到了。先掌握最核心的三件事——选择序列、过滤标签、聚合。

选择序列靠指标名加标签匹配大括号。比如:

http_requests_total{code="200"} http_requests_total{code=~"5.."} http_requests_total{instance=~"10\\.0\\.0\\..*"}

第一行是精确匹配,第二行用正则匹配所有 5 开头的状态码,第三行用正则匹配一个网段内的实例。标签匹配规则就这么几种:等于、不等于、正则匹配、正则不匹配。

聚合操作负责把多序列合成一条。最常用的是sum、avg、min、max,配合by或without指定保留的标签维度:

sum(rate(http_requests_total[5m])) by (code)

这条查询的含义是:按状态码分组,计算最近 5 分钟的每秒请求增量,也就是每个状态码对应的 QPS。注意这里的rate()是 Counter 类型指标专用的函数,它会自动处理实例重启时计数器清零带来的跳变问题,这是 PromQL 里比较精巧的设计——单看绝对计数经常被重启误导,而 rate 能自动规避。

5.2 高频函数与常用计算套路

把工作中最常用的一批函数掌握住,PromQL 就算过关了。除了rate,还有这些出现频率极高:

  • irate():基于最后两个采样点计算瞬时速率,适合快变化指标,曲线锯齿更多,但响应快。
  • increase():计算一段时间内的总量增量,比如increase(http_requests_total[1h])就是过去一小时的请求总数。
  • histogram_quantile():从 Histogram 桶数据反推分位数。
  • topk()和bottomk():取前几名和后几名,排障时特别好用。
  • >,<这类阈值比较,通常配合告警。

一个几乎必用的套路是计算服务错误率:

sum(rate(http_requests_total{code=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))

分子是 5xx 的速率,分母是所有请求的速率,一除就是错误率。这个值超过阈值就触发告警。另一个套路是延迟分位数:

histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))

histogram_quantile是把桶的累计计数反向处理,再根据目标分位点做线性插值估算,所以算出来是近似值。桶的边界划分越细,估算越准,但存储开销也越大。

5.3 写查询的三个好习惯

实际写查询时,有几个习惯能帮你少走弯路:

一是永远先确认指标类型。Counter 直接看当前值会让人误判,Gauge 用 rate 会得到无意义的波动。在查询框输入指标名后会自动显示类型和帮助描述,先看一眼再写表达式。

二是区间向量和时间范围别搞混。rate(xxx[5m])中括号里的时间范围决定了速率计算的窗口,窗口太短抖动大,窗口太长反应慢。日常排障用 5 分钟窗口,看长期趋势用 1 小时窗口。

三是正则匹配的转义很容易错。PromQL 的正则基于 RE2,点号要写成\\.,在 YAML 配置里写还要再转义一层,新手经常在这里被折磨。一个建议:能用精确匹配就少用正则,正则用得越多,查询越难维护。

四是先在小范围验证再放开。调新的查询时,先用一个已知实例,比如instance="具体IP:端口",确认数据形态正确,再逐步放宽标签条件。直接从全量数据开始调,一旦出错很难判断是表达式问题还是数据本身问题。

6. 告警与可视化的完整闭环

6.1 告警规则与 Alertmanager 怎么配合

采集了指标、能查了,监控系统还差最后一步闭环:出问题时得有人知道。Prometheus 的告警分成两段:Prometheus Server 负责规则评估,算出"哪些表达式命中";Alertmanager 负责把命中的告警送到对应的人手里。

告警规则写在专门的规则文件里,典型结构:

groups: - name: example rules: - alert: InstanceDown expr: up == 0 for: 5m labels: severity: critical annotations: summary: "实例 {{ $labels.instance }} 不可用"

一个规则里最关键的是expr(触发条件表达式)、for(持续时间条件)、labels(附加标签)、annotations(告警内容模板)。for的作用是防止瞬时抖动误报——指标只在某一瞬间满足条件,还没持续够时长就不算数。

Alertmanager 的配置包含三块主要逻辑:route路由规则决定告警来了走哪个分支发出去,通常根据严重级别或团队标签分组;receivers接收人定义通知渠道,比如邮件、企业微信、钉钉、Webhook;inhibit_rules抑制规则,比如主机宕机时,就不用再发一堆该主机上所有服务的关联告警。把告警理解为"分诊台"就对了:Prometheus 负责喊"有人出问题了",Alertmanager 负责判断"该喊谁、怎么喊、要不要合并喊"。

6.2 通知分组参数怎么调

接一个 Webhook 通知为例,Alertmanager 配置:

route: group_by: ['alertname'] group_wait: 30s group_interval: 5m repeat_interval: 4h receiver: webhook receivers: - name: webhook webhook_configs: - url: 'http://notification-service:8080/alert' send_resolved: true

group_by指定哪些标签相同的告警归为一组,group_wait决定同组告警积压多久再一起发,group_interval是组内新增告警的发送间隔,repeat_interval是告警没恢复时多久重复发一次。这些参数直接决定告警的轰炸程度。经验值上:group_wait30 秒左右、repeat_interval4 小时起步是大部分团队选用的节奏。重复间隔太短会把值班人的手机打成筛子,太长则让持续问题错过提醒。send_resolved: true也很关键,恢复通知能极大减少值班时的猜测成本。

6.3 用 Grafana 搭仪表盘的提效技巧

Grafana 和 Prometheus 是黄金搭档。Grafana 本身没有监控能力,它从 Prometheus 查询数据,专门负责画仪表盘。为什么推荐它而不是直接用 Prometheus 自带的界面?因为成熟团队的仪表盘不是一张图,而是一整套视图分层:全局总览、服务列表、单服务的详细面板、单机的硬件状态,每一层都要有对应的查询脚本和布局,Grafana 的模板化和变量体系让这套东西可维护。

实操中最常用的是 Grafana 的变量功能。比如做了一个"服务 QPS"面板,可以加一个label_values(http_requests_total, job)变量,下拉框里就能动态切换服务,一张面板通吃所有服务的 QPS 查看。这个能力能极大减少重复建面板的工作量,是我在团队推广时强调的第一个提效技巧。

从告警到可视化,监控才形成完整闭环:指标暴露到系统、采集进 Prometheus、规则匹配触发告警、Alertmanager 分诊通知、值班人员在仪表盘里顺着视图定位根因。

7. 生产环境避坑指南

7.1 高基数:最隐蔽的杀手

生产环境里最坑的问题,是下面要讲的高基数(high cardinality)。先明确概念:高基数不是一个指标参数问题,而是标签产生的序列组合数爆炸。

举个例子,有个业务指标http_request_duration_seconds,如果加一个user_id标签,每个用户都成为一条独立序列。假设用户量一万,这个指标就是一 万条序列;每个采集周期都会写入一万个新样本,存储和查询压力立刻飙升。更恐怖的是,如果配合每次请求的随机值这样的标签,序列数量会看不到尽头。

高基数的杀伤力是潜移默化的:早期看不出问题,等时间序列攒到几千万条时,系统会变得很慢,查询超时,内存飙升,甚至直接影响抓取和告警评估。规避手段有这几条:

  • 标签值必须是低基数集合。状态码(几十个)、实例名(几十个)、接口名的聚合分组(几百个上限)都没问题。用户 ID、订单 ID、设备 ID 这些必须毫不犹豫地砍掉。
  • 如果确实需要按具体业务维度细分,优先考虑用日志系统或链路追踪体系承担,而不是把监控系统变成一把到处乱用的锤子。
  • 定期观察prometheus_tsdb_head_series这个内置指标,它反映当前活跃的时间序列数。持续上涨就意味着高危,得尽快找到是哪条指标在膨胀。

7.2 存储与持久化的规划

Prometheus 默认把数据存在本地磁盘,单机模式下数据量大了之后,备份和容灾都是要提前规划的事。建议从第一天就想清楚三个问题。

第一个问题是磁盘容量估算。经验判断基准:每个样本大约 1 到 2 字节,按抓取间隔 15 秒算,如果单实例有一百万条序列,一天的数据量大约是 6 到 12 GB。按这个量级去配磁盘,能覆盖绝大多数中等规模业务。

第二个问题是保留时长。默认 15 天,对于"排查一个月前的问题"这种需求远远不够。我习惯把本地保留时长按业务回溯需求设置成 30 天或者更长,同时配合远程存储做冷数据归档。如果用了云平台托管方案,则把本地留短一些,省磁盘。

第三个问题是重启和数据损坏。非正常关机或磁盘满时,本地存储可能出现损坏。这时不能只靠一个节点硬扛,要用多个副本或联邦架构分散风险。至少做到定期给数据目录做快照备份,并且不要在磁盘快满时运行,否则指标时间轴会直接断掉,监控出现"空窗期"反而错过故障。

7.3 常见问题与排查思路速查

我在维护生产监控环境时沉淀了一张问题排查表,分享给你们参考:

现象排查路径
抓取目标显示 DOWN先查网络连通性,再用curl http://IP:端口/metrics手动访问,确认接口是否正常返回
查询返回空结果先用最简单指标名确认序列存在,再一步步加标签条件,排除条件组合问题
告警不触发检查规则文件是否被加载,检查 Alertmanager 地址和路由配置,确认for持续时间是否太长
内存占用持续走高观察活跃序列数是否上涨,判断是否打上了高基数标签
抓取超时检查超时值是否小于间隔值,再看目标接口本身的响应时间

这套速查表解决的 90% 的现场问题,剩下的基本都能从日志里找到线索。Prometheus 日志默认输出到标准输出,排查时先看日志永远是对的。

7.4 几条不写进文档的实践经验

最后分享几条我的经验,这几条不写在官方文档里,但每一条都来自真实翻车现场。

第一,告警规则要"小而准",不要一上来就搞一个大而全的表达式。把每个告警拆成最小可验证单元,比如up == 0单独一条,错误率阈值单独一条。这样触发时定位更快,也不会因为依赖关系相互掩盖。太复杂的告警表达式,出问题的时候没人能一眼看懂它为什么报,反而增加值班压力。

第二,在搭建监控时就要把指标命名约定立好。命名统一成"应用名_模块名_指标名"的层级,标签语义保持一致,能用小写字符就绝不用大写。命名混乱的监控体系,半年之后会变成一座没人敢动的粪山,这是无数团队的真实写照。

第三,测试环境里一定要做告警演练。把节点直接杀掉、把服务停掉,看告警链路从采集到通知是否顺畅,看分组、抑制是否正确。很多配置只有在真实故障场景里才会暴露问题,等线上出事再发现告警漏掉,那才是最被动的局面。

我个人的体会是,Prometheus 这个项目最难得的地方,不是单个功能有多惊艳,而是把"抓取-存储-查询-告警"四件事的边界划得非常清楚。每个组件只干一件事,每件事都可以独立扩展和替换。这种设计让它入门曲线可以做到极低,同时上限又高到足以支撑整个云原生体系的监控底座。哪怕你只是打算自学监控的新人,从它开始学起,往后接触其他监控系统,很多思路都是相通的。最后再留一个小建议:别急着把文档里所有功能都部署一遍,先用最小闭环跑通你的第一个业务指标,再慢慢往体系里加东西,复杂度会随着你的理解自然消化掉。

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

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

立即咨询