深入 Prometheus 内核:解析 Pull 采样模型与时序数据库底座原理
深入 Prometheus 内核:解析 Pull 采样模型与时序数据库底座原理

一、Pull模型的深度解析
1.1 一次完整的Scrape过程
当一个Scrape请求发生时,Prometheus内部是这样工作的:
flowchart TD
N1["1. 服务发现 → 获取目标列表"] --> N2["2. 计算任务分发 → 每个ScrapeManager负责一组目标"]
N2 --> N3["3. 发送HTTP GET → 请求目标的/metrics端点"]
N3 --> N4["4. 解析响应 → 用TextParser解析Prometheus文本格式"]
N4 --> N5["5. 样本处理 → 转换Label、时间戳、值"]
N5 --> N6["6. Appender → 将样本写入TSDB"]
N6 --> N7["7. 更新meta → 更新up指标、Scrape耗时等"]
// prometheus/scrape/scrape.go — 简化的Scrape流程
func (sl *scrapeLoop) scrape(ctx context.Context) error {
// 1. 发起HTTP请求
resp, err := sl.scraper.scrape(ctx, sl.target)
if err != nil {
sl.reportError(err)
return err
}
// 2. 解析metrics文本
var totalSamples int
sl.loopMut.Lock()
// 3. 对每个样本调用Appender
app := sl.appender(ctx)
for _, series := range resp.series {
ref, err := app.Append(series.labels, series.timestamp, series.value)
if err != nil {
// 跳过格式错误的样本
continue
}
totalSamples++
}
// 4. 提交批量写入
err = app.Commit()
sl.loopMut.Unlock()
return nil
}
1.2 Pull模型的关键优势
通过看源码,我理解了为什么Prometheus坚持用Pull:
优势1:故障检测的即时性
当目标挂了,Pull模型能在下一个Scrape周期(默认15s)立即发现——up指标变成0。而Push模型必须等目标重新上线后才能上报,或者在Push端做心跳检测,增加了复杂度。
优势2:负载的可控性
Pull的节奏由Prometheus Server决定。如果Server负载高了,可以通过scrape_interval降低采集频率。Push的节奏由数据源决定,突发流量会直接冲击Server。
优势3:天然的服务发现对齐
# 服务发现自动生成的目标列表
scrape_configs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_ready]
# 只采集Ready状态的Pod
regex: "true"
action: keep
Pull模型可以结合服务发现的元数据做过滤——只有Ready的Pod才采集,这个能力Push模型很难实现。
1.3 一个被低估的设计:WAL与崩溃恢复
Prometheus的TSDB使用了WAL(Write-Ahead Log)来保证数据不丢失:
flowchart LR
A["Scrape"] --> B["Head Appender"]
B --> C["WAL (磁盘)"]
C --> D["Head Chunk (内存)"]
D --> E["压缩/合并"]
E --> F["Block (磁盘)"]
崩溃恢复策略:
- 启动时检查WAL目录
- 如果WAL存在,重放所有未压缩的样本
- 重建Head Chunk中的内存索引
- 继续正常采集
这个设计和ELK的Translog很像——都是先写日志再写数据。但TRDB的WAL是批量写入的(每秒一次),而ES的Translog默认是每次请求都刷盘。这就是为什么Prometheus的写入性能比ES好得多的原因之一。
二、TSDB的存储结构
2.1 目录结构
$ ls -la /data/prometheus/tsdb/
total 64
drwxr-xr-x wal/ # WAL目录
drwxr-xr-x 01GABCDEFG/ # Block目录
drwxr-xr-x 01GHIJKLMN/
-rw-r--r-- chunks_head/ # 当前内存chunk的映射文件
-rw-r--r-- index/ # 倒排索引
-rw-r--r-- meta.json # Block元数据
-rw-r--r-- tombstone/ # 删除标记
-rw-r--r-- lock # 文件锁
2.2 Block结构
每个Block包含了2小时的数据,内部结构:
$ ls -la /data/prometheus/tsdb/01GABCDEFG/
total 32
drwxr-xr-x chunks/ # 存储压缩后的样本数据
drwxr-xr-x index/ # 倒排索引
-rw-r--r-- meta.json # 元数据:时间范围、stats等
-rw-r--r-- tombstone/ # 删除标记(逻辑删除)
Prometheus将时间分成2小时一个的Block。每个Block是不可变的(immutable),只读不写。这样做的好处是:
- 不需要对Block加锁,查询可以安全并发
- 压缩合并时只需要创建新Block,删除旧Block
- 备份时可以无损复制Block文件
2.3 倒排索引:快速定位时间序列
Prometheus查询能这么快,倒排索引功不可没:
// 倒排索引结构(伪代码)
type PostingsIndex struct {
// 每个label对 → 对应的series ID列表
// 例如: service="payment" → [1, 5, 12, 45, 78]
// env="prod" → [1, 2, 5, 12, 34, 45, 67, 78]
mapping map[string][]uint64
}
// 查询 service="payment", env="prod"
// 取交集:Intersect([1,5,12,45,78], [1,2,5,12,34,45,67,78])
// = [1, 5, 12, 45, 78]
这个倒排索引是内存映射(mmap)加载的,查询时不需要反序列化。这就是为什么Prometheus的label匹配查询能达到毫秒级响应。
三、Pull模型 + TSDB的协同设计
Pull模型和TSDB的设计是深度耦合的:
3.1 写入模式
Pull模型带来了稳定的写入节奏:
flowchart TD
A["每15s一次Scrape"] --> B["每个目标产生5-20个时间序列"]
B --> C["每个序列1个样本"]
C --> D["每秒约1000-10000个样本写入"]
D --> E["写入速率恒定"]
恒定速率的写入对TSDB非常友好:
- WAL可以批量fsync(每秒一次)
- Head Chunk可以平稳增长(不会突然暴涨)
- 后台合并(Compaction)可以预测
3.2 压缩合并策略
// TSDB后台合并 — 将小Block合并成大Block
func (db *DB) compaction() {
// 1. 选择需要合并的Block(通常是最小的2-3个)
blocks := selectBlocksForMerge()
// 2. 创建新的Block
newBlock := createMergedBlock(blocks)
// 3. 原子替换:删除旧Block,写入新Block
// 新Block包含了合并后的chunk和索引
// 这个过程不会阻塞查询
// 4. 清理WAL中已被合并的数据
}
合并后的Block大小大约是原始数据的1/3(因为chunk压缩)。
四、性能优化实践
理解了原理后,我们在生产环境的调优:
# 1. 延长Block保留时间(默认15天对我们不够)
--storage.tsdb.retention.time=30d
# 2. 增大Block大小(减少Block数量,提升查询性能)
--storage.tsdb.max-block-duration=4h
# 3. 调整WAL大小(减少WAL清理频率)
--storage.tsdb.wal-segment-size=256MB
# 4. 内存限制(防止OOM)
--storage.tsdb.max-chunks-to-persist=5000
| 参数 | 默认值 | 优化值 | 效果 |
|---|---|---|---|
| retention.time | 15d | 30d | 保留更多历史数据 |
| max-block-duration | 2h | 4h | Block数量减半 |
| wal-segment-size | 128MB | 256MB | 减少WAL分段数量 |
| max-chunks-to-persist | 无限制 | 5000 | 防止内存暴涨 |
结语
理解Prometheus的Pull模型和TSDB原理后,再去看那些配置参数,就不只是"别人说这么配"了,而是知道每个参数背后的设计考量。
Pull模型为TSDB提供了稳定写入,TSDB为Pull模型提供了高效存储。这套设计不是一蹴而就的——它是经历了多年的生产实践和调优后才形成的。理解了它们的设计哲学,你就能在遇到性能问题时,做出合理的优化决策。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)