# 一条指标的一生 · 从埋点到那条曲线

> 用一个具体的指标走完全程——从代码里的一行埋点，经暴露、服务发现、抓取、存储、查询，到 Grafana 上那条曲线，以及告警怎么被触发。这张全景图就是整个专栏的路线索引。

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

# 一条指标的一生 · 从埋点到那条曲线

理论讲完了（[可观测性是什么](https://daiw.org/manual/observability/why-observability)、[三大支柱](https://daiw.org/manual/observability/three-pillars)），这一章用**一个具体的指标**走完全程。这条路上的每一站，后面都有专门的部分展开——所以这张图也是**整个专栏的路线索引**。

## 起点：代码里的一行埋点

一切始于你在业务代码里埋的一行。假设你想知道下单接口的请求数：

```go
// 定义一个 counter,带两个标签
httpRequests := prometheus.NewCounterVec(
    prometheus.CounterOpts{Name: "http_requests_total", Help: "HTTP 请求总数"},
    []string{"method", "status"},   // 标签:未来提问的自由度
)
// 每处理完一个请求,加一
httpRequests.WithLabelValues("POST", "200").Inc()
```

<Callout type="info">
  **注意 `[]string{"method", "status"}` 这两个标签——它们就是[上一章说的“未来提问的自由度”](https://daiw.org/manual/observability/why-observability)。** 埋点时多带一个标签，日后就能多一个维度去切分（“只看 POST 的”、“只看 5xx 的”）。但每个标签也在**成倍放大数据量**——这就是[基数问题](https://daiw.org/manual/observability/three-pillars)的源头，[第 8 部分](https://daiw.org/manual/observability/three-pillars)会专门算这笔账。**埋点这一刻的决定，决定了你日后能问什么问题、以及要付多少钱。**
</Callout>

进程内，这个计数器只是内存里的一个数（准确说是一组数，每个标签组合一个）。它需要被**暴露**出去。

## 第 1 站：暴露与抓取（第 1 部分）

Prometheus 用的是 **pull（拉）** 模型——**它主动来抓你**，而不是你推给它：

1. **暴露** —— 你的进程开一个 `/metrics` HTTP 端点，把当前所有指标以[文本格式](https://daiw.org/manual/observability/three-pillars)吐出来：
   ```
   # HELP http_requests_total HTTP 请求总数
   # TYPE http_requests_total counter
   http_requests_total{method="POST",status="200"} 1027
   http_requests_total{method="POST",status="500"} 3
   ```
2. **服务发现** —— Prometheus 怎么知道去抓谁？靠 `discovery/`：从 Kubernetes API、Consul、DNS、静态配置等**动态发现**要抓的目标（pod 来来去去，不可能手工维护列表）。
3. **抓取** —— `scrape/` 里的抓取循环按固定间隔（如 15s）去 GET 每个目标的 `/metrics`，解析文本，得到一批**采样点**。

<Callout type="info">
  **“pull 还是 push”是 Prometheus 最著名的设计取舍**——[第 1 部分会细讲](https://daiw.org/manual/observability/three-pillars)。简单说，pull 让 Prometheus **掌握主动权**：它知道该抓谁（靠服务发现）、抓不到就说明目标挂了（**抓取本身就是健康检查**）、且不会被失控的客户端打爆。代价是它得能**网络可达**每个目标——这在某些场景（如短命的批处理任务、NAT 后的客户端）行不通，于是有了 Pushgateway 这类补丁。
</Callout>

## 第 2 站：存进 TSDB（第 2 部分）

抓回来的采样点交给存储层（`storage/` 的 `Appender` 接口），最终落进 **TSDB**（`tsdb/`）。这是整个 Prometheus 硬核程度**数一数二**的部分：

<Callout type="info">
  **每个“指标名 + 标签组合”是一条独立的**时间序列**（series），而一条 series 就是一串 `(时间戳, 值)`。TSDB 要解决两个难题：**

  - **怎么存得下** —— 每秒几百万个采样点，原始存法（8 字节时间戳 + 8 字节浮点）一天就是 TB 级。TSDB 用 **Gorilla 压缩**（[delta-of-delta 存时间戳、XOR 存值](https://daiw.org/manual/observability/three-pillars)），把平均每个采样点压到**1~2 字节**——[压缩率超过 10 倍](https://daiw.org/manual/observability/three-pillars)，这才让“指标全量长存”成为可能。
  - **怎么查得快** —— 你要问“所有 `status="500"` 的 series”，得在几百万条里瞬间找出来。TSDB 用**倒排索引**（标签 → series 列表），和搜索引擎一个思路。

  数据先进**内存里的 Head block**（同时写 **WAL** 防丢），定期切成**不可变的持久化 block** 落盘，老 block 再被**压实**（compaction）合并。这个“内存写入 + 定期落盘 + 后台合并”的结构，和 LSM-tree 一脉相承。[第 2 部分](https://daiw.org/manual/observability/three-pillars)会打开 `tsdb/docs/format/` 里的**官方磁盘格式规范**逐字节看。
</Callout>

## 第 3 站：查询（第 3 部分）

数据存下来，你得能问出答案。这是 **PromQL**（`promql/`）的活：

```text
# 过去 5 分钟,每秒的 5xx 错误率
rate(http_requests_total{status=~"5.."}[5m])
```

PromQL 是一门**为时序数据设计的语言**：`{status=~"5.."}` 通过[倒排索引](https://daiw.org/manual/observability/data-journey)选出 series，`[5m]` 取一段时间窗口，`rate()` 算增长速率。[第 3 部分](https://daiw.org/manual/observability/three-pillars)会读 `promql/parser`（怎么解析）和 `promql/engine.go`（怎么执行），并讲清 `rate()` 这个**最常用也最容易用错**的函数背后的坑。

## 第 4 站：分叉——告警与展示

查询的结果流向两个终点：

```mermaid
flowchart TD
    CODE["① 代码埋点<br/>counter.Inc()"] --> EXPO["② /metrics 端点暴露"]
    SD["服务发现 discovery/<br/>(去抓谁?)"] --> SCRAPE
    EXPO --> SCRAPE["③ 抓取 scrape/<br/>(pull,每 15s)"]
    SCRAPE --> TSDB["④ TSDB 存储 tsdb/<br/>Head + WAL → block → 压实"]
    TSDB --> PROMQL["⑤ PromQL 查询 promql/"]
    PROMQL --> RULES["⑥a 规则引擎 rules/<br/>告警规则周期求值"]
    PROMQL --> GRAFANA["⑥b Grafana<br/>那条曲线"]
    RULES -->|"条件持续满足"| AM["Alertmanager<br/>去重/分组/静默 → 通知你"]
    OTEL["OTel Collector<br/>(统一管道,第 5 部分)"] -.->|"也可喂数据"| TSDB
    style CODE fill:#3b82f6,color:#fff
    style TSDB fill:#f59e0b,color:#000
    style GRAFANA fill:#a855f7,color:#fff
    style AM fill:#ef4444,color:#fff
```

- **告警**（第 4 部分） —— `rules/` 里的规则引擎**周期性地跑 PromQL**，结果满足条件且**持续够久**就产生告警，交给 Alertmanager 去重、分组、静默，最后通知到人。
- **展示**（第 7 部分） —— Grafana 把 Prometheus 当**数据源**，把 PromQL 结果画成你屏幕上那条曲线。

至此，**一条指标从代码里的一行走到了你的屏幕上**。

## 另一条路：OpenTelemetry Collector（第 5、6 部分）

上面这条是 **Prometheus 的经典路线**。但现实里往往还有一条**统一管道**：

<Callout type="info">
  **[OpenTelemetry Collector](https://daiw.org/manual/observability/the-three-projects) 是一个**厂商中立的中转站**：它接收（receiver）各种格式的[指标 / 日志 / 追踪](https://daiw.org/manual/observability/three-pillars)，做加工（processor：过滤、改标签、批量、采样），再导出（exporter）到各种后端。** 它解决的问题是：[三大支柱](https://daiw.org/manual/observability/three-pillars)、N 种客户端 SDK、M 个后端，如果两两对接就是 N×M 的噩梦。Collector 站在中间，把它变成 **N+M**——**“管道”这一层被抽象出来独立演进**。

  这也是为什么本专栏要讲三个项目而不是一个：**Prometheus 代表“指标怎么被存好查好”、Collector 代表“数据怎么被灵活地运输和加工”、Grafana 代表“数据怎么变成人能看懂的东西”**——三者合起来才是完整的可观测性，缺一块都跑不通。
</Callout>

## 这张图就是路线图

回头看这条路，它正好对应本专栏的结构：

| 站点 | 干什么 | 部分 |
| --- | --- | --- |
| 埋点 → 暴露 → 发现 → 抓取 | 数据怎么被拿到 | [第 1 部分](https://daiw.org/manual/observability/the-three-projects) |
| TSDB 存储 | 怎么存得下、查得快 | 第 2 部分 |
| PromQL | 怎么问出答案 | 第 3 部分 |
| 规则与告警 | 什么时候叫醒你 | 第 4 部分 |
| OTel Collector | 数据怎么被统一运输 | 第 5 部分 |
| 追踪与日志 | 另两根支柱 | 第 6 部分 |
| Grafana | 怎么变成看得懂的 | 第 7 部分 |
| 工程实践 | 基数、成本、SLO、排障 | 第 8 部分 |

## 小结与下一章

一条指标的一生：**[代码埋点](https://daiw.org/manual/observability/data-journey)**（标签 = [提问自由度 + 成本](https://daiw.org/manual/observability/three-pillars)）→ **`/metrics` 暴露** → **[服务发现](https://daiw.org/manual/observability/data-journey)**（`discovery/`，去抓谁）→ **[抓取](https://daiw.org/manual/observability/data-journey)**（`scrape/`，pull 模型，[抓取即健康检查](https://daiw.org/manual/observability/data-journey)）→ **[TSDB](https://daiw.org/manual/observability/data-journey)**（`tsdb/`，[Gorilla 压缩到 1~2 字节/点](https://daiw.org/manual/observability/three-pillars) + [倒排索引](https://daiw.org/manual/observability/data-journey)，Head+WAL→block→压实）→ **[PromQL](https://daiw.org/manual/observability/data-journey)**（`promql/`）→ 分叉成 **[告警](https://daiw.org/manual/observability/data-journey)**（`rules/`→Alertmanager）和 **[Grafana 展示](https://daiw.org/manual/observability/the-three-projects)**。另有 **[OTel Collector](https://daiw.org/manual/observability/the-three-projects)** 这条统一管道（[把 N×M 对接变成 N+M](https://daiw.org/manual/observability/the-three-projects)）。

这条路上的三个主角各司其职。下一章正式介绍它们，以及为什么是这三个。👉 [三个主角与分工](https://daiw.org/manual/observability/the-three-projects)。

## 参考资料

- [1] Prometheus 源码 `scrape/`、`tsdb/`、`promql/`、`discovery/`、`rules/`、`storage/`（本章各站点）。
- [2] Prometheus 官方文档 `docs/getting_started.md`、`docs/index.md`。
- [3] OpenTelemetry Collector 源码 `docs/internal-architecture.md`。
