强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

03 - 架构原理

03 · 架构原理

本章目标

  • 理解 VictoriaMetrics 的存储引擎设计
  • 了解数据压缩策略及其优势
  • 掌握集群版三大组件的职责与协作
  • 认识数据分片(sharding)和复制(replication)机制
  • 为性能调优和容量规划打下理论基础

3.1 整体架构概览

                   写入路径 (Write Path)
┌──────────┐  ┌──────────┐  ┌──────────┐
│ vmagent  │  │Prometheus│  │ 应用程序 │
│  scrape  │  │remote_w  │  │  SDK     │
└────┬─────┘  └────┬─────┘  └────┬─────┘
     │              │              │
     ▼              ▼              ▼
┌─────────────────────────────────────────┐
│              vminsert                   │
│         (路由 + 分片)                   │
└─────────────────┬───────────────────────┘
                  │ consistent hashing
                  ▼
   ┌──────────┬──────────┬──────────┐
   │vmstorage1│vmstorage2│vmstorage3│
   │          │          │          │
   │ Part     │ Part     │ Part     │  ← 分区(Partitions)
   │ Index    │ Index    │ Index    │  ← 倒排索引
   │ Cache    │ Cache    │ Cache    │  ← 内存缓存
   └──────────┴──────────┴──────────┘
                  ▲
                  │
┌─────────────────────────────────────────┐
│              vmselect                   │
│         (查询 + 聚合 + 缓存)            │
└─────────────────┬───────────────────────┘
                  │
                  ▼
            ┌──────────┐
            │ Grafana  │
            │ vmui     │
            └──────────┘

                   查询路径 (Query Path)

3.2 存储引擎

3.2.1 核心设计原则

VictoriaMetrics 的存储引擎基于以下原则设计:

原则实现方式
追加写入所有数据以 append-only 方式追加到数据文件
不可变分区数据文件一旦写入就不再修改
后台合并小分区(part)后台合并为大分区(类似 LSM-Tree)
列式存储时间戳和值分别存储,提升压缩效率
倒排索引metric_name → time_series_id 的快速查找

3.2.2 数据写入流程

写入请求 (metric_name{label=value} timestamp value)
         │
         ▼
┌─────────────────┐
│  1. 内存缓冲区   │  ← 先写入内存中的活跃 block
│   (Active Block) │
└────────┬────────┘
         │ 达到阈值(如 64MB)或定时 flush
         ▼
┌─────────────────┐
│  2. 写入磁盘    │  ← 生成一个 Part(分区)
│   (Immutable    │     包含:data.bin + index.bin + timestamps.bin
│    Part)        │
└────────┬────────┘
         │ 后台合并
         ▼
┌─────────────────┐
│  3. Part 合并   │  ← 小 Part 合并为大 Part
│   (Merge)       │     减少文件数量,提升查询效率
└─────────────────┘

3.2.3 Part(分区)结构

每个 Part 包含以下文件:

<part_dir>/
├── metadata.json     # 分区元数据
├── timestamps.bin    # 时间戳列(列式存储)
├── values.bin        # 数据值列(列式存储)
├── index.bin         # 倒排索引
└── metaindex.bin     # 元索引(index 的索引)

分区的生命周期:

小 Part (memory flush)
    │
    ▼
中 Part (merge level 1)
    │
    ▼
大 Part (merge level 2)
    │
    ▼
超大 Part (merge level N)
    │
    ▼  retention 过期
    删除

3.2.4 倒排索引

倒排索引结构:
┌──────────────┬─────────────────────┐
│ Search Tag   │ Time Series IDs     │
├──────────────┼─────────────────────┤
│ __name__=cpu │ 1, 3, 7, 12, 45     │
│ host=web01   │ 1, 5, 12            │
│ host=web02   │ 3, 7, 45            │
│ region=cn    │ 1, 3, 5, 7          │
└──────────────┴─────────────────────┘

查询 cpu{host="web01"} 时:
  → 查找 __name__=cpu → {1, 3, 7, 12, 45}
  → 查找 host=web01    → {1, 5, 12}
  → 取交集             → {1, 12}
  → 读取时间序列 1 和 12 的数据

3.3 数据压缩

3.3.1 压缩策略概览

数据类型压缩算法说明
时间戳delta-of-delta + varbit相邻时间戳差值通常恒定,压缩率极高
浮点值XOR 压缩类 Facebook Gorilla,相似值差异小
字符串(label)字典编码 + 前缀压缩label 值重复率高
索引前缀压缩metric name 共享前缀

3.3.2 时间戳压缩(Delta-of-Delta)

原始时间戳序列(每 15 秒采集):
  1000, 1015, 1030, 1045, 1060

Delta(一阶差值):
  -, 15, 15, 15, 15

Delta-of-Delta(二阶差值):
  -, -, 0, 0, 0

编码后:只需存储第一个值(1000)、第一个delta(15)、然后一串 0
用 varbit 编码 0 只需 1 bit,压缩率极高!

3.3.3 值压缩(XOR)

原始浮点值:
  95.20, 95.15, 95.18, 95.22

XOR 编码:
  存储完整第一个值: 95.20
  XOR(95.20, 95.15) = 微小差异 → 几个有效位 → 极少 bit
  XOR(95.15, 95.18) = 微小差异 → 几个有效位 → 极少 bit
  ...

3.3.4 压缩率对比

场景Prometheus TSDBVictoriaMetrics压缩比提升
CPU 指标(15s 间隔)1.2 bytes/sample0.15 bytes/sample8x
内存指标(60s 间隔)1.5 bytes/sample0.2 bytes/sample7.5x
网络计数器(高精度)1.8 bytes/sample0.3 bytes/sample6x
混合工作负载~1.5 bytes/sample~0.2 bytes/sample7-10x

3.4 集群组件详解

3.4.1 vminsert(写入层)

职责

  • 接收客户端写入请求
  • 按 metric name + labels 进行一致性哈希分片
  • 将数据路由到对应的 vmstorage 节点
客户端请求
    │
    ▼
┌─────────────────────────┐
│        vminsert          │
│                          │
│  1. 解析写入协议          │
│  2. 计算 hash(series)     │
│  3. hash % N = 节点索引   │
│  4. 转发到对应 vmstorage  │
└──────────────────────────┘
    │           │           │
    ▼           ▼           ▼
vmstorage0  vmstorage1  vmstorage2

关键参数:

参数默认值说明
-storageNodevmstorage 地址列表
-replicationFactor1副本数(1 = 无副本)
-maxLabelsPerTimeseries30每个时间序列最大 label 数
-dedup.minScrapeInterval0s去重间隔

3.4.2 vmstorage(存储层)

职责

  • 接收并持久化时序数据
  • 维护倒排索引
  • 响应 vmselect 的数据查询
  • 处理数据保留(retention)和合并(merge)
vmstorage 内部结构:
┌──────────────────────────────┐
│         vmstorage            │
│                              │
│  ┌────────────────────────┐  │
│  │    内存缓存 (Cache)     │  │
│  │  - 最近写入的数据       │  │
│  │  - 热数据索引           │  │
│  └────────────────────────┘  │
│                              │
│  ┌────────────────────────┐  │
│  │    活跃 Part            │  │
│  │  - 接收新数据           │  │
│  │  - 定期 flush 到磁盘    │  │
│  └────────────────────────┘  │
│                              │
│  ┌────────────────────────┐  │
│  │    存储层               │  │
│  │  - Part 0 (最旧)       │  │
│  │  - Part 1              │  │
│  │  - Part 2              │  │
│  │  - Part N (最新)       │  │
│  └────────────────────────┘  │
│                              │
│  ┌────────────────────────┐  │
│  │    倒排索引             │  │
│  │  - metric name 索引    │  │
│  │  - label 索引          │  │
│  └────────────────────────┘  │
└──────────────────────────────┘

关键参数:

参数默认值说明
-storageDataPathvictoria-metrics-data数据存储路径
-retentionPeriod1 个月数据保留期
-memory.allowedPercent60可用内存百分比
-dedup.minScrapeInterval0s去重间隔
-minScrapeInterval0s最小采集间隔

3.4.3 vmselect(查询层)

职责

  • 接收查询请求(MetricsQL / PromQL)
  • 将查询扇出(fan-out)到所有 vmstorage 节点
  • 合并各节点的中间结果
  • 返回最终结果
  • 管理查询结果缓存
查询请求:cpu_usage{host="web01"}
              │
              ▼
        ┌──────────┐
        │ vmselect  │
        └─────┬────┘
              │ 并行发送到所有 vmstorage
     ┌────────┼────────┐
     ▼        ▼        ▼
  storage0  storage1  storage2
  (部分数据) (部分数据) (部分数据)
     │        │        │
     └────────┼────────┘
              │ merge 结果
              ▼
        ┌──────────┐
        │ vmselect  │ 排序、去重、返回
        └─────┬────┘
              │
              ▼
          最终结果

关键参数:

参数默认值说明
-storageNodevmstorage 地址列表
-cacheDataPath内存缓存查询缓存存储路径
-dedup.minScrapeInterval0s去重间隔
-search.maxQueryDuration30s最大查询耗时
-search.maxConcurrentRequests16最大并发查询数

3.5 数据分片机制

3.5.1 一致性哈希

vminsert 使用一致性哈希将时间序列分配到 vmstorage 节点:

哈希环 (Hash Ring)
         0
         │
    ┌────┴────┐
    │         │
  节点0     节点1
  (120°)    (240°)
    │         │
    └────┬────┘
         │
       节点2
       (360°=0°)

series = {__name__="cpu_usage", host="web01"}
hash(series) = 180°  → 分配到 节点1

3.5.2 分片策略

# vminsert 将数据分片到 3 个 vmstorage
vminsert \
    -storageNode=vmstorage1:8400,vmstorage2:8400,vmstorage3:8400

# 每个时间序列始终写入同一个 vmstorage
# 这保证了单个 vmstorage 对某个 series 的数据完整性

3.5.3 扩缩容影响

操作影响是否需要数据迁移
增加 vmstorage新 series 分配到新节点否(旧数据仍在原节点)
减少 vmstorage对应 series 查询可能失败是(需要数据迁移)
增加 vmselect查询并发能力增加
增加 vminsert写入并发能力增加

注意:减少 vmstorage 节点会导致部分数据不可查询。生产环境扩容容易、缩容需要规划数据迁移。


3.6 复制机制

3.6.1 配置副本

# vminsert 设置副本因子为 2
vminsert \
    -storageNode=vmstorage1:8400,vmstorage2:8400,vmstorage3:8400 \
    -replicationFactor=2

3.6.2 副本写入流程

replicationFactor=2 时:

写入请求
    │
    ▼
vminsert
    │
    ├──── hash(series) → 主节点 vmstorage0
    │
    └──── hash(series) + 1 → 副本节点 vmstorage1

任意一个节点宕机,数据仍可查询

3.6.3 副本数选择建议

节点数建议副本数容错能力存储开销
11(无副本)0 节点1x
221 节点2x
321 节点2x
5+2 或 31-2 节点2-3x

提示:副本数不能大于 vmstorage 节点数,否则 vminsert 会报错。


3.7 数据保留与过期

3.7.1 保留策略

# 保留 90 天数据
vmstorage -retentionPeriod=90d

# 保留 2 年数据
vmstorage -retentionPeriod=730d

# 保留 6 个月
vmstorage -retentionPeriod=6M

3.7.2 过期删除机制

数据过期流程:

1. 后台任务每小时检查一次
2. 识别包含过期数据的 Part
3. 如果 Part 全部过期 → 直接删除整个 Part
4. 如果 Part 部分过期 → 重写 Part(保留未过期数据)
5. 释放磁盘空间

时间线:
├── Part A (全过期) ──▶ 直接删除 ✅
├── Part B (部分过期) ──▶ 重写为 Part B' ✅
└── Part C (未过期) ──▶ 保留 ⏭️

注意:磁盘空间不会立即释放,需要等待文件系统回收。使用 SSD 时,建议预留 20-30% 额外空间。


3.8 与 Prometheus TSDB 架构对比

维度Prometheus TSDBVictoriaMetrics
数据结构WAL + BlockPart + Merge
索引正排 + 倒排倒排索引
压缩Gorilla自研压缩(更强)
分块大小2 小时动态合并
内存模式mmap自管理内存
多租户不支持支持(集群版)
水平扩展不支持原生支持
数据更新不支持不支持(追加写入)

3.9 内部通信协议

集群组件间通信

vminsert ──(net.InsertProtobuf)──▶ vmstorage
    写入协议:protobuf 序列化,HTTP/gRPC

vmselect ──(net.SelectProtobuf)──▶ vmstorage
    查询协议:protobuf 序列化,HTTP/gRPC

端口分配(默认):
┌─────────────┬──────────┬──────────┬──────────┐
│  组件        │ HTTP API │ 写入端口  │ 查询端口  │
├─────────────┼──────────┼──────────┼──────────┤
│ vminsert    │ 8480     │ -        │ -        │
│ vmselect    │ 8481     │ -        │ -        │
│ vmstorage   │ 8482     │ 8400     │ 8401     │
└─────────────┴──────────┴──────────┴──────────┘

本章小结

要点内容
存储引擎追加写入 + 不可变 Part + 后台合并
压缩策略时间戳 delta-of-delta,值 XOR 编码,7-10x 压缩率提升
集群组件vminsert(写入路由)、vmstorage(存储)、vmselect(查询聚合)
分片一致性哈希,扩容容易缩容难
复制支持多副本,副本数 ≤ 节点数
保留Part 级别管理,后台定期清理过期数据

扩展阅读