# 第 1 部分导论 · Prometheus 架构总览

> 打开 Prometheus 的整体骨架——它由哪几块组成、各块怎么接线、以及贯穿其设计的两个根本选择：pull 模型和「单机自足」。这一章给出后续所有章节的坐标系。

- 作者：David（道雾轩）
- 专栏：指标采集与观测（https://daiw.org/manual/observability.md）
- 最后更新：2026-07-15
- 原文：https://daiw.org/manual/observability/prometheus-architecture
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 第 1 部分导论 · Prometheus 架构总览

[第 0 部分](https://daiw.org/manual/observability/data-journey)画了[数据的一生](https://daiw.org/manual/observability/data-journey)。从这一部分起，我们打开 **Prometheus** 的源码，先看它的**整体骨架**——后面每一章都会落在这张图的某个位置上。

## 五大块

Prometheus 是**一个二进制**（`cmd/prometheus/main.go`），内部由五块组成：

```mermaid
flowchart TD
    SD["① 服务发现 discovery/<br/>去抓谁?"] -->|"目标列表"| SCRAPE
    SCRAPE["② 抓取 scrape/<br/>怎么抓、抓回来解析"] -->|"Appender 接口"| TSDB
    TSDB["③ 存储 tsdb/<br/>存得下、查得快"] -->|"Querier 接口"| QUERY
    QUERY["④ 查询 promql/<br/>问出答案"] --> RULES["⑤ 规则 rules/<br/>周期求值 → 告警"]
    QUERY --> API["web/ HTTP API<br/>(Grafana 从这查)"]
    RULES -->|"预聚合结果写回"| TSDB
    RULES -->|"告警"| AM["Alertmanager(独立进程)"]
    style TSDB fill:#f59e0b,color:#000
```

| 块 | 目录 | 职责 | 本专栏 |
| --- | --- | --- | --- |
| 服务发现 | `discovery/` | 动态找出要抓的目标 | [本部分](https://daiw.org/manual/observability/service-discovery) |
| 抓取 | `scrape/` | 按周期拉 `/metrics`、解析 | [本部分](https://daiw.org/manual/observability/scrape-loop) |
| 存储 | `tsdb/` | 时序数据落盘与检索 | 第 3 部分 |
| 查询 | `promql/` | PromQL 解析与执行 | 第 4 部分 |
| 规则 | `rules/` | 预聚合与告警规则 | 第 5 部分 |

<Callout type="info">
  **注意这五块之间靠**接口**解耦，而不是直接调用——这是读懂 Prometheus 架构的钥匙。** 最关键的两个接口在 `storage/`：

  - **`storage.Appender`** —— [抓取](https://daiw.org/manual/observability/scrape-loop)只管“把采样点交出去”，不关心背后是 [TSDB](https://daiw.org/manual/observability/prometheus-architecture)、[远程写](https://daiw.org/manual/observability/prometheus-architecture)、还是测试假实现。
  - **`storage.Querier`** —— [查询引擎](https://daiw.org/manual/observability/prometheus-architecture)只管“按标签匹配器要 series”，不关心数据从内存还是磁盘来。

  于是“**采集**”和“**存储**”、“**存储**”和“**查询**”之间是**可替换的边界**。这也是 Thanos、Mimir 这类项目能“把 Prometheus 的存储换掉、复用它的抓取和 PromQL”的原因——[边界处就是生态的接口](https://daiw.org/manual/observability/reading-the-source)。
</Callout>

## 根本选择一：pull 而非 push

Prometheus 最著名的设计取舍：**它主动去抓你，而不是你推给它**。

<Callout type="info">
  **pull 让 Prometheus **掌握主动权**，这带来一串连锁的好处——理解这一点，才能理解为什么它长成现在这样。**

  - **目标列表是“真相”** —— Prometheus 通过[服务发现](https://daiw.org/manual/observability/service-discovery)知道**应该**有哪些目标。抓不到 = 目标出问题了。如果是 push，你**永远不知道“没收到数据”是因为服务挂了还是因为它根本没部署**。
  - **抓取即健康检查** —— 每次抓取的成败，被自动记成一个 `up` 指标（源码 `scrape/scrape.go:2044`）。**你不用额外配存活探测，`up == 0` 就是它挂了**。这是个极优雅的副产品。
  - **不会被打爆** —— 抓取节奏由 Prometheus 定（如 15s 一次）。客户端就算疯狂产生指标，也只是让**那一次抓取**变大，不会像 push 那样瞬间打垮服务端。
  - **易于调试** —— `/metrics` 是个**普通 HTTP 端点**，你 `curl` 一下就能看到这个服务当前所有指标。这个“**随时可自查**”的性质极其实用。

  **代价**：Prometheus 必须**网络可达**每个目标。这在[短命的批处理任务](https://daiw.org/manual/observability/scrape-loop)（跑完就没了，来不及被抓）、[NAT/防火墙后的客户端](https://daiw.org/manual/observability/scrape-loop)上行不通——于是有了 Pushgateway 这类补丁。**但补丁是例外，pull 是主线**。
</Callout>

## 根本选择二：单机自足，不搞分布式

另一个同样重要、却常被忽略的选择：

<Callout type="warn">
  **一个 Prometheus 实例是**完全自足**的单机进程：自己发现、自己抓、自己存（本地磁盘）、自己查。它**刻意不做**分布式存储、不做集群共识。**

  这在“什么都要云原生分布式”的年代显得反直觉，但理由很硬：**监控系统必须比被监控系统更可靠**。如果 Prometheus 自己是个需要共识、需要协调、可能脑裂的分布式系统，那么**当基础设施出问题时，它自己也会挂**——而那恰恰是你最需要它的时刻。

  单机自足意味着：**没有外部依赖 = 没有额外的故障模式**。你的 etcd 挂了、网络分区了，Prometheus 依然能抓到它够得着的目标、依然能查本地数据、依然能告警。**这是一个用“能力上限”换“可靠性下限”的清醒取舍。**

  代价当然有：单机容量有限、数据不高可用。业界的解法不是“把 Prometheus 改成分布式”，而是**在它之上叠一层**——跑多个独立实例（冗余），用 Thanos / Mimir 做长期存储和全局查询。**保持核心简单，把复杂性推到外层**，这个思路值得记住。
</Callout>

## 一次抓取的完整链路

把五块串起来，一次抓取的实际链路是：

1. **`discovery/`** 吐出目标列表（如 K8s 里所有带某注解的 pod）。
2. **relabeling** 对目标做[改写 / 过滤](https://daiw.org/manual/observability/relabeling)（决定最终抓谁、带什么标签）。
3. **`scrape/`** 的 `scrapePool`（源码 `scrape/scrape.go:84`）为每个目标起一个 **`scrapeLoop`**（`scrape.go:828`），它的 `run()`（`scrape.go:1263`）就是那个**按周期循环抓取**的心脏。
4. 抓回文本 → 解析 → 再做一轮 **metric relabeling** → 通过 **`storage.Appender`** 交给 [TSDB](https://daiw.org/manual/observability/prometheus-architecture)。
5. 顺带自动写入几个**元指标**：`up`、`scrape_duration_seconds`、`scrape_samples_scraped` 等（`scrape.go:2044` 起）——[后面会讲这些是排查采集问题的第一现场](https://daiw.org/manual/observability/scrape-loop)。

这条链路就是[第 1 部分](https://daiw.org/manual/observability/prometheus-architecture)剩下几章的顺序：[数据模型](https://daiw.org/manual/observability/data-model) → [指标类型](https://daiw.org/manual/observability/metric-types) → [暴露格式](https://daiw.org/manual/observability/exposition-format) → [服务发现](https://daiw.org/manual/observability/service-discovery) → [抓取循环](https://daiw.org/manual/observability/scrape-loop) → [relabeling](https://daiw.org/manual/observability/relabeling)。

## 小结与下一章

Prometheus 是**一个自足的二进制**，五块组成：[服务发现](https://daiw.org/manual/observability/service-discovery)（`discovery/`）、[抓取](https://daiw.org/manual/observability/scrape-loop)（`scrape/`）、[存储](https://daiw.org/manual/observability/prometheus-architecture)（`tsdb/`）、[查询](https://daiw.org/manual/observability/prometheus-architecture)（`promql/`）、[规则](https://daiw.org/manual/observability/prometheus-architecture)（`rules/`），彼此靠 **`storage.Appender`/`storage.Querier`** 两个接口解耦（[边界处即生态接口](https://daiw.org/manual/observability/reading-the-source)，Thanos/Mimir 由此而来）。两个根本选择：**pull 模型**（[目标列表即真相](https://daiw.org/manual/observability/prometheus-architecture)、[抓取即健康检查（`up`）](https://daiw.org/manual/observability/scrape-loop)、不会被打爆、[可 curl 自查](https://daiw.org/manual/observability/exposition-format)；代价是要网络可达）、**单机自足**（监控必须比被监控更可靠 → [无外部依赖 = 无额外故障模式](https://daiw.org/manual/observability/prometheus-architecture)；容量与高可用推到 Thanos/Mimir 那层解决）。

骨架有了，先看**数据本身长什么样**——Prometheus 的数据模型，以及它为省内存做到了什么地步。👉 [数据模型](https://daiw.org/manual/observability/data-model)。

## 参考资料

- [1] Prometheus 源码 `cmd/prometheus/main.go`（五块的接线）、`storage/interface.go`（`Appender`/`Querier`）。
- [2] Prometheus 源码 `scrape/scrape.go`（`scrapePool:84`、`scrapeLoop:828`、`run:1263`、`up:2044`）。
- [3] Prometheus 官方文档 `docs/index.md`、`docs/getting_started.md`。
