4. 时间序列数据库入门:Prometheus架构、数据模型、PromQL基础查询

说到监控系统,就绕不开时间序列数据库。我最早接触监控时,用的还是RRDtool那套轮询存储方案,后来转到Prometheus,说实话,第一次看到它的数据模型时,有种「原来监控数据可以这样组织」的感觉。这一章,我们就来拆解Prometheus的核心设计。

4.1 Prometheus整体架构

Prometheus的架构,说白了就是「拉」模式。跟传统推模式不同,它主动去目标机器上抓取指标。这个设计看似简单,但解决了分布式系统中数据一致性的很多麻烦。

核心组件一览:

  • Prometheus Server:负责数据采集、存储和查询
  • Exporters:暴露指标数据的代理程序
  • Pushgateway:处理短生命周期任务的指标推送
  • Alertmanager:告警路由和去重
  • Service Discovery:自动发现监控目标

我刚开始搭建Prometheus时,犯过一个低级错误——把所有Exporters的端口都暴露在公网上。嗯,这里要注意,Exporters默认没有任何认证机制,生产环境一定要用防火墙或反向代理保护起来。

Prometheus 核心架构图 Exporters Node Exporter / MySQL Exporter Pushgateway 短任务指标推送 Service Discovery Kubernetes / Consul Prometheus Server 数据采集 / 存储 / 查询 TSDB 引擎 Alertmanager Grafana 可视化 API / 客户端 核心流程:Exporters/Pushgateway 暴露指标 → Prometheus Server 主动拉取 → 存储/告警/可视化

4.2 数据模型:标签才是灵魂

Prometheus的数据模型,我个人的理解是「带标签的时序数据流」。每条时间序列由指标名和一组标签唯一标识。

# 基本格式
<metric_name>{<label_name>="<label_value>", ...}

# 示例
http_requests_total{method="GET", endpoint="/api/users", status="200"}

你想想看,传统监控系统里,每个指标就是一个独立的文件或表。但在Prometheus里,http_requests_total 只是一个名字,真正区分数据的是后面的标签组合。这意味着你可以用一条查询,就拿到所有维度的聚合数据。

概念 说明 示例
Metric Name 指标名称,描述监控对象 node_cpu_seconds_total
Labels 键值对标签,标识维度 {cpu="0", mode="user"}
Sample 时间戳 + 值 @1620000000 1234.5
Time Series 指标名 + 标签组合的唯一序列 上述三者的组合

避坑指南:我曾经在生产环境遇到过一个问题——某个服务的指标标签里包含了用户ID,结果每个用户都生成一条独立的时间序列。Prometheus的存储压力瞬间飙升。记住,标签基数(Cardinality)是设计时就要考虑的关键指标,高基数标签(如用户ID、请求ID)千万不要直接作为标签。

4.3 四种指标类型

Prometheus的客户端库提供了四种指标类型。我刚开始学的时候,总觉得Counter和Gauge差不多,后来踩过坑才明白它们的区别。

  • Counter:只增不减的计数器。适合记录请求总数、错误次数。重启会归零。
  • Gauge:可增可减的仪表盘。适合内存使用率、CPU温度、在线人数。
  • Histogram:直方图。统计分布情况,比如请求延迟的百分位数。
  • Summary:摘要。类似Histogram,但由客户端计算分位数。

我记得有一次排查线上问题,发现某个服务的错误率曲线一直在抖动。后来发现是用了Gauge来记录错误次数,而Gauge在进程重启后会丢失历史值。换成Counter后,配合 rate() 函数,曲线就平滑了。

4.4 PromQL基础查询

PromQL是Prometheus的查询语言。说实话,刚开始看它的语法会觉得有点怪,但用顺手后会发现它非常强大。

4.4.1 即时查询与范围查询

# 即时查询:返回当前最新值
node_memory_MemAvailable_bytes

# 范围查询:返回过去5分钟的数据
node_memory_MemAvailable_bytes[5m]

范围查询返回的是多个时间点的样本,不能直接用于计算,需要配合聚合函数使用。

4.4.2 常用函数

函数 作用 示例
rate() 计算Counter的每秒增长率 rate(http_requests_total[1m])
irate() 计算瞬时增长率(对短时间窗口敏感) irate(node_network_receive_bytes_total[30s])
avg_over_time() 计算一段时间内的平均值 avg_over_time(node_load1[5m])
sum() 按标签维度求和 sum(rate(http_requests_total[1m])) by (status)

注意:rate() 只能用于Counter类型,不能用于Gauge。我曾经看到有人用 rate(node_memory_MemAvailable_bytes[5m]),结果查出来的数据完全没意义。Gauge的变化率应该用 deriv() 函数。

4.4.3 标签过滤与聚合

# 精确匹配
node_cpu_seconds_total{cpu="0", mode="user"}

# 正则匹配
node_cpu_seconds_total{mode=~"user|system"}

# 排除匹配
node_cpu_seconds_total{mode!="idle"}

# 按CPU聚合
sum(rate(node_cpu_seconds_total[1m])) by (cpu)

我个人习惯在写PromQL时,先用 {} 过滤掉不必要的数据,再做聚合。这样可以减少计算量,查询速度会快很多。

4.5 实战:一条完整的监控查询

假设我们要监控每台机器的CPU使用率。嗯,这里要注意,CPU使用率不能直接用 node_cpu_seconds_total,因为它是一个累加值。

# 计算每台机器的CPU使用率(排除idle模式)
100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) by (instance) * 100)

# 或者更常见的写法
(1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) by (instance)) * 100

这条查询的逻辑是:先计算idle时间的增长率,然后用1减去它,再乘以100得到百分比。我在项目中用这个表达式配合Grafana,做了几十个CPU监控面板,效果一直很稳定。

核心要点回顾:

  • Prometheus采用拉模式架构,核心是TSDB引擎
  • 数据模型由指标名+标签唯一标识,注意控制标签基数
  • 四种指标类型各有用途,Counter和Gauge最容易混淆
  • PromQL的核心是即时查询和范围查询,配合rate/sum等函数使用
  • 写查询时先过滤再聚合,性能会更好