怎么读这三份源码 · 不迷路的方法
三份源码加起来一万七千多个文件,通读是不可能的。这一章给一套可操作的读法:先读仓库自带的文档(它们都带 AGENTS.md 和架构文档)、从 cmd/ 找入口、盯住 Go 的接口、拿测试当用法手册,以及顺着数据流而不是目录树读。
怎么读这三份源码 · 不迷路的方法
三个主角加起来一万七千多个 Go/TS 文件。通读是不可能的,也没必要。这一章给一套可操作的读法——本专栏自己就是按这套方法写的。
第一条:先读仓库自带的文档
最省力的一步,却最常被跳过:这三个仓库都自带了高质量的架构文档。
读大型项目源码,第一件事永远是“找作者留下的地图”,而不是一头扎进代码。 这三份都有:
AGENTS.md/CLAUDE.md(三个仓库都有!) —— 这是个很“当下”的现象:它们是写给 AI 编码助手看的约定文件。但对人类同样有用——它们浓缩了维护者的偏好和项目惯例。比如 Prometheus 的AGENTS.md列出了 PR 标题必须用的 area 前缀:tsdb、tsdb/wlog、promql、discovery/<name>、agent、alerting、textparse、ui……这张前缀表本身就是一份子系统地图,告诉你这个项目是由哪些模块构成的。tsdb/docs/format/(Prometheus) —— 磁盘格式的官方规范:index.md、chunks.md、wal.md、head_chunks.md、tombstones.md。还有tsdb/docs/bstream.md讲位流。第 2 部分基本就是围着这几份规范 + 对应实现读的。docs/internal-architecture.md(OTel Collector) —— 直接给了启动流程图和“该从哪几个文件开始读”的清单:otelcol/collector.go、service/internal/graph/graph.go、component/component.go。tsdb/README.md—— 甚至给了原始设计文章和 Gorilla 论文的链接。
官方文档 + 源码对读,比任何二手博客都可靠——博客会过时,tsdb/docs/format/ 和它旁边的代码是一起演进的。
第二条:从 cmd/ 找入口,顺着数据流读
Go 项目有个好处:入口非常明确。
cmd/prometheus/main.go—— Prometheus 的main。从这里能看到它怎么把服务发现、抓取、TSDB、查询引擎、规则引擎这些组件拼起来。otelcol/collector.go—— Collector 的核心(它的架构文档直接点名)。
拿到入口后,顺着数据流读,而不是按目录树顺序读:
那条“指标的一生”就是你的阅读顺序:抓取 → 存储 → 查询 → 告警。按数据流读,每一步都有“接下来它去哪”的悬念带着你,不容易迷路;按目录字母序读,读到第三个包就忘了前面在干嘛。
第三条:Go 的接口是骨架
读 Go 项目,接口(interface)是最重要的路标——它们定义了系统的关节:
| 接口 | 在哪 | 是什么关节 |
|---|---|---|
storage.Appender | Prometheus storage/ | 抓取怎么把数据交给存储 |
storage.Querier | Prometheus storage/ | 查询怎么从存储读数据 |
consumer.Consumer | Collector consumer/ | 管道组件间怎么传数据(背压在这) |
component.Component | Collector component/ | 所有组件的生命周期 |
找到接口,就找到了“可替换的边界”——而边界处往往就是设计的精华。 比如 Prometheus 的 storage.Appender:抓取模块只认这个接口,不关心背后是 TSDB 还是远程写还是测试用的假实现。看懂这个接口,你就懂了“采集”和“存储”是怎么解耦的。
Collector 更是把这点做到极致:五类组件全都是接口 + Factory,整个系统就是“一堆接口 + 一个把它们连起来的图”。所以读 Collector,读懂 component/、consumer/、service/internal/graph/ 这三处,主体就通了——剩下的具体 receiver/exporter 都是同一模式的复制。
第四条:测试是最好的用法手册
Go 的 _test.go 就在被测代码旁边,这是巨大的便利:
- 想知道某个类型怎么用 → 看它的
_test.go,里面是可运行的最小示例。 - 想知道边界行为 → 测试里通常列全了各种边界 case。
- Prometheus 还有
promql/promqltest/—— 一套声明式的 PromQL 测试框架,用文本描述“给这些数据、跑这个查询、该得这个结果”。读 PromQL 语义时,这些测试文件比文档还清楚。 example_test.go(如tsdb/example_test.go)—— Go 的惯例,专门写给人看的示例。
第五条:接受“读不完”,按需下钻
最重要的心态:不要试图读完。 Grafana 有 1.5 万个文件,其中绝大部分是 UI 组件和业务代码——读完既不可能也无价值。正确的做法是:
- 主干读透,枝叶跳过 —— TSDB 的压缩和索引值得逐行读;某个具体数据源的适配代码扫一眼知道套路即可。
- 带着问题读 —— “
rate()为什么会外推?”比“我要读完 promql 包”有效一万倍。问题给你路径和终点。 - 允许有黑盒 —— 读 TSDB 时,先把压缩算法当黑盒(“它能把点压小”),把主流程走通,再回头钻进去。一次只开一个黑盒。
本专栏的写作约定
最后交代一下,方便你对照源码验证:
- 给真实坐标 —— 凡有结论,尽量给真实的文件路径 + 类型名 + 关键函数(如
tsdb/head.go的Head.Appender),你可以直接去仓库里核对。 - 代码片段是节选 —— 为了可读,会省略次要分支(标
...)、简化错误处理,但不虚构:签名和关键逻辑忠于源码。 - 版本明确 —— Prometheus 3.13.1 / Collector 0.156.0 / Grafana 13.1.0。这些项目在快速演进,本专栏是某个时刻的切片;读的时候留意你手上的版本可能已经不同。
小结与下一部分
读这三份源码的方法:① 先读仓库自带的文档——三者都有 AGENTS.md/CLAUDE.md(维护者约定,Prometheus 的 area 前缀表就是子系统地图)、tsdb/docs/format/(磁盘格式规范)、docs/internal-architecture.md(启动流程 + 该读哪些文件);② 从 cmd/ 找入口、顺数据流读而非按目录序;③ 盯住 Go 接口(storage.Appender/consumer.Consumer 就是系统关节和可替换边界);④ 拿 _test.go 当用法手册(promqltest 尤佳);⑤ 接受读不完,带着问题、主干读透枝叶跳过。
第 0 部分到此结束——你已经知道可观测性是什么、三大支柱、数据的一生、三个主角和读法。下一部分开始动真格,从 Prometheus 的数据模型与采集入手。👉 第 1 部分 · Prometheus:数据模型与采集。