4、指标采集与Prometheus:从架构到告警,一套完整的可观测体系

大家好,我是你们的运维老司机。今天咱们聊聊监控体系里最核心的一环——Prometheus。说实话,我入行那会儿,监控还是Zabbix和Nagios的天下,后来Prometheus一出来,整个生态都变了。它不只是一个工具,更像是一套哲学:拉模式采集、多维数据模型、强大的PromQL查询语言……嗯,今天我就把这些年踩过的坑、总结的经验,一次性讲透。

4.1 Prometheus架构与核心概念

先看一张图,这是我手绘的Prometheus核心架构,你品,你细品。

Prometheus 核心架构图 数据源 Exporter / 应用 / Pushgateway HTTP pull Prometheus Server 检索器 · 存储引擎 · HTTP API 告警评估 · 规则管理 告警推送 Alertmanager 去重 · 分组 · 路由 · 静默 通知 邮件 / 钉钉 / Slack PromQL查询 Grafana / API 客户端 本地存储 (TSDB) 读写 远程存储 (可选) 远程读写 服务发现 (K8s / Consul / DNS) 动态发现

这张图我画了好几个版本,最终定稿成这样。你看,Prometheus Server是绝对的核心,它通过HTTP pull的方式从各种Exporter拉取指标。为什么是拉而不是推?我个人觉得,拉模式让监控系统掌握了主动权——你想什么时候采集、采集哪些目标,都由Server说了算。这在微服务架构里特别重要,你想想看,如果每个服务都主动推数据,万一某个服务挂了,数据就丢了。而拉模式,Server发现目标挂了,还能记录一条"目标不可达"的告警。

4.2 Exporter开发与使用

Exporter说白了就是一个HTTP服务,暴露/metrics端点,返回Prometheus能识别的文本格式数据。我最早接触Exporter是在监控MySQL的时候,用官方的mysqld_exporter,部署起来很简单:

# 下载并启动MySQL Exporter
wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.14.0/mysqld_exporter-0.14.0.linux-amd64.tar.gz
tar xzf mysqld_exporter-0.14.0.linux-amd64.tar.gz
cd mysqld_exporter-0.14.0.linux-amd64

# 配置数据库连接信息
export DATA_SOURCE_NAME='user:password@tcp(localhost:3306)/'

# 启动
./mysqld_exporter --web.listen-address=:9104

启动后访问http://localhost:9104/metrics,你会看到类似这样的输出:

# HELP mysql_global_status_connections MySQL global status connections
# TYPE mysql_global_status_connections gauge
mysql_global_status_connections 1234

# HELP mysql_global_status_threads_connected MySQL global status threads connected
# TYPE mysql_global_status_threads_connected gauge
mysql_global_status_threads_connected 45

嗯,这里要注意,Exporter的指标命名是有规范的。我见过不少团队自己写Exporter,命名随意得很,什么my_app_cpu_usagemy_app_mem……这其实不好。Prometheus官方推荐用namespace_subsystem_metricname的格式,比如mysql_global_status_connections。这样在PromQL里查询时,可以方便地用正则匹配。

小技巧: 如果你需要监控的业务指标比较特殊,比如订单系统的"每秒订单数",建议用官方提供的client库来开发自定义Exporter。Python、Go、Java都有对应的库。我个人习惯用Go写,性能好,部署也简单。

4.2.1 自定义Exporter示例

下面是一个用Go写的简单Exporter,监控一个假想的"订单处理延迟"指标:

package main

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    orderLatency = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "order_processing_latency_seconds",
        Help: "Current order processing latency in seconds",
    })
)

func init() {
    prometheus.MustRegister(orderLatency)
}

func main() {
    http.Handle("/metrics", promhttp.Handler())
    http.ListenAndServe(":8080", nil)
}

这段代码虽然简单,但已经是一个完整的Exporter了。你只需要在业务逻辑里更新orderLatency的值,Prometheus就能拉取到。

避坑指南: 我曾经遇到过一个坑——Exporter的/metrics端点返回的数据量太大,导致Prometheus拉取超时。原因是某个指标加了过多的标签,比如把每个用户的ID都作为一个标签值。记住,标签的基数(cardinality)一定要控制,否则TSDB会爆炸。一般建议标签值不超过10万种。

4.3 PromQL查询语言入门

PromQL是Prometheus的灵魂。说实话,刚开始学PromQL的时候,我觉得它有点反人类——没有SQL那种直观的SELECT FROM WHERE。但用久了你会发现,它其实是为时序数据量身定做的。

先看几个最常用的查询:

查询目的 PromQL表达式 说明
查看当前CPU使用率 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) rate函数计算每秒增长率,5m是时间窗口
查看内存使用量 node_memory_MemTotal_bytes - node_memory_MemFree_bytes - node_memory_Buffers_bytes - node_memory_Cached_bytes 直接做减法,单位是字节
查看过去1小时的平均延迟 avg_over_time(http_request_duration_seconds[1h]) avg_over_time是聚合函数
查看错误率超过5%的服务 rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05 用正则匹配5xx状态码

你可能会问,为什么CPU使用率要这么复杂?直接看node_cpu_seconds_total不行吗?嗯,这里有个关键点——node_cpu_seconds_total是一个Counter类型的指标,它只会不断增加。要得到"每秒使用率",必须用rate()函数计算变化率。这是PromQL里最容易犯错的地方,我刚开始也搞混过。

核心要点: PromQL有四种指标类型:Counter(只增不减)、Gauge(可增可减)、Histogram(直方图)、Summary(摘要)。Counter必须用rate()或increase()函数,Gauge可以直接取值。记住这个,能避免80%的查询错误。

4.4 告警规则配置(Alertmanager)

告警是监控的最终目的。Prometheus的告警分为两步:先在Prometheus Server里定义告警规则,再由Alertmanager负责发送通知。

告警规则的写法是这样的:

groups:
- name: example-alerts
  rules:
  - alert: HighCPUUsage
    expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "Instance {{ $labels.instance }} CPU usage is high"
      description: "CPU usage is {{ $value }}% for more than 5 minutes"

这里有几个关键字段:

  • expr:PromQL表达式,当结果为true时触发告警
  • for:持续时间,比如持续5分钟才告警,避免抖动
  • labels:告警标签,可以用来区分严重级别
  • annotations:告警描述,会发送到通知渠道

Alertmanager的配置稍微复杂一些,它负责路由、分组、去重和静默。我一般这样配置:

route:
  receiver: 'default-receiver'
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
  - match:
      severity: critical
    receiver: 'critical-receiver'

receivers:
- name: 'default-receiver'
  webhook_configs:
  - url: 'http://webhook.example.com/alert'

- name: 'critical-receiver'
  webhook_configs:
  - url: 'http://webhook.example.com/critical-alert'
避坑指南: 我曾经犯过一个错误——把repeat_interval设得太短,比如30分钟。结果某个晚上一个告警重复发送了十几次,值班同事差点把我拉黑。建议至少设4小时以上,除非是P0级别的告警。另外,group_interval也不要太短,否则告警风暴会让你怀疑人生。

最后说一句,告警规则不是越多越好。我见过有些团队配置了上百条告警规则,结果每天告警几百条,最后大家都麻木了。我的建议是:先配最核心的5-10条,比如CPU、内存、磁盘、关键业务接口的延迟和错误率。等稳定了再逐步增加。告警是手段,不是目的——让系统稳定运行才是。

好了,这一章的内容就到这里。Prometheus的生态很庞大,今天我们只讲了最核心的部分。下一章我们会深入Grafana的可视化配置,到时候见。


专注资料整理