4. 时间序列数据库入门:Prometheus架构、数据模型、PromQL基础查询
说到监控系统,就绕不开时间序列数据库。我最早接触监控时,用的还是RRDtool那套轮询存储方案,后来转到Prometheus,说实话,第一次看到它的数据模型时,有种「原来监控数据可以这样组织」的感觉。这一章,我们就来拆解Prometheus的核心设计。
4.1 Prometheus整体架构
Prometheus的架构,说白了就是「拉」模式。跟传统推模式不同,它主动去目标机器上抓取指标。这个设计看似简单,但解决了分布式系统中数据一致性的很多麻烦。
核心组件一览:
- Prometheus Server:负责数据采集、存储和查询
- Exporters:暴露指标数据的代理程序
- Pushgateway:处理短生命周期任务的指标推送
- Alertmanager:告警路由和去重
- Service Discovery:自动发现监控目标
我刚开始搭建Prometheus时,犯过一个低级错误——把所有Exporters的端口都暴露在公网上。嗯,这里要注意,Exporters默认没有任何认证机制,生产环境一定要用防火墙或反向代理保护起来。
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等函数使用
- 写查询时先过滤再聚合,性能会更好