up 本身就是信号 — pull 与 TSDB 的设计
一句话总结
Prometheus 选择 pull 的原因不是性能,而是可观测性。服务器主动抓取时,“没有响应”这一事实本身也会成为数据,这就是 up metric。TSDB 会先把收到的 sample 顺序写入 WAL,集中到两小时的 Head Block 中,再 compaction 为持久 block。
为什么需要了解这一点
在 push 模型中,无法区分数据没有到达与进程已经终止,因为两者看起来都是“什么都没有”。pull 模型会留下服务器尝试过的记录,因此失败会成为一个值。
up{job="checkout-api"} == 0
or
absent(up{job="checkout-api"})
考试经常考查这两个条件代表不同情况。up == 0 表示 target 已被发现,但 scrape 失败;absent(up{...}) 表示 target 整体从 discovery 中消失,连 up time series 本身都不存在。漏掉后者,service 被删除时 alert 会悄悄失效。
当然,pull 不是万能的。在 Prometheus 前来抓取之前就结束的短期 batch job 无法通过 pull 捕获,所以才有 Pushgateway 这一例外通道。让例外保持为例外,正是设计要点。
工作原理
存储路径分三层。
| 层级 | 位置 | 特性 |
|---|---|---|
| WAL | wal/ segment(默认 128MB) |
顺序写入,用于 crash recovery |
| Head Block | 内存 + chunks_head/ mmap |
最近约 2 小时,可写入 |
| 持久 block | ULID 目录 | meta.json、index、chunks/、tombstones |
新 sample 先写入 WAL,再附加到 Head Block 的 memSeries。active chunk 达到 120 个 sample 或 2 小时时便会封存,落入 chunks_head/ 的 mmap 文件,由 OS page cache 代为管理内存。compaction 按 level 进行:多个两小时 block 合并成六小时,再合并成十八小时。删除不会立即执行,而是先标记为 tombstone,直到 compaction 时才真正移除。
压缩源自 Gorilla 论文。timestamp 使用 delta-of-delta 存储,scrape 规律时大部分只需 1 bit;value 与前一个值做 XOR,再去掉前后的 0 bit。最终结果是每个 sample 平均 1.37 字节。记住这个数字,容量计算会很容易。
搜索使用 inverted index。每个 label name-value pair 对应的 time series ID 列表(posting list)以排序状态存储,因此能在线性时间内求出 job="prometheus" 与 instance="localhost:9090" 的交集。这既是 label selector 速度快的原因,也是 cardinality 增加时该 index 最先变重的原因。
配置方面,考试关注三点。
- 配置文件变更不会自动检测。 必须发送 SIGHUP,或者启用
--web.enable-lifecycle后向/-/reload发送 POST。 scrape_timeout不能大于scrape_interval。默认值为 10 秒。- retention 不在配置文件中,而是 command line flag(
--storage.tsdb.retention.time,默认 15 天)。与基于大小的限制同时设置时,先达到的一方生效。
然后是 staleness。target 消失后,相应 time series 会附加 stale marker(特殊 NaN)。query 会在 lookback delta(默认 5 分钟)内寻找最新 sample,遇到 stale marker 后便从结果中移除该 time series。准确的说法不是“5 分钟后才消失”,而是“遇到 marker 后立即移除”。
实际工作中的表现
曾有一次同时部署了 2,000 条 recording rule。每条 rule 会按 route 数量生成 time series,结果新增了约 100 万条 time series;它们全部进入 Head Block,导致 WAL 暴增。rule 文件看起来像代码,但从成本角度看,等同于添加新 metric。部署前应养成计算“120 条 route × 5 个 window × 50 条 rule”的习惯。
也可以在采集阶段进行阻止。设置 sample_limit,能够防止单个爆炸的 target 拖垮整个 Prometheus。触发后,该 target 的整次 scrape 会失败,所以值应留有充足余量,但务必要设置。没有上限,一个 service 的失误就可能中断全部 monitoring。
还要建立容量直觉。按每条 time series 约 8KB 计算,1,000 万条就是 80GB。对于 home lab 中 4C/14GB mini PC 组成的 control plane,这个数字就是极限线。
下个练习将做什么
从头编写 /root/pca-scrape/prometheus.yml。加入 global block、static_configs job、通过 kubernetes_sd 查找 Pod 的 job、用 relabel 选择 target 并重组 address 的规则、通过 metric_relabel 丢弃无用 metric 的规则,以及 sample_limit guardrail。最后把该文件作为 ConfigMap 放入 cluster,并创建一个会被刚才所写 keep 规则实际保留的 Pod 来确认结果。