第三章 时序数据库入门:Prometheus 架构、TSDB 核心概念、数据模型与标签设计
好,咱们进入第三章。这一章聊的是时序数据库,具体说就是 Prometheus。说实话,我刚开始接触运维的时候,对时序数据库的理解就是“存时间序列数据的数据库”,听起来像废话对吧?但真正用起来,你会发现它跟传统的关系型数据库完全是两码事。
我记得第一次在生产环境部署 Prometheus,当时心里还挺没底的。后来踩了几个坑,才慢慢摸清楚它的脾气。今天我就把 Prometheus 的架构、TSDB 的核心概念,还有数据模型和标签设计,掰开了揉碎了讲给你听。
3.1 Prometheus 整体架构
Prometheus 的架构,说白了就是一个“拉”的模式。它主动去目标机器上抓取指标数据,而不是等别人推过来。这一点跟很多传统监控系统不一样。
整个架构可以分成几个核心组件:
- Prometheus Server:核心服务,负责数据采集、存储和查询
- Exporters:暴露指标的代理程序,比如 node_exporter 采集机器指标
- Pushgateway:用于短期任务的指标推送网关
- Alertmanager:告警管理组件
- Web UI / Grafana:可视化展示
我习惯把 Prometheus 比作一个“勤劳的巡检员”。它每隔一段时间就去各个机器上转一圈,把数据记在本子上。你想想看,这种方式的好处是什么?
嗯,最大的好处就是故障隔离。如果某台机器挂了,Prometheus 只是抓不到它的数据,不会影响整个系统。我曾经见过一个用推模式做的监控系统,推送端一崩,整个监控链路都断了,那叫一个惨。
核心要点:Prometheus 采用 Pull 模式,每个被监控的目标独立暴露 HTTP 端点,Server 定期拉取数据。这种设计天然支持水平扩展和故障隔离。
3.2 TSDB 核心概念
TSDB,全称 Time Series Database,时序数据库。它跟普通数据库最大的区别在于:数据天生带有时间戳,而且写入基本上是追加模式,很少修改。
Prometheus 内置的 TSDB 有几个关键概念,我一个个说:
3.2.1 时间序列(Time Series)
一个时间序列,就是一组带时间戳的数据点。每个序列由指标名 + 标签集合唯一标识。比如:
node_cpu_seconds_total{instance="192.168.1.1:9100", mode="idle"}
这就是一个时间序列。它记录了某个节点上 CPU 空闲时间的累计值。
3.2.2 样本(Sample)
样本是时序数据的最小单位。一个样本包含:
- 一个时间戳(毫秒精度)
- 一个值(float64)
说白了,就是“在某个时刻,某个指标的值是多少”。
3.2.3 Block 与 Compaction
Prometheus 的 TSDB 会把数据分成一个个 Block。每个 Block 默认存储 2 小时的数据。这些 Block 是不可变的,写入后就不会再修改。
然后有个后台进程叫 Compaction,它会定期把小 Block 合并成大 Block。这样做的好处是:
- 减少文件数量
- 提高查询效率
- 节省存储空间
我在项目中遇到过一个问题:某个集群的磁盘写入量突然暴增。查了半天,发现是 Compaction 频率太高了。后来调整了 --storage.tsdb.retention.time 和 --storage.tsdb.retention.size 参数,才把问题解决。
小技巧:如果你的 Prometheus 写入压力很大,可以适当调大 Block 的时长。默认 2 小时,我一般会改成 4 小时,能减少 Compaction 的频率。
3.3 数据模型与标签设计
这一节我觉得是 Prometheus 最核心的地方。数据模型设计得好不好,直接决定了你后续的查询和告警体验。
3.3.1 指标类型
Prometheus 支持四种指标类型:
| 类型 | 说明 | 典型场景 |
|---|---|---|
| Counter | 只增不减的计数器 | 请求总数、错误总数 |
| Gauge | 可增可减的测量值 | CPU 使用率、内存使用量 |
| Histogram | 直方图,统计分布 | 请求延迟分布 |
| Summary | 类似 Histogram,但计算分位数 | 请求延迟的 P99 |
嗯,这里要注意:Counter 不能用来算平均值。我见过有人用 Counter 类型的指标直接除时间算速率,结果算出来全是错的。正确的做法是用 rate() 函数。
3.3.2 标签设计原则
标签是 Prometheus 数据模型的灵魂。设计得好,查询起来行云流水;设计得不好,那就是一场灾难。
我总结了几条原则:
- 标签值不要太多:每个标签的取值数量最好控制在 100 以内。比如
instance标签,如果机器数量上万,那查询性能会急剧下降。 - 不要用标签存动态数据:比如把用户 ID、订单 ID 这种高基数数据放进标签。我曾经见过一个系统,把请求 ID 当标签,结果 Prometheus 直接 OOM 了。
- 标签名要有意义:别用
a、b、c这种无意义的命名。我习惯用service、instance、job这种标准命名。 - 保持标签一致性:同一个标签在不同指标中含义要一致。比如
env标签,在所有指标中都表示环境(prod/staging/dev)。
避坑指南:我曾经在一个项目中,把容器 ID 作为标签。结果容器频繁重启,每次重启 ID 都变,导致标签基数爆炸。Prometheus 的内存占用从 2GB 飙到了 16GB。后来我改用 pod_name 代替 container_id,问题才解决。
3.3.3 标签设计实战
给你看一个我常用的标签设计模板:
# 系统指标
node_cpu_seconds_total{job="node_exporter", instance="10.0.0.1:9100", env="prod", dc="us-east-1"}
# 应用指标
http_requests_total{job="api-server", instance="10.0.0.2:8080", service="user-service", version="v2.1.0"}
# 业务指标
order_total{job="order-service", instance="10.0.0.3:8080", payment_method="credit_card", currency="USD"}
你看,每个指标都带了 job 和 instance 这两个基础标签。然后根据场景,再加一些业务相关的标签。这样设计的好处是:
- 查询时可以灵活过滤
- 告警规则可以按标签分组
- Grafana 面板可以动态切换
3.4 查询语言 PromQL 入门
PromQL 是 Prometheus 的查询语言。说实话,刚开始学的时候觉得挺别扭的,因为它跟 SQL 完全不一样。但用习惯了,你会发现它特别适合时序数据的分析。
几个最常用的操作:
# 查询所有 CPU 使用率
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 查询最近 1 小时的请求总数
sum(rate(http_requests_total[1h]))
# 查询某个服务的 P99 延迟
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
我建议你刚开始不要追求复杂的查询。先把 rate()、sum()、avg() 这几个基础函数用好,后面再慢慢学 histogram_quantile() 这种高级函数。
小技巧:Prometheus 自带的 Web UI 里有个“Graph”页面,可以实时调试 PromQL。我写复杂查询的时候,都是先在那里试,确认没问题了再放到 Grafana 里。
3.5 本章小结
这一章我们聊了 Prometheus 的架构、TSDB 的核心概念,还有数据模型和标签设计。说白了,Prometheus 的设计哲学就是“简单、可靠、可扩展”。
我个人觉得,学 Prometheus 最重要的不是背命令,而是理解它的数据模型。标签设计得好,后续的查询、告警、可视化都会很顺畅。设计得不好,那就是给自己挖坑。
下一章我们会聊告警规则的设计,到时候我会分享一些我在生产环境中踩过的坑,保证让你少走弯路。