3. Prometheus 基础:架构设计、数据模型、Pull vs Push 模式

好,咱们进入监控体系的核心环节——Prometheus。说实话,在云原生时代,Prometheus 几乎成了监控的事实标准。我最早接触它是在 2017 年,当时还在用 Zabbix 和 Open-Falcon,第一次看到 Prometheus 的 Pull 模型,心里想:这玩意儿能行吗?后来真香了。

3.1 整体架构:一张图看懂 Prometheus

先看整体架构。Prometheus 不是一个大而全的“全家桶”,它更像一个生态。核心组件就那么几个,但配合起来非常灵活。

核心思想:Prometheus 主动去“拉”数据,而不是等数据“推”过来。这个设计决策,影响了整个监控体系的构建方式。

Prometheus 核心架构图 Prometheus Server 时序数据库 + 检索 + 告警 目标 1 (exporter) 目标 2 (exporter) 目标 3 (exporter) Alertmanager Grafana API / 其他消费端 核心流程 Prometheus Server 定期从各 exporter 拉取指标 → 存储到本地 TSDB → 触发告警规则 → 推送到 Alertmanager → 通知到人

你看这个图,Prometheus Server 是的核心。它负责三件事:拉数据、存数据、算告警。左侧是各种 exporter,也就是被监控的目标。右侧是消费端,Alertmanager 管告警分发,Grafana 管可视化。

3.2 数据模型:时间序列是核心

Prometheus 的数据模型,说白了就是“带标签的时间序列”。这个概念非常重要,你理解了这个,后面写 PromQL 就顺了。

每条数据长这样:

<metric_name>{<label_name>=<label_value>, ...}  value  timestamp

举个例子:

http_requests_total{method="GET", endpoint="/api/v1/users", status="200"}  1024  1700000000

这里 http_requests_total 是指标名,花括号里是标签,后面是值和时间戳。我刚开始用的时候,总觉得标签就是“可选的元数据”。后来踩了坑才明白——标签决定了时间序列的唯一性。

我曾经踩过的坑:标签值不要用高基数(high cardinality)的数据,比如 user_id、request_id。每个不同的标签值都会产生一条新的时间序列,如果用户 ID 有 100 万,你的 Prometheus 内存就炸了。我见过有人把 session_id 当标签,结果 10 分钟就把 32G 内存打满了。

Prometheus 的数据模型有四种指标类型:

类型 说明 典型场景
Counter 只增不减的计数器 请求总数、错误总数、CPU 时间
Gauge 可增可减的测量值 内存使用率、当前连接数、温度
Histogram 分桶直方图,统计分布 请求延迟分布、响应大小分布
Summary 类似 Histogram,但计算分位数 请求延迟的 p99、p95

个人建议:能用 Counter 就别用 Gauge。Counter 的语义更清晰,而且可以计算速率(rate),这是监控中最常用的操作。我习惯把所有“累计值”都用 Counter 表示,比如 http_requests_total、db_queries_total。

3.3 Pull vs Push:为什么 Prometheus 选择了 Pull?

这个问题,面试经常问,实际选型也经常纠结。我聊聊我的理解。

Pull 模式:Prometheus Server 主动去目标地址拉取指标。目标只需要暴露一个 HTTP 端点(通常是 /metrics)。

Push 模式:目标主动把指标推送到中心收集器。比如 Graphite、OpenTSDB、InfluxDB 都支持 Push。

为什么 Prometheus 选了 Pull?

  • 健康检查更自然:拉不到数据,说明目标挂了。Push 模式下,收不到数据可能是网络问题,也可能是目标挂了,你分不清。
  • 控制权在服务端:你可以控制拉取频率,避免被突发的 Push 流量冲垮。我在项目中遇到过,某个业务模块突然疯狂 Push 数据,直接把收集器打挂了。
  • 更容易做安全控制:只需要在服务端配置认证,不需要每个目标都配。
  • 天然支持静态发现:配置文件里写死目标地址,简单粗暴有效。

但 Pull 模式也有短板:

  • 无法穿透 NAT/防火墙:如果目标在私有网络里,Prometheus 拉不到。这时候需要 Pushgateway 做中转。
  • 不适合短生命周期任务:比如批处理任务、CronJob,跑完就没了,Prometheus 还没拉到数据呢。

我的经验:80% 的场景用 Pull 就够了。剩下的 20%,用 Pushgateway 或者 Prometheus 的 remote write 来解决。别一上来就全上 Push,那是给自己找麻烦。

3.4 服务发现:动态环境下的必备能力

在 Kubernetes 里,Pod 的 IP 是动态的。你不可能手动写死每个 Pod 的地址。这时候就需要服务发现。

Prometheus 支持多种服务发现机制:

发现方式 适用场景 配置复杂度
静态配置 小规模、固定目标 低
Kubernetes SD K8s 集群 中
Consul SD 服务注册中心 中
DNS SD 基于 DNS 的服务发现 低
文件 SD 自定义、灵活 中

我个人最常用的是 Kubernetes SD。配置起来其实不复杂:

scrape_configs:
  - job_name: 'kubernetes-pods'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_label_app]
        action: keep
        regex: my-app

这段配置的意思是:发现所有 K8s Pod,只保留标签 app=my-app 的 Pod 作为抓取目标。嗯,这里要注意,relabel_configs 是 Prometheus 里非常强大的功能,你可以用它来过滤、修改、增加标签。

避坑指南:我曾经在配置 Kubernetes SD 时,忘了设置 __metrics_path__,结果 Prometheus 一直去抓 /metrics,但我的应用暴露在 /actuator/prometheus。折腾了半天才发现。记得检查默认路径。

3.5 存储与数据保留

Prometheus 的本地存储是自研的 TSDB,设计得非常高效。它按 2 小时为一个 block 存储数据,每个 block 包含多个文件。

存储配置很简单:

--storage.tsdb.path=/data/prometheus
--storage.tsdb.retention.time=15d
--storage.tsdb.retention.size=50GB

这里我建议两个参数都设上。只设时间,万一数据量暴增,磁盘可能撑爆。只设大小,可能数据保留时间不够。两个一起设,谁先到就按谁执行。

注意:Prometheus 本地存储是单机的,不适合做长期存储。如果你需要保留 30 天以上的数据,建议用 Thanos 或 VictoriaMetrics 做远端存储。我一般把 Prometheus 本地存 7 天,远端存 6 个月。

3.6 小结

Prometheus 的架构设计,核心就是“主动拉取 + 标签化时间序列”。这个设计让它非常适合云原生环境。Pull 模式给了你控制权,标签模型给了你灵活性,服务发现解决了动态性问题。