← 返回 Day 177 正文

Day 177 · 图解

Day 177 — 原理图解

五张图,从「谁拉谁」到「看板怎么出数」。


图 1 · 拉(pull)模型时序

   应用 (localhost:9101)              Prometheus (localhost:9090)
        │                                     │
        │                                     │  每 5s 一轮
        │◀────── ① GET /metrics ──────────────┤
        │                                     │
        ├─────── ② 200 OK + 文本 ────────────▶│
        │        # TYPE ... 30.0              │
        │                                     │  ③ 用「自己的」当前时间打戳
        │                                     │  ④ 写 WAL + 内存
        │                                     │
        │◀────── ⑤ GET /metrics ──────────────┤   (下一轮)
        │                                     │
        │        ✗ 连接被拒                    │  ⑥ 目标挂了
        │                                     ├──▶ up{job="app"} = 0   ← 自动生成
        │                                     │

要点:发起方永远是 Prometheus;应用没有任何「上报」逻辑; up 是第 ⑥ 步的副产品,不是应用给的。


图 2 · 一次 scrape 的六步链路

┌────────────────────────────────────────────────────────────────┐
│ ① 读配置          scrape_configs: [{job: app, targets: [:9101]}]│
│         │                                                       │
│ ② HTTP 拉取       GET http://localhost:9101/metrics             │
│         │                                                       │
│ ③ 解析文本        每行 → (name, {labels}, value)                │
│         │          "# TYPE app_http_requests_total counter"     │
│         │          → 决定这族指标怎么存、怎么算                  │
│ ④ 打时间戳        用采集端时钟(客户端说了不算)                 │
│         │                                                       │
│ ⑤ 落库            WAL(顺序写)→ 内存 → 每 2h 压成不可变块       │
│         │                                                       │
│ ⑥ 写 up           up{job="app",instance="localhost:9101"} = 1/0  │
└────────────────────────────────────────────────────────────────┘

第 ④ 步是「为什么样本里没有时间戳」的答案;第 ⑥ 步是「为什么排障第一眼看 up」。


图 3 · TSDB 的写入路径与保留策略

   新样本
     │
     ▼
 ┌─────────┐   顺序追加,崩溃只丢最近这点   ┌──────────────┐
 │   WAL   │ ────────────────────────────▶ │  内存 head   │
 └─────────┘                                └──────┬───────┘
                                                   │ 每 2h 封存
                                                   ▼
                              ┌─────────────────────────────┐
                              │  block(不可变)             │
                              │  chunks  +  index           │
                              │  [00:00–02:00]              │
                              └──────────────┬──────────────┘
                                             │ 后台压缩合并
                                             ▼
                              ┌─────────────────────────────┐
                              │  更大的 block [00:00–06:00]  │
                              └──────────────┬──────────────┘
                                             │ 超过保留期(默认 15d)
                                             ▼
                                     整块删目录(不是扫全表)

「块不可变」→ 查询不加锁 → 高并发;代价是改历史数据很别扭 (这正是长期存储要交给 Thanos / VictoriaMetrics 的原因)。


图 4 · 四种指标类型的数据形状

Counter(只增)            Gauge(可增可减)
   │  ╱                     │    ╱╲    ╱╲
   │ ╱                      │   ╱  ╲  ╱  ╲
   │╱                       │  ╱    ╲╱    ╲
   └──────────▶ t           └──────────────▶ t
   查法:rate() / increase()   查法:直接读 / avg_over_time()

Histogram(桶累计)                   Summary(客户端聚合)
  _bucket{le="0.01"} 1.0               _count 6.0
  _bucket{le="0.05"} 2.0               _sum   2.563
  _bucket{le="0.1"}  4.0               (0.26.0 起分位已被移除:
  _bucket{le="0.5"}  5.0                 要分位请用 Histogram)
  _bucket{le="1.0"}  5.0
  _bucket{le="+Inf"} 6.0  ← 等于 _count
  _sum / _count
  查法:histogram_quantile(0.95, rate(_bucket[5m]))

为什么 _bucket 一定要配 rate(): _bucket 是 counter(累计),跨重启会归零,直接算分位会被历史累计污染。


图 5 · 从指标到看板:数据流

  ┌──────────────┐   GET /metrics   ┌──────────────┐
  │  Python 应用  │ ◀────────────────│  Prometheus  │
  │  :9101       │ ────文本────────▶ │  :9090       │
  └──────────────┘                  └───────┬──────┘
                                            │ HTTP API
                     ┌──────────────────────┴──────────────────────┐
                     │                                             │
              ┌──────▼──────┐                            ┌─────────▼─────────┐
              │  Grafana    │  /api/v1/query (instant)   │  你的脚本 / 告警   │
              │  :3000      │  /api/v1/query_range       │  (cron / CI)      │
              │             │                            └───────────────────┘
              │ 数据源 uid  │  看板 JSON:
              │ day177-prom │  panel.expr = 一句 PromQL
              └─────────────┘

Grafana 不存数据;每条曲线背后都是一次 query_range 调用。 所以「图不对」时,先用同样的 PromQL 直接打 Prometheus API 验证—— 这就是 code/03-promql-query.py 存在的意义。


一句话速记

图 一句话
1 Prometheus 主动来拉,up 是白送的
2 时间戳由采集端打,所以格式里没有时间
3 块不可变 → 查得快、改历史难
4 counter 配 rate(),分布配 histogram_quantile()
5 Grafana 只画不算,图背后是 PromQL