Skip to content

Prometheus 存储:本地存储的真相

Prometheus 默认使用本地存储,很多人因此觉得它「不够专业」。

但实际上,Prometheus 的本地存储设计得相当精妙。

今天我们来看看它是怎么工作的。


存储架构

┌─────────────────────────────────────────────────────────────┐
│                    Prometheus Server                          │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │                    In-Memory Database                 │  │
│  │  ┌─────────────────────────────────────────────────┐ │  │
│  │  │ Head Block (最新数据,< 2h)                     │ │  │
│  │  │ 写入 → Memory → WAL → Backfill                 │ │  │
│  │  └─────────────────────────────────────────────────┘ │  │
│  │  ┌─────────────────────────────────────────────────┐ │  │
│  │  │ Mmap Blocks (历史数据)                          │ │  │
│  │  │ 2h → 2h → 2h → ... → N (压缩)                │ │  │
│  │  └─────────────────────────────────────────────────┘ │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │              TSDB Files (磁盘)                       │  │
│  │  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐      │  │
│  │  │block-1 │ │block-2 │ │block-3 │ │block-N │      │  │
│  │  │(2h)   │ │(2h)   │ │(2h)   │ │(压缩)  │      │  │
│  │  └────────┘ └────────┘ └────────┘ └────────┘      │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

TSDB 文件结构

data/
├──wal/                        # Write-Ahead Log
│   ├── 00000000
│   └── checkpoint.00000001
├──queries/                    # 临时查询文件
└──01HGB7QXYZ.../             # Block 目录
    ├── index                  # 索引文件
    ├── meta.json              # 元数据
    ├── tombststones           # 删除标记
    ├── chunks/                # 数据块
    │   ├── 000001
    │   └── 000002
    └── tombststones

写入流程

java
// 写入流程(伪代码)
public class TSDBWrite {
    private final HeadBlock headBlock;
    private final WAL wal;

    public void write(Sample sample) {
        // 1. 先写入 WAL(保证持久化)
        wal.append(sample);

        // 2. 写入内存
        headBlock.append(sample);

        // 3. 检查是否需要刷新到磁盘
        if (headBlock.shouldCutBlock()) {
            // 4. 切出新的 Block
            Block block = headBlock.cutBlock();

            // 5. 压缩 Block(可选)
            block = compactor.compact(block);

            // 6. 持久化到磁盘
            block.persist();
        }
    }
}

Block 结构

每个 Block 包含一个时间段的数据:

Block: 01HGB7QXYZ... (2h 数据)

├── index
│   ├── Postings: {metric1 → [chunk1, chunk2]}
│   ├── Series: {metric1 → series_id}
│   └── Label Index: {label_value → posting}

├── chunks/
│   ├── 000001 (压缩后的数据块)
│   └── 000002

└── meta.json

查询流程

java
public class TSDBQuery {
    private final HeadBlock headBlock;
    private final List<Block> persistedBlocks;

    // 查询某个时间范围的数据
    public List<Series> query(long minTime, long maxTime) {
        List<Series> results = new ArrayList<>();

        // 1. 查询 Head Block(内存中)
        results.addAll(headBlock.query(minTime, maxTime));

        // 2. 查询已持久化的 Blocks
        for (Block block : persistedBlocks) {
            // 跳过不包含目标时间范围的 Block
            if (!block.overlaps(minTime, maxTime)) {
                continue;
            }
            // 用索引快速定位
            results.addAll(block.query(minTime, maxTime));
        }

        // 3. 合并结果并去重
        return mergeAndDeduplicate(results);
    }
}

压缩(Compaction)

TSDB 会定期压缩历史数据:

阶段Block 大小说明
Head< 2h内存中,未压缩
Block2h初始持久化
压缩后2h × 2 = 4h合并两个 Block
压缩后4h × 2 = 8h继续合并
......最大 21 天
时间线: ──────── 2h ──────── 2h ──────── 2h ──────── 2h ──────── 2h ────────

Level 0: [2h][2h][2h][2h][2h][2h]...  (原始 Block)
Level 1:        [4h]      [4h]      [4h]...   (合并)
Level 2:              [8h]            [8h]...  (继续合并)
Level 3:                        [16h]...
Level 4:                              [21d]    (最大 Block)

本地存储的局限

局限说明
容量有限单机磁盘决定上限
不支持集群无法横向扩展
高可用单点故障
长期存储不适合 > 1 年的数据

远程存储:解决长期存储问题

Prometheus 支持远程存储,将数据转发到外部系统:

yaml
# prometheus.yml
remote_write:
  - url: http://thanos-receiver:19291/api/v1/receive
    queue_config:
      capacity: 10000
      max_shards: 5

remote_read:
  - url: http://thanos-query:10902
    read_recent: true

常用远程存储方案

方案说明
ThanosPrometheus 原生扩展,支持全局视图
CortexCNCF 项目,多租户支持
M3Uber 开源,分布式 TSDB
InfluxDB原生支持 Prometheus 远程读写

Thanos:Prometheus 的长期存储方案

┌─────────────────────────────────────────────────────────────┐
│                     Thanos Architecture                        │
│                                                             │
│  ┌─────────────┐      ┌─────────────┐      ┌─────────────┐│
│  │Prometheus-1 │      │Prometheus-2 │      │Prometheus-3 ││
│  │  :9090      │      │  :9090      │      │  :9090      ││
│  └──────┬──────┘      └──────┬──────┘      └──────┬──────┘│
│         │                    │                    │         │
│         ↓                    ↓                    ↓         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    Object Storage                     │   │
│  │                 (S3/MinIO/GCS)                        │   │
│  │  ┌─────────────────────────────────────────────────┐ │   │
│  │  │  远程 Block 文件                                │ │   │
│  │  │  thanos/blocks/01HGB7QXYZ...                  │ │   │
│  │  └─────────────────────────────────────────────────┘ │   │
│  └─────────────────────────────────────────────────────┘   │
│                            ↑                                │
│         ┌─────────────────┼─────────────────┐              │
│         ↓                 ↓                 ↓              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     │
│  │  Store      │  │  Query      │  │  Compact    │     │
│  │  Gateway    │  │  Frontend   │  │  (压缩)     │     │
│  │  (读取)     │  │  (查询)     │  │             │     │
│  └─────────────┘  └─────────────┘  └─────────────┘     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Thanos 组件

组件作用
Sidecar部署在 Prometheus 旁,上传 Block 到对象存储
Store Gateway从对象存储读取数据,提供查询接口
Query Frontend查询前端,缓存和分片
Query聚合多个 Store 和 Prometheus 的数据
Compact压缩和降采样历史数据
Receive接收远程写入(Push 模式)

面试追问方向

  • Prometheus 的 TSDB 和普通数据库的索引有什么区别?
  • Thanos 和原生 Prometheus 存储相比有什么优势?

下一节,我们来了解 PromQL 查询语言。

基于 VitePress 构建