VictoriaMetrics 集群版:三组件架构与水平扩展机制解析
假设你的 Prometheus 已经运行了一段时间,单实例的存储成为瓶颈:数据量增长到几百 GB,查询范围稍大就超时,硬盘快满却不敢删旧数据。这时候你会考虑 VictoriaMetrics——一个兼容 Prometheus 远程写入协议的时序数据库,用更低的资源占用存储更多数据。但很快你会发现它有两个形态:单机版(Single-node)和集群版(Cluster)。单机版部署简单,集群版则有 vminsert、vmstorage、vmselect 三个组件。本文拆解这三个组件的职责分工、数据流转路径,以及什么时候该从单机版迈向集群版。
版本说明
原写于 2024-08,2026-09 重写。
基于 VictoriaMetrics 1.102+ 版本。集群版架构设计在 v1.80 后趋于稳定,但参数和端口可能随版本微调,部署前请查阅对应版本的官方文档确认。
# 1. 从 Prometheus 到 VictoriaMetrics:为什么需要它
Prometheus 的设计哲学是「每个服务一个实例,横向通过联邦聚合」。这种模型适合中小规模,但当监控目标膨胀到成千上万、指标基数(cardinality)达到百万级时,单实例 Prometheus 会遇到几个硬瓶颈:
- 存储限制:Prometheus 默认只保留 15 天数据,TSDB 没有内置压缩机制,长期存储成本高昂
- 查询限制:大时间范围的聚合查询会触发大量磁盘 I/O,单线程查询模型容易超时
- 高可用缺口:Prometheus 没有原生存储级的高可用,只能靠外部 Thanos/Cortex 或双写
VictoriaMetrics 作为 Prometheus 的远程存储后端,用几个设计解决了这些问题:
- 更高的数据压缩率(官方称可达 10x,相比 Prometheus 原始存储)
- 支持长期保留(通过
-retentionPeriod参数) - 单机版即可水平扩展到多核,集群版进一步拆解读写路径
但 VictoriaMetrics 不是 Prometheus 的替代品——它承接 Prometheus 的远程写入(remote_write),但告警规则、服务发现、抓取(scrape)仍然依赖 Prometheus 或其生态(如 vmagent)。准确说,VictoriaMetrics 是「存储与查询层」,Prometheus(或 vmagent)是「采集层」。
# 2. 集群版三组件:职责与数据流
VictoriaMetrics 集群版由三个独立可扩展的组件构成,每个组件都是无状态的(除 vmstorage 的磁盘数据外),可以独立扩缩容。
# 2.1 vmstorage:存储节点
职责:存储原始数据,为查询提供指定时间范围和标签过滤的数据。
vmstorage 是唯一有状态的组件,它在本地磁盘维护:
- 原始时间序列数据(按时间分片的 LSM 结构)
- IndexDB(metric name 和 label 的倒排索引)
- 元数据(TSID 映射等)
关键参数:
vmstorage-prod \
-storageDataPath=/data/vmstorage \
-retentionPeriod=3 \
-vminsertAddr=:8400 \
-vmselectAddr=:8401 \
-httpListenAddr=:8482
2
3
4
5
6
-retentionPeriod:保留月数,超过的数据自动清理-vminsertAddr:接收来自 vminsert 的数据写入(TCP)-vmselectAddr:接收来自 vmselect 的查询请求(TCP)
# 2.2 vminsert:写入网关
职责:接收来自采集端(Prometheus/vmagent)的远程写入请求,按一致性哈希将数据分发给 vmstorage 节点。
vminsert 本身不存储数据,它是无状态的写入代理。收到一批指标后,它会:
- 解析 Prometheus remote_write 协议的数据块
- 对每条时间序列计算哈希(基于 metric name + 所有 label)
- 根据哈希值将数据路由到对应的 vmstorage 节点
关键参数:
vminsert-prod \
-storageNode=vmstorage-1:8400,vmstorage-2:8400,vmstorage-3:8400 \
-replicationFactor=2 \
-httpListenAddr=:8480
2
3
4
-storageNode:所有 vmstorage 节点的地址列表(逗号分隔)-replicationFactor:副本数,设为 N 表示每条数据存 N 份到 N 个不同节点
当某个 vmstorage 节点不可用时,vminsert 会自动将数据路由到剩余健康节点,保证写入不中断(但剩余节点会承担更多负载)。
# 2.3 vmselect:查询聚合器
职责:接收查询请求(PromQL),并行从所有 vmstorage 节点拉取数据,在内存中合并、去重、计算后返回结果。
vmselect 是无状态的查询代理,它本身不缓存数据(除了查询过程中的临时结果)。查询流程:
- 接收 PromQL 查询请求
- 并行向所有配置的 vmstorage 节点发送子查询
- 合并各节点返回的数据(按时间序列去重)
- 执行聚合函数(sum、rate 等)
- 返回最终结果
关键参数:
vmselect-prod \
-storageNode=vmstorage-1:8401,vmstorage-2:8401,vmstorage-3:8401 \
-replicationFactor=2 \
-search.denyPartialResponse=false \
-httpListenAddr=:8481
2
3
4
5
-replicationFactor:必须与 vminsert 配置一致,告诉 vmselect「数据存了几份」-search.denyPartialResponse:设为 true 时,如果任意 vmstorage 节点不可用,查询返回错误而非部分数据
# 2.4 数据流全景
整体数据路径如下:
[Prometheus/vmagent] --remote_write--> [vminsert:8480]
|
| (分片路由)
v
+----------------+---------------+
| | |
[vmstorage-1] [vmstorage-2] [vmstorage-3]
[:8400/:8401] [:8400/:8401] [:8400/:8401]
^ ^ ^
| | |
+----------------+---------------+
|
| (并行查询)
v
[Grafana/API] <--PromQL-- [vmselect:8481]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这三个组件之间通过 TCP 通信(vminsert→vmstorage 的 8400 端口,vmselect→vmstorage 的 8401 端口),HTTP 端口(8480/8481/8482)用于接收客户端请求和暴露监控指标。
# 3. vmagent 的角色与采集链路
在完整的数据链路中,vmagent 通常位于 Prometheus 与 vminsert 之间,承担「边缘采集 + 远程写入代理」的角色。它的核心职责:
- 替代 Prometheus 做抓取:vmagent 实现了 Prometheus 的抓取逻辑,可以配置
scrape_configs直接拉取目标指标,但资源占用远低于 Prometheus - 聚合与改写:支持
relabel_configs对指标进行标签改写、丢弃、聚合 - 多远端写入:支持同时向多个 vminsert 地址写入(
-remoteWrite.url),实现写入层高可用 - 本地 WAL 缓冲:当远端 vminsert 不可用时,数据写入本地 WAL,恢复后自动重传
Prometheus 与 vmagent 的对接方式:
# prometheus.yml 中的 remote_write 配置
remote_write:
- url: "http://vmagent:8429/api/v1/write"
queue_config:
max_samples_per_send: 10000
max_shards: 10
# 或直接指向 vminsert(无 vmagent 时)
remote_write:
- url: "http://vminsert:8480/insert/0/prometheus/api/v1/write"
2
3
4
5
6
7
8
9
10
vmagent 的 HTTP 端口(默认 8429)接收 Prometheus remote_write 协议,内部做缓冲后再转发给配置的 vminsert。这种分层的好处是:
- 边缘节点(机房、K8s 集群)部署 vmagent 做本地缓冲,跨网络写入中心 vmcluster
- vminsert 故障时,vmagent 本地队列兜底,避免数据丢失
- 中心机房专注存储,不直接暴露在边缘网络的公网访问中
# 3. 集群拓扑选择:单机版 vs 集群版
# 3.1 单机版足够的情况
VictoriaMetrics 单机版已经非常高效,可以处理:
- 每秒百万级数据点写入
- 数 TB 的存储(取决于磁盘)
- 多核并行查询
推荐先用单机版的情况:
- 监控目标 < 1000 个节点
- 指标基数(unique time series)< 1000 万
- 不需要跨可用区容灾
- 团队运维资源有限
# 3.2 需要集群版的信号
- 单机 CPU 或磁盘 I/O 成为瓶颈,垂直扩容(换更大机器)成本高
- 需要持续保留 6 个月以上数据,单机磁盘容量不够
- 对可用性要求高(单节点故障不能中断写入或查询)
- 写入和查询负载需要独立扩容(写入量大但查询少,或反之)
# 3.3 集群版的代价
集群版不是「更强版本的单机版」,它带来额外的运维复杂度:
- 需要管理三个独立的进程/服务
- vmstorage 扩容后,历史数据不会自动重新平衡(只有新数据会写到新节点)
- 网络延迟增加(组件间通过 TCP 通信)
- 需要额外的负载均衡器(如 nginx、vmauth)在 vminsert 和 vmselect 前做代理
预期输出
# 检查 vmstorage 本地存储占用
du -sh /data/vmstorage/data/*
# 预期看到按月份分片的目录,如 big/2024_08, small/2024_08
# 查看 vmstorage 指标(本地 http 接口)
curl -s http://localhost:8482/metrics | grep vm_rows_added_to_storage_total
# 预期返回累计写入的数据行数
2
3
4
5
6
7
# 4. 关键配置参数与容量规划
# 4.1 副本与一致性
-replicationFactor 是集群版最重要的参数之一:
- 设为 1:无冗余,单 vmstorage 节点故障会导致该分片数据不可查
- 设为 2:每条数据存两份,可容忍任意一个 vmstorage 节点故障
- 设为 3:更高冗余,但存储成本翻倍
注意:副本数在写入侧(vminsert)和查询侧(vmselect)必须配置一致,否则 vmselect 会按错误的副本预期做查询,导致数据缺失或重复。
# 4.2 副本与去重的配合
当 -replicationFactor > 1 时,同一指标数据会存在多个 vmstorage 节点上。vmselect 查询时会拿到多份相同数据,需要在查询端去重。VictoriaMetrics 通过以下机制处理:
- 查询时去重:vmselect 默认会对返回的时间序列按 (metric name + labels + timestamp) 去重,保留最新值
-dedup.minScrapeInterval:可配置最小采集间隔,用于识别「同一指标相近时间的多个采样点」是否为重复。如果两个采样点时间差小于此值,后一个覆盖前一个
示例场景:
- replicationFactor=2,两个 vmstorage 节点都存有
cpu_usage{host="A"} 50 - vmselect 查询时从两个节点各拿到一条,去重后只返回一条给客户端
- 如果因为时钟漂移或网络延迟,两条数据 timestamp 相差 1ms,
-dedup.minScrapeInterval=1ms会保留后写入的那条
配置建议:
- 生产环境建议设
-dedup.minScrapeInterval略大于实际采集间隔(如采集间隔 15s,设 20s),避免抖动导致的重复计数 - 该参数只在 vmselect 配置,vminsert 无需关心
# 4.3 扩容操作与节点数规划
-replicationFactor 与 vmstorage 节点数的关系:
| vmstorage 节点数 | replicationFactor | 容错能力 | 存储利用率 |
|---|---|---|---|
| 3 | 1 | 0(单点故障丢数据) | 100% |
| 3 | 2 | 1(容忍任意 1 节点故障) | 200% |
| 3 | 3 | 2(容忍任意 2 节点故障) | 300% |
| 5 | 2 | 1 | 200% |
| 5 | 3 | 2 | 300% |
扩容(增加 vmstorage 节点)的详细步骤:
# 1. 启动新 vmstorage 节点(假设新节点为 vmstorage-4)
vmstorage-prod \
-storageDataPath=/data/vmstorage \
-retentionPeriod=3 \
-vminsertAddr=:8400 \
-vmselectAddr=:8401 \
-httpListenAddr=:8482
# 2. 滚动更新所有 vminsert,加入新节点地址
# 修改 vminsert 启动参数:
vminsert-prod \
-storageNode=vmstorage-1:8400,vmstorage-2:8400,vmstorage-3:8400,vmstorage-4:8400 \
-replicationFactor=2 \
-httpListenAddr=:8480
# 3. 滚动更新所有 vmselect,加入新节点地址
vmselect-prod \
-storageNode=vmstorage-1:8401,vmstorage-2:8401,vmstorage-3:8401,vmstorage-4:8401 \
-replicationFactor=2 \
-httpListenAddr=:8481
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
关键限制: VictoriaMetrics 的分片基于一致性哈希,但扩容时不会触发历史数据重分布。新加入的节点只承接「扩容后新写入」的数据,扩容前已有的数据仍留在原来的节点上。这意味着:
- 查询性能不会立即均匀化(旧数据还在旧节点)
- 磁盘使用率也不会立即平衡(旧节点仍有历史数据)
- 随着时间推移和旧数据 TTL 过期,各节点数据量会自然趋近平衡
如果需要强制平衡,只能通过「下线旧节点 → 删除数据 → 重新加入」的方式,但这会导致该节点上所有历史数据丢失(需确保 replicationFactor 足够从其他副本恢复)。
缩容(移除 vmstorage 节点)需要更谨慎:
- 确保
-replicationFactor足够(缩容后仍有冗余) - 修改 vminsert 和 vmselect 的
-storageNode移除目标节点,重启 - 等待数据在其他节点自然过期(由
-retentionPeriod控制),或手动清理
预期输出
# 查看 vmstorage 集群状态(vmselect 端点)
curl -s "http://vmselect:8481/api/v1/status/tsdb" | jq '.data {seriesCountByLabelName, seriesCountByMetricName}'
# 预期返回当前 TSDB 中的时间序列统计
# 检查各 vmstorage 节点的健康状态
curl -s http://vmstorage-1:8482/health
# 预期返回 "OK"
2
3
4
5
6
7
# 5. 部署后验证清单
集群搭起来后,需要验证数据真的在流动:
# 5.1 写入验证
# 1. 确认 vminsert 接收数据(查看自身指标)
curl -s http://vminsert:8480/metrics | grep vm_protoparser_rows_read_total
# 2. 确认 vmstorage 存储数据
curl -s http://vmstorage:8482/metrics | grep vm_rows_added_to_storage_total
2
3
4
5
# 5.2 查询验证
# 查询最近 5 分钟的数据(替换为实际存在的指标名)
curl -s "http://vmselect:8481/api/v1/query?query=up&time=$(date +%s)" | jq '.data.result | length'
# 预期返回非空结果(返回时间序列数量)
2
3
# 5.3 高可用验证
# 停止一个 vmstorage 节点,检查写入是否继续
systemctl stop vmstorage-2
# 检查 vminsert 指标,确认重路由发生
curl -s http://vminsert:8480/metrics | grep vm_storage_node_conns
# 检查 vmselect 是否返回 partial response
curl -s "http://vmselect:8481/api/v1/query?query=up" | jq '.isPartial'
# 预期返回 true(表示数据不完整但可用)或 null(取决于 denyPartialResponse 配置)
2
3
4
5
6
7
8
9
# 6. 常见坑与边界
# 6.1 副本数配置不一致
vminsert 配了 -replicationFactor=2,vmselect 配了默认的 1,会导致 vmselect 只期望每份数据在一个节点上,查询时可能漏掉副本节点上的数据。必须保证两边配置一致。
# 6.2 vmstorage 节点时钟不同步
vmstorage 节点依赖本地时间进行数据分片和过期清理。如果节点间时钟偏差过大,可能导致数据分布不均或提前/延迟过期。部署前必须配置 NTP。
# 6.3 扩容后期望立即平衡
很多运维习惯 Elasticsearch 那种「加节点后自动重新分片」的行为,但 VictoriaMetrics 没有自动重平衡。新节点只承接新数据,历史数据仍在旧节点。如需强制平衡,只能让旧节点下线重建(会丢失该节点历史数据)。
# 6.4 防火墙只开放 HTTP 端口
vminsert 连接 vmstorage 的 8400 端口(TCP 自定义协议),vmselect 连接 vmstorage 的 8401 端口(TCP 自定义协议)。如果只开放 8482 HTTP 端口,集群无法通信。必须确保 8400/8401 端口可达。
# 7. 排查指南:当查询不到数据时
vmselect 查不到数据时,需要分阶段定位是「写入链路断了」还是「查询链路问题」。
# 7.1 确认写入侧是否正常
# 1. 检查 vminsert 是否收到数据
curl -s http://vminsert:8480/metrics | grep vm_protoparser_rows_read_total
# 2. 检查 vminsert 是否能连接到所有 vmstorage
curl -s http://vminsert:8480/metrics | grep vm_storage_node_conns
# 预期看到每个 storageNode 对应一条 conn 指标,值为 1 表示连接正常
# 3. 检查 vmstorage 是否实际存储了数据
curl -s http://vmstorage:8482/metrics | grep vm_rows_added_to_storage_total
# 这个值应该持续增长
# 4. 检查 vmstorage 磁盘是否已满
curl -s http://vmstorage:8482/api/v1/status/tsdb | jq '.data.freeDiskSpaceBytes'
2
3
4
5
6
7
8
9
10
11
12
13
# 7.2 确认查询侧是否正常
# 1. 直接查询 vmselect 可用的指标列表
curl -s "http://vmselect:8481/api/v1/label/__name__/values" | jq '.data[]'
# 2. 检查 vmselect 到各 vmstorage 的连接
curl -s http://vmselect:8481/metrics | grep vmselect_storage_nodes
# 3. 查看是否有 partial response
curl -s "http://vmselect:8481/api/v1/query?query=up" | jq '.isPartial'
# null 或 false 表示数据完整;true 表示部分节点未返回数据
2
3
4
5
6
7
8
9
# 7.3 常见症状与定位
| 症状 | 可能原因 | 排查命令 |
|---|---|---|
| vmselect 返回空结果,但指标确定存在 | 时间范围错误、标签不匹配 | 检查 query 的 start/end 时间戳 |
| vmselect 返回 isPartial=true | 部分 vmstorage 节点故障或网络不通 | 检查 vmselect 到各节点的连通性 |
| vminsert rows_read 增长但 storage rows_added 不增长 | vminsert 到 vmstorage 连不通 | 检查 8400 端口连通性 |
| 所有指标都查不到,但服务都running | vmselect 配置的 storageNode 端口错误 | 确认 vmselect 用 8401 而非 8400 |
最关键的检查点:vminsert 用 8400 端口连 vmstorage,vmselect 用 8401 端口连 vmstorage。如果 vmselect 误配成 8400,查询会无结果但也不会报错(因为 8400 是 vmstorage 的插入端口,查询请求会被静默丢弃)。
Agent 可直接解析的元数据块
{
"article_id": "victoriametrics-cluster",
"permalink": "/pages/victoriametrics-cluster-install/",
"category": "Linux笔记/监控",
"tags": ["可观测性", "VictoriaMetrics", "时序数据库", "集群"],
"commands": {
"check_storage_usage": "du -sh /data/vmstorage/data/*",
"check_ingest_metrics": "curl -s http://localhost:8480/metrics | grep vm_protoparser_rows_read_total",
"check_storage_metrics": "curl -s http://localhost:8482/metrics | grep vm_rows_added_to_storage_total",
"query_via_select": "curl -s 'http://vmselect:8481/api/v1/query?query=up&time=$(date +%s)'",
"check_health": "curl -s http://vmstorage:8482/health",
"check_tsdb_status": "curl -s http://vmselect:8481/api/v1/status/tsdb"
},
"config_keys": [
"-storageNode",
"-replicationFactor",
"-retentionPeriod",
"-storageDataPath",
"-vminsertAddr",
"-vmselectAddr",
"-search.denyPartialResponse"
],
"key_insights": [
"vmstorage是唯一有状态组件,存储所有原始数据和索引",
"vminsert是无状态写入代理,按一致性哈希分片",
"vmselect是无状态查询代理,并行查询所有vmstorage并合并结果",
"副本数在写入侧和查询侧必须配置一致",
"扩容只影响新数据,历史数据不会自动重平衡"
],
"version_assertions": {
"based_on": "v1.102+",
"architecture_stable_since": "v1.80",
"source": "https://docs.victoriametrics.com/cluster-victoriametrics/"
},
"misconceptions": [
"VictoriaMetrics可以替代Prometheus(错,它只替代存储和查询,采集仍需要Prometheus/vmagent)",
"集群版是单机版的升级替换(错,集群版有额外的运维复杂度,单机版能先撑很久)",
"扩容后数据会自动平衡(错,只有新数据写到新节点)",
"vmselect缓存查询结果(错,每次查询都穿透到vmstorage)"
],
"risk_level": "medium",
"related_articles": [
{
"title": "prometheus监控介绍",
"permalink": "/pages/87d3b8/",
"relationship": "前置阅读:Prometheus架构与数据模型"
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49