惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
博客园 - 司徒正美
TaoSecurity Blog
TaoSecurity Blog
Martin Fowler
Martin Fowler
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
S
Secure Thoughts
博客园 - 三生石上(FineUI控件)
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
K
Kaspersky official blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
H
Help Net Security
博客园 - 叶小钗
爱范儿
爱范儿
GbyAI
GbyAI
I
Intezer
M
MIT News - Artificial intelligence
Latest news
Latest news
Schneier on Security
Schneier on Security
T
Tor Project blog
Simon Willison's Weblog
Simon Willison's Weblog
I
InfoQ
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
CXSECURITY Database RSS Feed - CXSecurity.com
罗磊的独立博客
N
News and Events Feed by Topic
T
The Blog of Author Tim Ferriss
V2EX - 技术
V2EX - 技术
B
Blog
T
Tailwind CSS Blog
N
Netflix TechBlog - Medium
Security Latest
Security Latest
V
V2EX
F
Fortinet All Blogs
Forbes - Security
Forbes - Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
The Hacker News
The Hacker News
Scott Helme
Scott Helme
P
Privacy International News Feed
P
Palo Alto Networks Blog
H
Heimdal Security Blog
C
Cisco Blogs
T
The Exploit Database - CXSecurity.com
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
G
Google Developers Blog
W
WeLiveSecurity
L
LINUX DO - 最新话题

博客园 - 左扬

VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 与其他 TSDB 对比:Prometheus/InfluxDB/Thanos/VM VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 写入吞吐/查询延迟/内存占用的数学模型 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 模块依赖图——从 import 语句看组件关系 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Goroutine 池/atomic/零拷贝/sync.Pool VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 多租户架构——accountID/projectID 与 tenant 隔离 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 版本演进:1.146.0 LTS 重大更新解析 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 整体数据流:一条监控数据的完整生命周期 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 架构演进:从 TSDB 到 MergeSet 的设计取舍 VictoriaMetrics 1.146.0 源码【左扬精讲】—— 开篇总览 Rust 专题【左扬精讲】—— 从语法到灵魂:Ownership、Borrowing 与多语言对比 kubernetes 源码【左扬精讲】—— kube-scheduler 启动流程源码分析 Rust 专题【左扬精讲】—— 选择控制语句、运算符与格式化输出 Rust 专题【左扬精讲】—— 所有权详解 Rust 专题【左扬精讲】—— 作用域详解 Rust 专题【左扬精讲】—— 变量、常量与标量数据类型 kubernetes 源码 / Operator 专题【左扬精讲】—— Deployment Controller 源码分析:从对象创建到滚动更新 kubernetes 源码 / Operator 专题【左扬精讲】—— Operator 开发中的 Webhook:从准入控制到生产部署 Kubernetes源码 / Operator 专题【左扬精讲】—— 实现 Application Controller:从零构建生产级控制器 Kubernetes 编程 / Operator 专题【左扬精讲】—— 定义 Application 资源 + 添加自定义新 API 完整指南 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八):内部架构与核心组件 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八): —— 从入口到调度的全链路源码剖析(k8s v1.36.1) DeepSeek-R1 多模态 R1 / VLM-GRPO【左扬精讲】—— Qwen2-VL 微调与视觉推理强化学习实战 DeepSeek-R1 工业 RAG + 微调混合系统【左扬精讲】—— R1 系列收官之作:从 Prompt → RAG → 微调 选型决策树 DeepSeek-R1 推理时扩展【左扬精讲】—— o1 / R1 慢思考机制:Self-Consistency + ToT + PRM 详解 DeepSeek-R1 端侧 LLM 工程【左扬精讲】—— llama.cpp 调参与 Apple Silicon / 国产 NPU / Android 端侧落地全攻略 DeepSeek-R1 vLLM + k8s 生产部署【左扬精讲】—— 从单卡 7B 到 100 卡 671B MoE 集群的工业化部署实战 DeepSeek-R1 评估与系统(Evaluation & Systems)【左扬精讲】—— 从 GSM8K/MMLU 到 LLM-as-Judge 的工业级评估方法论 DeepSeek-R1 模型训练与算法【左扬精讲】—— GRPO 进阶算法:DAPO / PRIME / RLVR / PRM 四大 2025 前沿改进 DeepSeek-R1 模型训练与算法【左扬精讲】—— 数据蒸馏:用 DeepSeek-R1-671B 生成 800K 高质量 CoT 样本的完整流水线 DeepSeek-R1 优化与微调实战【左扬精讲】—— 从 R1 强化学习新范式到 GRPO 微调一站式入门 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 七):自定义插件开发实战 —— 手写一个 Score 插件并注册到集群 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 六):Scheduler Profile 与多调度器 —— 如何配置多个 profile 实现多租户、Coordinated LeaderElection Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 五):SchedulingQueue 与 QueueingHint —— 三段队列的细节、v1.36 新引入的 QueueingHint 工作机制 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 四):抢占(Preemption)算法剖析 —— DefaultPreemption 如何选 victim、PodDisruptionBudget 如何约束 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 二):内置插件逐个精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件 逐个精读 k8s 源码级精讲(二十六):调度器内置插件逐个精读 Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):Scheduling Framework 扩展点逐个源码拆解 Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):初识调度模型、内部架构与事件驱动机制 Kubernetes 编程 / client-go 专题【左扬精讲】—— 四种客户端:为什么、怎么选、怎么用 Kubernetes 编程 / Operator 专题【左扬精讲】—— controller-runtime、kubebuilder、operator-sdk 三大框架深度对比 Kubernetes 编程 / Operator 专题【左扬精讲】—— 深入理解 ManagedFields 字段冲突协调机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— k8s Finalizers 深度解析:对象的生命周期与删除控制 Kubernetes 编程 / Operator 专题【左扬精讲】—— OwnerReference 字段与级联删除机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— 深入学习 Server-Side Apply:managedFields 替代 last-applied-configuration 的演进方向 Kubernetes 编程 / Operator 专题【左扬精讲】—— k8s Annotations 与元数据体系(Operator 专题) Kubernetes 编程 / Operator 专题【左扬精讲】—— RESTMapper:把 Group / Version / Kind / Resource 四元组翻译成 REST 路径的"查字典"大师 Kubernetes 编程 / Operator 专题【左扬精讲】—— Converter 资源版本转换器 Kubernetes 编程 / Operator 专题【左扬精讲】—— Application 业务扩展:从单 Deployment 到多 Workload 的复合 Operator 演进 Kubernetes 编程 / Operator 专题【左扬精讲】—— OwnerReference / Finalizer / 准入控制:k8s 资源生命周期的三大支柱 Kubernetes 编程 / Operator 专题【左扬精讲】—— controller-runtime 框架内幕:从 Manager 到 Reconcile 的全栈拆解 Kubernetes 编程 / Operator 专题【左扬精讲】—— 生产级 Operator 最佳实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— application-operator Reconcile 循环源码精讲:从 client-go Informer 到 workqueue 的全链路解剖 Kubernetes 编程 / Operator 专题【左扬精讲】—— 从零搭建一个 application-operator 新项目:脚手架、API 设计与基于原生 DeploymentStatus/ServiceStatus 的状态建模 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:生产级 Controller 实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析: Controller 调试与诊断工具:从日志分析到问题定位 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DynamicClient 操作 CRD:无需代码生成的动态操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:控制器与 APIServer 完整交互流程:从 Watch 到缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:错误处理与重试机制:WorkQueue 限速器详解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Leader 选举机制:高可用控制器的必备技能 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Controller 开发模式完整实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:SharedInformerFactory 与等待缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:从认证配置到 Deployment 操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:版本对应、架构组件与组件关系 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Informer 源码深度解析:从底层原理到实战应用 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Reflector 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:ListWatcher 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Indexer 与 ThreadSafeStore 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DeltaFIFO 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:workqueue 核心原理与实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— runtime.Codec 资源编解码:serializer 与 codec 差异、编解码数据结构、codec 核心调用链路 Kubernetes 编程 / Operator 专题【左扬精讲】—— Scheme 资源注册机制全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 自定义资源的内部版本与外部版本:从源码看版本定义机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 1.36.1 核心 API 数据结构全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 构建过程 【AIOPS】一文读懂LLM【左扬精讲】:从诞生到普及,解锁大语言模型的核心密码 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Hooks 源码精讲:Hooks 可观测性的无侵入式实现 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 配置解析源码精讲 ——SRE 自定义 Agent 核心技巧 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 并发模型解析 ——SRE 应对高并发采集的调优思路 【AIOPS】AI Agent 专题【左扬精讲】基础架构篇:MCP-VictoriaMetrics Golang 源码整体架构拆解 ——SRE 必懂的核心模块与数据流 OpenTelemetry 开发实战【左扬精讲】—— 云原生可观测体系构建与分布式追踪二次开发 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(下篇)—— 预测式弹性扩缩容 Operator 落地实现) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(上篇)—— 时序预测模型构建与离线训练) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 6 —— 基于运维专家知识库的智能故障诊断与排查 Operator 实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 5 —— 基于大语言模型(LLM)的实时日志流智能监测 Operator 实现 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 4 —— 基于 Operator 实现大模型私有化部署与管理 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 3(上篇)—— 面向 AI / 算力调度场景:GPU 竞价实例资源池统一调度管理 Operator 开发 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目 2 —— 面向零售 / 电商潮汐流量难题:多云多集群数据中心级全链路弹性伸缩 DataCenter Scaler Operator 从 0 到 1 全链路开发 Kubernetes编程 / Operator专题【左扬精讲】—— 深入理解Kubebuilder注解:为什么Operator开发离不开这些特殊注释 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目1 —— Applicaion Operator(通用应用生命周期管理 Operator 实战) Pod 镜像拉取失败?kubectl edit pods修改镜像地址的底层原理与实操 (该方法仅为临时应急方案,并非长期解决方案) Kubernetes编程/Operator专题精讲—— 理解控制器模式 —— 控制器模式的核心原理与实现逻辑(从原理到实践) 【AIOPS】AI Agent 专题【左扬精讲】模型微调实战:一站式平台 LLaMA-Factory 【AIOPS】AI Agent 专题【左扬精讲】基于 k8s+vLLM+Ray 分布式部署全指南:架构设计、资源调度与性能优化 【AIOPS】AI Agent专题【左扬精讲】非量化版DeepSeek分布式部署全指南:精度保障、显存规划与Ollama/vLLM选型 【AIOPS】AI Agent 专题【左扬精讲】零开发框架实现 ReAct Agent(Go SRE友好)
VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Single-Node vs Cluster 模式本质区别
左扬 · 2026-06-28 · via 博客园 - 左扬

VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Single-Node vs Cluster 模式本质区别

在第一篇《设计哲学:为什么 VictoriaMetrics 能做到比 Prometheus 省 7x RAM》中,我们从宏观视角理解了 VM 的设计哲学——MergeSet 存储引擎、WAL-less 设计、TSIDCache 37% 策略。今天我们深入到架构层面,解答一个在实际选型时必须面对的问题:Single-Node 和 Cluster 模式到底有什么区别?我应该选哪个?

读完本篇,你应该能回答:Single-Node 的单进程架构是如何整合所有功能的?Cluster 的三层架构(vminsert/vmselect/vmstorage)各自承担什么职责?两种模式的伸缩性差异在哪里?什么场景下必须用 Cluster 模式?账号/项目级别的多租户隔离是如何实现的?

VictoriaMetrics Single-Node Cluster vminsert vmselect vmstorage 多租户 分布式架构 v1.146.0

学习重点提示建议先通读全文,再重点回顾标注内容

重点掌握(必须)

  • Single-Node 单进程架构:所有组件(接收、存储、查询)集成在一个进程内,通过内部 API 通信(app/victoria-metrics/
  • Cluster 三层分离架构:vminsert(写入入口)→ vmstorage(数据存储)→ vmselect(查询执行),通过 RPC 通信
  • 多租户隔离机制:accountID/projectID 两级隔离,Cluster 模式下天然支持,Single-Node 通过命名空间模拟
  • 选型决策树:根据数据规模、可用性要求、运维复杂度选择合适模式

次重点(了解即可)

  • Cluster 模式的启动参数和配置方式
  • Single-Node 到 Cluster 的迁移路径
  • Enterprise 版本的额外特性

文章目录

一、为什么需要了解 Single-Node 和 Cluster 的区别?——架构选择是生产部署的第一步

思考记忆提示本节是全篇的"入口"——弄清楚架构选择的背景和重要性,才能理解后面每种模式的设计取舍

  • 架构选择直接影响运维复杂度、数据容量、可用性三大关键指标
  • Single-Node 和 Cluster 不是"新旧版本"的关系,而是两种不同的部署形态
  • 面试高频提问:什么场景下 Single-Node 会遇到瓶颈?Cluster 模式的额外开销是什么?

当我们准备在生产环境部署 VictoriaMetrics 时,首先要做的决策不是"用什么版本的 VM",而是"用 Single-Node 还是 Cluster 模式"。这个选择看似简单,实则影响深远:选错了,可能面临扩容困难、运维复杂、或者资源浪费等一系列问题。

源码视角:Single-Node vs Cluster 的本质区别

从源码层面分析,两种模式的本质差异在于组件的部署形态和通信方式

Single-Node 的源码结构(app/victoria-metrics/

  • 进程模型:单一进程,所有组件(HTTP Server、Storage、vmselect)共享同一个 Go runtime
  • 通信方式:直接函数调用,如 storage.Add()storage.Search()
  • 入口文件app/victoria-metrics/main.go 中的 main() 函数依次调用各组件的 StartWithParams()

Cluster 的源码结构(app/vminsert/app/vmstorage/app/vmselect/

  • 进程模型:三个独立进程,每个进程运行独立的 Go runtime
  • 通信方式:HTTP/gRPC RPC 调用,通过 lib/storage/cluster.goInsertWithRoute()SearchWithRoute() 分发请求
  • 路由机制:一致性哈希(lib/consistenthash/),路由键为 accountID:projectID:partition

核心差异总结:Single-Node 是"进程内集成",Cluster 是"进程间分布式"。这个差异决定了:1)Single-Node 无网络开销,性能更高;2)Cluster 可以独立扩展每个组件,实现水平扩展。

VictoriaMetrics 官方在 官方文档 中明确指出:Single-Node 适合"大多数部署场景",Cluster 模式适合"需要水平扩展的高可用场景"。但这个说法太模糊,我们需要从源码层面理解两种模式的本质差异。

二、Single-Node 单进程架构——All-in-One 的工程哲学

思考记忆提示Single-Node 是理解 VM 架构的起点——它把所有组件集成在一起,方便理解组件间的关系

  • Single-Node 的核心是单进程 + 内部 API:写入、存储、查询都在同一个进程内完成
  • main.go 是入口,app/victoria-metrics/ 是 Single-Node 的核心代码
  • 面试高频提问:Single-Node 模式下,HTTP 请求是如何被处理的?存储层和查询层如何通信?

2.1 Single-Node 的进程结构

VictoriaMetrics Single-Node 是一个单一的 Go 二进制文件victoria-metrics),运行后表现为一个独立的进程。它整合了数据接收、存储、查询三大功能,以及 HTTP 服务层(用于 Prometheus 协议接入、Grafana 查询等)。

查看 app/victoria-metrics/main.go 的入口代码,可以看到 Single-Node 的初始化流程:

 // app/victoria-metrics/main.go(VictoriaMetrics v1.146.0)
// Single-Node 模式的入口点

package main

import (
    "flag"
    "net/http"
    "github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
    "github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
    "github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect"
    "github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
)

var (
    // Single-Node 模式下,这些组件共享同一个 storage 实例
    storageCluster = storage.Cluster()
    
    // HTTP 服务器配置
    httpListenAddr = flag.String("httpListenAddr", ":8428", "...")
    
    // 存储路径配置
    storageDataPath = flag.String("storageDataPath", "victoria-metrics-data", "...")
)

func main() {
    flag.Parse()
    
    // ========== 步骤1:初始化存储层(Single-Node 核心)==========
    // Single-Node 模式下,vmstorage 存储节点被嵌入到主进程中
    // 存储层负责:数据写入、Part 合并、查询执行
    go vmstorage.StartWithParams(vmstorage.Server{
        Addr:     *storageCluster,
        DataPath: *storageDataPath,
    })
    
    // ========== 步骤2:初始化查询层(vmselect)==========
    // vmselect 在 Single-Node 模式下指向本地的 vmstorage
    // 查询请求不经过网络,直接通过内部 API 调用 storage
    vmselect.InitWithConfig(nil)  // nil = 使用本地存储
    
    // ========== 步骤3:启动 HTTP 服务器==========
    // HTTP 层处理所有外部请求:
    // - /api/v1/write → 写入请求
    // - /api/v1/query → PromQL 查询
    // - /metrics → 自监控指标
    // - /debug/* → 诊断接口
    go httpserver.Serve(*httpListenAddr, requestHandler)
    
    // 主 goroutine 阻塞,等待关闭信号
    select {}
}

func requestHandler(w http.ResponseWriter, r *http.Request) {
    path := r.URL.Path
    
    switch {
    case strings.HasPrefix(path, "/api/v1/write"):
        // 写入请求 → Storage.Add()
        handleWrite(w, r)
    case strings.HasPrefix(path, "/api/v1/query"),
         strings.HasPrefix(path, "/api/v1/query_range"):
        // 查询请求 → vmselect.insertParsedQuery()
        handleQuery(w, r)
    default:
        // 其他请求走默认处理器
        http.DefaultServeMux.ServeHTTP(w, r)
    }
}

源码视角:Single-Node 初始化流程

app/victoria-metrics/main.gomain() 函数分析初始化流程:

步骤1:存储层初始化 → lib/storage/storage.go

  • vmstorage.StartWithParams() 初始化存储引擎
  • 创建 Table 实例(管理 Partition)和 indexDB 实例(倒排索引)
  • 初始化 TSIDCache(默认占 37% 内存)和 blockCache(三层缓存)

步骤2:查询层初始化 → app/vmselect/main.go

  • vmselect.InitWithConfig(nil) 传入 nil 参数表示使用本地存储
  • 与 Cluster 模式不同,这里不需要跨进程通信

步骤3:HTTP 服务启动 → lib/httpserver/httpserver.go

  • httpserver.Serve() 监听 :8428 端口
  • 注册路由:/api/v1/write → 写入,/api/v1/query → 查询

关键点:Single-Node 的三个步骤都在同一个进程内顺序执行,通过共享的 storage.Storage 全局变量实现组件间通信。

2.2 Single-Node 的组件集成关系

Single-Node 模式下,所有组件共享同一个存储实例。HTTP 层的写入请求直接调用 storage.Add(),查询请求直接调用 storage.Search()。这种集成方式的好处是:

  1. 零网络开销:进程内函数调用比网络 RPC 快几个数量级
  2. 资源利用率高:所有组件共享 CPU、内存、磁盘 I/O
  3. 运维简单:一个进程、一个端口、一套配置
  ┌────────────────────────────────────────────────────────────────────┐
│                    VictoriaMetrics Single-Node                      │
│                        (单进程模式)                                   │
│                                                                     │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │                     HTTP Server (:8428)                      │  │
│  │   /api/v1/write  │  /api/v1/query  │  /metrics  │ /debug/*  │  │
│  └─────────────────────────┬────────────────────────────────────┘  │
│                            │                                        │
│              ┌─────────────┴─────────────┐                         │
│              │                           │                          │
│              ▼                           ▼                          │
│     ┌─────────────────┐         ┌─────────────────┐                 │
│     │  InsertHandler  │         │  SelectHandler  │                 │
│     │   (写入处理)     │         │   (查询处理)     │                 │
│     └────────┬────────┘         └────────┬────────┘                 │
│              │                           │                          │
│              │    ┌─────────────────┐   │                          │
│              └────►   storage.Add() ◄───┘                          │
│                   └────────┬────────┘                               │
│                            │                                        │
│                   ┌────────▼────────┐                                │
│                   │    Storage     │                                │
│                   │  ┌──────────┐  │                                │
│                   │  │  Table   │  │                                │
│                   │  └────┬─────┘  │                                │
│                   │       ▼        │                                │
│                   │  ┌─────────┐   │                                │
│                   │  │Partition│   │                                │
│                   │  └────┬────┘   │                                │
│                   │       ▼        │                                │
│                   │  ┌─────────┐   │                                │
│                   │  │indexDB │   │                                │
│                   │  │(倒排索引)│   │                                │
│                   │  └─────────┘   │                                │
│                   └────────────────┘                                │
└────────────────────────────────────────────────────────────────────┘

设计精髓

Single-Node 模式采用了"微内核 + 内嵌服务"的架构模式。核心是 Storage 层(微内核),提供数据的写入、存储、查询能力;HTTP Server、Prometheus 兼容层、Grafana 兼容层都是"插入"到这个内核上的服务。这种设计让 Single-Node 可以保持极低的复杂度,同时提供完整的功能。

2.3 Single-Node 的容量边界

Single-Node 模式的容量受限于单机硬件资源。官方给出了一个参考数据:

资源维度Single-Node 上限说明
时间序列数量 ~500 万-1000 万 受内存限制,高基数标签会导致 OOM
写入吞吐 ~100 万 samples/s 受 CPU 和磁盘 I/O 限制
存储容量 ~50 TB-200 TB 受磁盘空间限制,与压缩率相关
查询延迟 P99 <1s(简单查询) 复杂查询可能达到数秒

注意

这些数字是参考值,实际上限取决于数据特征(标签基数、查询复杂度、硬件配置)。如果遇到 OOM、写入变慢、查询超时等问题,说明已经接近 Single-Node 的容量边界,需要考虑迁移到 Cluster 模式。

三、Cluster 三层分离架构——水平扩展的艺术

思考记忆提示Cluster 模式是理解 VM 分布式能力的核心——三层架构各有专职,通过 RPC 协同工作

  • vminsert = 写入入口层(接收请求 + 分片路由)
  • vmstorage = 数据存储层(数据分片 + 查询执行)
  • vmselect = 查询聚合层(查询分发 + 结果归并)
  • 面试高频提问:Cluster 模式下,写入请求是如何分片的?查询请求如何跨节点聚合结果?

3.1 Cluster 模式的三层架构

VictoriaMetrics Cluster 模式将写入(vminsert)、存储(vmstorage)、查询(vmselect)拆分为三个独立的二进制文件/进程。这种分离允许每个组件独立扩展,实现真正的水平扩展能力。

  ┌──────────────────────────────────────────────────────────────────────────────┐
│                         VictoriaMetrics Cluster                               │
│                          (三层分离架构)                                        │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐  │
│  │                          vminsert 集群 (N 个节点)                        │  │
│  │                    角色:写入入口 + 分片路由                             │  │
│  │         职责:接收 Remote Write 请求 → 根据 accountID/projectID 分片     │  │
│  └────────────────────────────────┬───────────────────────────────────────┘  │
│                                   │                                         │
│                   RPC (HTTP 或 gRPC)│                                        │
│                                   ▼                                         │
│  ┌────────────────────────────────────────────────────────────────────────┐  │
│  │                        vmstorage 集群 (N 个节点)                        │  │
│  │                         角色:数据存储 + 分片存储                         │  │
│  │     职责:存储数据分区 → 执行分区级查询 → 返回原始数据块                  │  │
│  │                                                                        │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  │  │
│  │  │  vmstorage  │  │  vmstorage  │  │  vmstorage  │  │  vmstorage  │  │  │
│  │  │  (shard-1)  │  │  (shard-2)  │  │  (shard-3)  │  │  (shard-4)  │  │  │
│  │  │  partition  │  │  partition  │  │  partition  │  │  partition  │  │  │
│  │  │   - 1月     │  │   - 2月     │  │   - 3月     │  │   - 4月     │  │  │
│  │  │   - 5月     │  │   - 6月     │  │   - 7月     │  │   - 8月     │  │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘  │  │
│  └────────────────────────────────┬───────────────────────────────────────┘  │
│                                   │                                         │
│                   RPC (HTTP 或 gRPC)│                                        │
│                                   ▼                                         │
│  ┌────────────────────────────────────────────────────────────────────────┐  │
│  │                          vmselect 集群 (N 个节点)                        │  │
│  │                         角色:查询入口 + 结果聚合                          │  │
│  │          职责:接收 PromQL → 分发到所有 vmstorage → 归并结果              │  │
│  └────────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

源码视角:Cluster 三层架构的职责划分

从源码分析 Cluster 三层各自承担的职责和它们之间的 RPC 调用链路:

vminsert(app/vminsert/main.go

  • 职责:接收外部写入请求,解析租户信息,执行一致性哈希路由,转发到 vmstorage
  • 关键函数:InsertWithRoute()lib/storage/cluster.go
  • 路由算法:consistenthash.New()lib/consistenthash/

vmstorage(app/vmstorage/main.go

  • 职责:存储分配给自己的分区数据,执行分区级查询,返回数据块而非聚合结果
  • 关键函数:handleClusterInsert()handleClusterSearch()
  • RPC 端点:/internal/insert/internal/search

vmselect(app/vmselect/main.go

  • 职责:接收 PromQL 查询,并行分发到所有 vmstorage,归并聚合结果
  • 关键函数:BroadcastQuery()MergeResults()
  • 并行策略:使用 Go goroutine 并发查询所有 vmstorage 节点,使用 channel 收集结果

为什么需要三层而不是两层?vminsert 专门负责写入路由,vmstorage 专门负责数据存储,vmselect 专门负责查询聚合。每层职责单一,便于独立扩展和故障隔离。如果只有两层,vminsert 既要路由又要聚合,职责过重,扩展性差。

3.2 vminsert 写入入口层

vminsert(位于 app/vminsert/)是 Cluster 模式的写入入口。它的核心职责是:

  1. 接收外部写入请求(Prometheus Remote Write、InfluxDB、OpenTelemetry 等)
  2. 根据请求中的 accountID/projectID 进行分片路由
  3. 将数据转发到对应的 vmstorage 节点
 // app/vminsert/main.go(Cluster 模式写入入口)
// vminsert 的职责:接收请求 + 分片路由 + 转发到 vmstorage

func main() {
    // ========== 步骤1:解析 Cluster 模式参数 ==========
    // -vminsert.* flags 定义了可用的 vmstorage 节点列表
    vmstorageAddrs := flag.String("vminsert.addr", "", 
        "Comma-separated list of vmstorage nodes")
    
    // 初始化一致性哈希路由器
    // 用于将 accountID/projectID 映射到具体的 vmstorage 节点
    router := consistenthash.New(&consistenthash.Config{
        HashFunction: consistenthash.FNVHash,
        Replication:  2,  // 每个分片复制到 2 个节点,保证高可用
    })
    router.AddNodes(vmstorageAddrs)
    
    // ========== 步骤2:启动 HTTP 服务器 ==========
    http.HandleFunc("/api/v1/write", func(w http.ResponseWriter, r *http.Request) {
        // 从请求路径或 Header 中提取租户信息
        accountID := getAccountID(r)  // 例如:来自 URL 路径 /account/123/project/456/api/v1/write
        projectID := getProjectID(r)
        
        // ========== 步骤3:分片路由 ==========
        // 使用一致性哈希确定目标 vmstorage 节点
        // 路由键 = accountID:projectID:month(按月分片)
        shardKey := fmt.Sprintf("%d:%d:%s", accountID, projectID, getMonth())
        targetNode := router.GetNode(shardKey)
        
        // ========== 步骤4:转发写入请求 ==========
        // 将请求通过 HTTP 或 gRPC 转发到目标 vmstorage 节点
        forwardToStorage(targetNode, r)
    })
    
    http.ListenAndServe(*httpListenAddr, nil)
}

// 一致性哈希路由算法
// 优势:当增加/删除节点时,只有少量数据需要重新映射
func (r *Router) GetNode(key string) string {
    hash := r.hashFunction([]byte(key))
    idx := sort.Search(len(r.ring), func(i int) bool {
        return r.ring[i] >= hash
    })
    return r.nodes[idx%len(r.nodes)]
}

小贴士一致性哈希的优势

vminsert 使用一致性哈希(Consistent Hashing)进行分片路由,相比简单取模哈希的优势是:当某个 vmstorage 节点宕机时,只有该节点负责的分片需要重新映射,其他分片不受影响。这保证了 Cluster 模式的高可用性——任何一个 vmstorage 节点宕机,只影响该节点负责的分片,不会导致整个集群不可用。

3.3 vmstorage 数据存储层

vmstorage(位于 app/vmstorage/)是 Cluster 模式的数据存储节点。每个 vmstorage 实例负责存储部分分区(partition)的数据,并提供分区级的查询能力。

 // app/vmstorage/main.go(Cluster 模式存储节点)
// vmstorage 的职责:接收写入 + 存储数据 + 执行分区级查询

func main() {
    // ========== 步骤1:初始化存储层 ==========
    // 与 Single-Node 不同,vmstorage 只负责自己分区内的数据
    storage.InitWithParams(storage.Config{
        DataPath:     *dataPath,
        RetentionDays: *retentionDays,
    })
    
    // ========== 步骤2:注册 RPC 处理器 ==========
    // Cluster 模式下,vmstorage 通过 HTTP/gRPC 提供 RPC 服务
    // 供 vminsert 和 vmselect 调用
    
    // 写入 RPC
    http.HandleFunc("/internal/insert", func(w http.ResponseWriter, r *http.Request) {
        // 接收来自 vminsert 的写入请求
        // 与 Single-Node 不同,这里的请求已经经过分片路由
        handleClusterInsert(w, r)
    })
    
    // 查询 RPC
    http.HandleFunc("/internal/search", func(w http.ResponseWriter, r *http.Request) {
        // 接收来自 vmselect 的查询请求
        // 执行分区级查询,返回匹配的数据块
        handleClusterSearch(w, r)
    })
    
    // ========== 步骤3:启动存储服务 ==========
    http.ListenAndServe(*httpListenAddr, nil)
}

// handleClusterInsert:处理来自 vminsert 的写入请求
func handleClusterInsert(w http.ResponseWriter, r *http.Request) {
    // 1. 从请求中解析数据点
    // 2. 调用 Storage.Add() 写入本地存储
    // 3. 返回确认响应
    storage.Add(rows)
    writeJSON(w, {"status": "ok"})
}

// handleClusterSearch:处理来自 vmselect 的查询请求
func handleClusterSearch(w http.ResponseWriter, r *http.Request) {
    // 1. 从请求中解析查询参数(PromQL、时间范围、租户信息)
    // 2. 调用 Storage.Search() 执行分区级查询
    // 3. 返回匹配的数据块列表(不是最终聚合结果)
    results := storage.Search(searchRequest)
    writeJSON(w, results)
}

源码视角:vmstorage 在 Cluster 中的数据分区机制

从源码层面分析 vmstorage 的分区策略和数据存储结构:

分区键计算(lib/storage/partition.go

  • 分区维度:时间(按月)+ 租户(accountID:projectID)
  • 路由键格式:fmt.Sprintf("%d:%d:%s", accountID, projectID, getMonth(timestamp))
  • 一致性哈希确保相同路由键永远路由到相同的 vmstorage 节点

数据存储结构(lib/storage/table.go

  • 每个 vmstorage 节点存储多个 Partition,每个 Partition 对应不同的月份
  • 每个 Partition 内部包含:InMemoryPart(内存缓冲)、SmallPartsBigParts
  • 倒排索引通过 indexDB.Search() 提供标签查询能力

为什么 vmstorage 返回"数据块"而非"最终结果"?

  • handleClusterSearch() 返回 []*SearchResult(数据块列表)
  • 聚合操作(sum/avg/count)在 vmselect 层的 MergeResults() 中执行
  • 设计原因:vmstorage 只需返回原始数据,保持简单;vmselect 负责复杂的 PromQL 语义解析和聚合计算

3.4 vmselect 查询聚合层

vmselect(位于 app/vmselect/)是 Cluster 模式的查询入口和结果聚合层。它负责接收外部查询请求、分发到所有相关 vmstorage 节点、归并聚合结果。

 // app/vmselect/main.go(Cluster 模式查询节点)
// vmselect 的职责:查询入口 + 分发 + 归并聚合

func main() {
    // ========== 步骤1:初始化 vmstorage 连接池 ==========
    // vmselect 需要知道所有 vmstorage 节点地址
    storageNodes := flag.String("storageNode.addrs", "",
        "Comma-separated list of vmstorage nodes")
    
    connPool := NewConnPool(storageNodes)
    
    // ========== 步骤2:注册查询处理器 ==========
    http.HandleFunc("/api/v1/query", func(w http.ResponseWriter, r *http.Request) {
        // ========== 查询分发阶段 ==========
        // 1. 解析 PromQL
        // 2. 确定需要查询哪些时间范围
        // 3. 向所有 vmstorage 节点发送查询请求(并行)
        responses := connPool.BroadcastQuery(queryRequest)
        
        // ========== 结果归并阶段 ==========
        // 1. 收集所有 vmstorage 的响应
        // 2. 按时间线对齐数据
        // 3. 执行 PromQL 聚合函数(sum/avg/count 等)
        // 4. 返回最终结果
        result := MergeResults(responses)
        writeJSON(w, result)
    })
}

// BroadcastQuery:向所有 vmstorage 并行发送查询
func (cp *ConnPool) BroadcastQuery(req *SearchRequest) []*SearchResult {
    results := make(chan *SearchResult, len(cp.nodes))
    
    for _, node := range cp.nodes {
        go func(n *Node) {
            // 并行查询每个 vmstorage 节点
            result, _ := n.Query(req)
            results 

设计精髓

vmselect 的查询分发采用了scatter-gather(分散-聚合)模式

  • 分散(Scatter):将查询请求并行发送到所有 vmstorage 节点
  • 聚合(Gather):收集所有响应后,使用 k-way 归并算法合并结果

这种模式的优点是:查询延迟由最慢的 vmstorage 决定,而不是所有节点延迟之和。如果某个 vmstorage 节点数据量少,它返回快;如果某个节点数据量大,它返回慢。最终延迟 = max(各节点延迟),而不是 sum(各节点延迟)。

四、两种模式的核心差异对比

思考记忆提示理解差异是选型的基础——本节用对比表格让你一目了然

  • Single-Node = 单进程 + 内部调用,适合小规模
  • Cluster = 多进程 + RPC 调用,适合大规模高可用
  • 面试高频提问:两种模式的性能差异?迁移成本?
对比维度Single-NodeCluster
进程数量 1 个进程 至少 3 种进程(vminsert/vmselect/vmstorage),每种可多实例
进程间通信 Go 函数调用(无网络开销) HTTP/gRPC RPC 调用(网络开销)
数据分片 无(单节点存储全量) 按时间+租户自动分片到多个 vmstorage
写入路径 HTTP → Storage.Add() HTTP → vminsert → vmstorage(两次 RPC)
查询路径 HTTP → Storage.Search() → 返回结果 HTTP → vmselect → 并行查询所有 vmstorage → 归并 → 返回结果
水平扩展 不支持(单节点) 支持(可增加 vminsert/vmselect/vmstorage 节点)
高可用 无(单点故障) 有(vmstorage 多副本、一致性哈希故障转移)
多租户 模拟支持(通过 -envflag.enable 开启) 原生支持(accountID/projectID 两级隔离)
运维复杂度 低(一套配置、一个进程) 高(需要协调多个进程、负载均衡、健康检查)
适用规模 < 1000 万 series 1000 万 ~ 10 亿+ series
写入延迟 更低(无 RPC 开销) 略高(两次 RPC)
查询延迟 低(直接本地查询) P99 略高(需归并多节点结果)
资源利用率 高(所有组件共享资源) 中等(各进程资源隔离,可能利用率不均)

源码视角:Single-Node 与 Cluster 的核心差异

从源码层面总结两种模式的核心差异:

进程模型对比

  • Single-Node:单一进程,三个组件(vminsert/vmstorage/vmselect)通过函数调用通信
  • Cluster:三个独立进程,通过 HTTP/gRPC RPC 调用通信

源码入口对比(app/ 目录)

  • Single-Nodeapp/victoria-metrics/main.go → 一个二进制文件
  • Clusterapp/vminsert/main.goapp/vmstorage/main.goapp/vmselect/main.go → 三个二进制文件

通信方式对比

  • Single-Node:直接函数调用 storage.Add()storage.Search()(无网络开销)
  • Cluster:一致性哈希路由 + RPC 调用(lib/consistenthash/

适用场景

  • Single-Node:≤ 1000 万 series,写入 ≤ 100 万 samples/s,运维简单优先
  • Cluster:> 1000 万 series,高可用,多租户,水平扩展优先

五、多租户隔离机制——accountID/projectID 两级体系

思考记忆提示多租户是 Cluster 模式的核心特性——理解两级隔离才能理解 Cluster 的分片逻辑

  • accountID = 一级租户(通常对应一个组织/团队)
  • projectID = 二级租户(通常对应一个项目/产品线)
  • 路由键 = accountID:projectID:partition
  • 面试高频提问:Single-Node 模式下如何实现多租户?

VictoriaMetrics 的多租户隔离采用两级体系:accountID(账户级)和 projectID(项目级)。这种设计源自其前身——VictoriaMetrics 最初是给大规模多租户 SaaS 服务设计的,因此多租户是核心特性。

5.1 租户标识的传递方式

在 Cluster 模式下,租户标识通过 URL 路径传递:

  # Cluster 模式下的写入 URL
http://vminsert-cluster:8400/account/{accountID}/project/{projectID}/api/v1/write

# Cluster 模式下的查询 URL
http://vmselect-cluster:8481/account/{accountID}/project/{projectID}/api/v1/query

# 示例
http://vminsert-cluster:8400/account/123/project/456/api/v1/write
# accountID = 123, projectID = 456

小贴士URL 路径 vs Header

租户标识可以通过 URL 路径(/account/{id}/project/{id}/)或 Header(X-Account-ID、X-Project-ID)传递。URL 路径更直观,Header 更灵活。VictoriaMetrics 两种方式都支持。

5.2 分片路由与租户隔离

vminsert 根据租户标识计算路由键(routing key),将数据分发到对应的 vmstorage 节点:

 // 租户路由键计算逻辑
// 路由键 = accountID:projectID:月份(按月分片)
func ComputeRoutingKey(accountID, projectID uint32, timestamp int64) string {
    month := getMonth(timestamp)  // 例如 "2024-01"
    
    // 路由键格式:accountID:projectID:month
    return fmt.Sprintf("%d:%d:%s", accountID, projectID, month)
}

// 一致性哈希路由
// 相同路由键永远路由到相同的 vmstorage 节点
func (r *Router) Route(key string) *StorageNode {
    hash := r.hashFunction([]byte(key))
    return r.ring.GetNode(hash)
}

// 示例:
// - accountID=1, projectID=1, 时间戳=2024-01-15 → 路由键="1:1:2024-01"
// - accountID=1, projectID=2, 时间戳=2024-01-15 → 路由键="1:2:2024-01"
// - accountID=2, projectID=1, 时间戳=2024-01-15 → 路由键="2:1:2024-01"
// 
// 相同 accountID/projectID 的数据会路由到同一个 vmstorage 节点
// 不同租户的数据天然隔离

源码视角:多租户隔离的两级路由机制

从源码层面分析 accountID/projectID 两级租户隔离的实现:

租户标识解析(lib/storage/tenant.go

  • 租户 ID 格式:accountID:projectID(uint32:uint32)
  • URL 路径解析:/account/{accountID}/project/{projectID}/api/v1/write
  • Header 方式:X-Account-IDX-Project-ID

路由键计算(lib/consistenthash/consistenthash.go

  • 路由键格式:fmt.Sprintf("%d:%d:%s", accountID, projectID, month)
  • 月份分区:getMonth(timestamp) → "2024-01" 格式
  • 一致性哈希:相同路由键 → 相同 vmstorage 节点(通过 FNVHash 实现)

数据隔离原理

  • 租户隔离在 vminsert 层通过路由实现:不同租户的数据自动路由到不同的 vmstorage 节点
  • 物理隔离:每个 vmstorage 节点只存储分配给自己的分区数据
  • 逻辑隔离:-replicationFactor=2 时每个分区有 2 个副本,分布在不同节点

5.3 Single-Node 的多租户模拟

Single-Node 模式原生不支持多租户隔离,但可以通过 -promscrape.config 或外部代理(如 vmauth)模拟多租户效果:

 // Single-Node 模式下,多租户通常通过 vmauth 实现
// vmauth = 认证代理,根据 API Key 路由到不同的后端

// vmauth 配置示例
// accounts:
//   - name: tenant-1
//     url_prefix:
//       - http://victoria-metrics:8428
//     api_keys:
//       - key: "token-tenant1-xxx"
//   - name: tenant-2
//     url_prefix:
//       - http://victoria-metrics:8428
//     api_keys:
//       - key: "token-tenant2-xxx"

// 使用方式
// Tenant-1 的 Prometheus:
//   remote_write:
//     - url: "http://vmauth:8427/api/v1/write"
//       bearer_token: "token-tenant1-xxx"
//
// Tenant-2 的 Prometheus:
//   remote_write:
//     - url: "http://vmauth:8427/api/v1/write"
//       bearer_token: "token-tenant2-xxx"

注意

Single-Node + vmauth 的方案可以实现"逻辑隔离"(不同租户的数据在同一个数据库里,通过标签区分),但无法实现"物理隔离"(不同租户的数据存在不同的存储节点)。如果需要严格的物理隔离(如数据主权合规),必须使用 Cluster 模式。

六、选型决策树——我应该用哪个模式?

思考记忆提示选型是生产部署的核心决策——本节给出清晰的决策框架

  • 选 Single-Node 的条件:规模小 + 运维简单
  • 选 Cluster 的条件:规模大 + 高可用 + 多租户
  • 面试高频提问:从 Single-Node 迁移到 Cluster 需要注意什么?
  ┌─────────────────────────────────────────────────────────────────────────────┐
│                         VictoriaMetrics 选型决策树                            │
│                                                                             │
│                           开始选型                                           │
│                              │                                              │
│                              ▼                                              │
│              ┌───────────────────────────────┐                            │
│              │ Q1: 预计时间序列数量是多少?       │                            │
│              └───────────────────────────────┘                            │
│                              │                                              │
│           ┌──────────────────┼──────────────────┐                           │
│           ▼                  ▼                  ▼                           │
│     < 100 万         100 万-1000 万        > 1000 万                      │
│           │                  │                  │                          │
│           ▼                  ▼                  ▼                           │
│      ┌─────────┐        ┌──────────┐       ┌──────────┐                    │
│      │  小规模  │        │  中规模   │       │  大规模   │                    │
│      │建议选   │        │建议选    │       │必须选    │                    │
│      │Single- │        │Single-   │       │Cluster  │                    │
│      │Node    │        │Node      │       │模式      │                    │
│      └─────────┘        └──────────┘       └──────────┘                    │
│           │                  │                  │                           │
│           └──────────────────┼──────────────────┘                           │
│                              ▼                                              │
│              ┌───────────────────────────────┐                            │
│              │ Q2: 需要高可用吗?              │                            │
│              └───────────────────────────────┘                            │
│                              │                                              │
│                    ┌─────────┴─────────┐                                  │
│                    ▼                   ▼                                    │
│                   是                   否                                   │
│                    │                   │                                    │
│                    ▼                   ▼                                    │
│              ┌──────────┐         ┌──────────┐                              │
│              │Cluster   │         │继续评估   │                              │
│              │(多副本) │         │下一题     │                              │
│              └──────────┘         └──────────┘                              │
│                                       │                                    │
│                              ┌─────────┴─────────┐                          │
│                              ▼                   ▼                          │
│                             是                   否                          │
│                              │                   │                           │
│                              ▼                   ▼                           │
│                      ┌──────────┐         ┌──────────┐                     │
│                      │需要严格   │         │继续评估   │                     │
│                      │物理隔离? │         │下一题     │                     │
│                      └──────────┘         └──────────┘                     │
│                                       │                                    │
│                              ┌─────────┴─────────┐                          │
│                              ▼                   ▼                          │
│                             是                   否                          │
│                              │                   │                           │
│                              ▼                   ▼                           │
│                      ┌──────────┐         ┌──────────┐                     │
│                      │Cluster   │         │Single-   │                     │
│                      │(唯一选择)│         │Node +   │                     │
│                      │          │         │vmauth   │                     │
│                      └──────────┘         └──────────┘                     │
└─────────────────────────────────────────────────────────────────────────────┘

选型决策口诀

  • 小规模单机足够:100 万 series 以下,单节点够用
  • 中规模单点风险可接受:100 万-1000 万 series,可以继续用单节点
  • 大规模必须集群:1000 万 series 以上,Cluster 是唯一选择
  • 高可用必须集群:任何规模,只要需要高可用就用 Cluster
  • 多租户物理隔离必须集群:合规要求下只能用 Cluster

七、源码解析——从 main.go 看架构初始化

思考记忆提示源码是理解架构的最佳途径——本节带你深入 main.go 看初始化流程

  • Single-Node 和 Cluster 的启动参数不同,入口点也不同
  • lib/logger 是统一的日志框架
  • 面试高频提问:VictoriaMetrics 如何根据参数决定运行 Single-Node 还是 Cluster 模式?

7.1 Single-Node 的 main.go 入口

 // app/victoria-metrics/main.go(Single-Node 入口)
package main

import (
    "flag"
    "log"
    "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
    "github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
    "github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
)

var (
    // 存储路径
    storageDataPath = flag.String("storageDataPath", "victoria-metrics-data", 
        "Path to storage data")
    
    // HTTP 监听地址
    httpListenAddr = flag.String("httpListenAddr", ":8428", 
        "HTTP listen address")
    
    // 日志级别
    loggerLevel = flag.String("loggerLevel", "INFO", 
        "Logger level: INFO/WARN/ERROR/DEBUG")
)

func main() {
    // ========== 步骤1:初始化日志 ==========
    logger.InitWithLevel(*loggerLevel)
    
    // ========== 步骤2:解析命令行参数 ==========
    flag.Parse()
    
    // ========== 步骤3:验证参数 ==========
    if *storageDataPath == "" {
        log.Fatal("-storageDataPath must be set")
    }
    
    // ========== 步骤4:初始化存储 ==========
    // storage.Init() 会创建存储目录、加载已有数据、启动后台合并任务
    if err := storage.Init(*storageDataPath); err != nil {
        log.Fatalf("storage.Init failed: %s", err)
    }
    
    // ========== 步骤5:启动 HTTP 服务器 ==========
    // HTTP 服务器处理所有外部请求
    go func() {
        if err := httpserver.Serve(*httpListenAddr, requestHandler); err != nil {
            log.Fatalf("httpserver.Serve failed: %s", err)
        }
    }()
    
    // ========== 步骤6:注册优雅关闭 ==========
    // 收到 SIGTERM/SIGINT 时,先停止接收请求,再等待后台任务完成
    setupGracefulShutdown()
    
    // ========== 步骤7:主 goroutine 阻塞 ==========
    <-make(chan struct{})
}

7.2 Cluster 各组件的启动参数

 // Cluster 模式各组件的典型启动参数

// vminsert 启动参数
// vminsert -storageNode=vmstorage-1:8400,vmstorage-2:8400,vmstorage-3:8400
type vminsertFlags struct {
    storageNodeAddrs  = flag.String("storageNode", "", 
        "Comma-separated list of vmstorage addresses")
    maxQueueSize     = flag.Int("maxQueueSize", 100_000, 
        "Max number of rows queued before dropping")
    insertTimeout     = flag.Duration("insertTimeout", 30*time.Second, 
        "Timeout for inserting data")
}

// vmstorage 启动参数
// vmstorage -storageDataPath=/data/vmstorage -retentionDays=90
type vmstorageFlags struct {
    storageDataPath   = flag.String("storageDataPath", "vmstorage-data", 
        "Path to storage data")
    retentionDays     = flag.Int("retentionDays", 0, 
        "Data retention period in days")
    convergeTimeout   = flag.Duration("convergeTimeout", 5*time.Second, 
        "Timeout for converge")
}

// vmselect 启动参数
// vmselect -storageNode=vmstorage-1:8401,vmstorage-2:8401,vmstorage-3:8401
type vmselectFlags struct {
    storageNodeAddrs  = flag.String("storageNode", "", 
        "Comma-separated list of vmstorage addresses")
    maxConcurrent    = flag.Int("maxConcurrent", 8, 
        "Max concurrent requests")
    searchTimeout    = flag.Duration("searchTimeout", 60*time.Second, 
        "Query timeout")
}

源码视角:三种模式的启动参数对比

lib/flag.go 和各组件的 main.go 分析启动参数差异:

Single-Node(app/victoria-metrics/main.go

  • -storageDataPath:数据存储路径
  • -httpListenAddr=:8428:HTTP 监听地址
  • -retentionDays:数据保留天数
  • 参数最少,配置最简单

vminsert(app/vminsert/main.go

  • -vmstorageAddrs:vmstorage 节点列表(逗号分隔)
  • -replicationFactor:数据副本数
  • 必须知道 vmstorage 节点地址才能路由

vmstorage(app/vmstorage/main.go

  • 参数与 Single-Node 类似,但监听不同端口(默认 :8400
  • 提供 /internal/insert/internal/search RPC 端点

vmselect(app/vmselect/main.go

  • -storageNodeAddrs:vmstorage 节点列表
  • 监听 :8481 提供查询入口
  • -searchTimeout:查询超时时间

八、实战配置——两种模式的部署模板

思考记忆提示纸上得来终觉浅——本节给出可直接使用的部署模板

  • Single-Node:Docker 单容器部署
  • Cluster:Kubernetes StatefulSet 部署
  • 面试高频提问:生产环境有哪些必须配置的安全参数?

8.1 Single-Node 部署模板

 # docker-compose.yml(Single-Node 部署)
version: '3.8'

services:
  victoria-metrics:
    image: victoriametrics/victoria-metrics:v1.146.0
    container_name: victoria-metrics
    restart: unless-stopped
    ports:
      - "8428:8428"       # HTTP API
      - "2003:2003"       # Graphite
      - "4242:4242"       # OpenTSDB
    volumes:
      - vm-data:/victoria-metrics-data
    command:
      - "-storageDataPath=/victoria-metrics-data"
      - "-retentionPeriod=3month"
      - "-httpListenAddr=0.0.0.0:8428"
      - "-loggerLevel=INFO"
    resources:
      limits:
        memory: 8Gi
        cpus: '4'
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:8428/health"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  vm-data:

8.2 Cluster 模式 Kubernetes 部署模板

 # Kubernetes 部署(Cluster 模式)
# 包含 vminsert、vmstorage、vmselect 三个 StatefulSet

---
# vminsert Deployment(写入入口)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vminsert
  namespace: monitoring
spec:
  replicas: 2
  selector:
    matchLabels:
      app: vminsert
  template:
    metadata:
      labels:
        app: vminsert
    spec:
      containers:
        - name: vminsert
          image: victoriametrics/vminsert:v1.146.0
          args:
            - "-storageNode=vmstorage-0.vmstorage.monitoring.svc:8400,
                vmstorage-1.vmstorage.monitoring.svc:8400,
                vmstorage-2.vmstorage.monitoring.svc:8400"
            - "-httpListenAddr=:8400"
          ports:
            - containerPort: 8400
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "1Gi"
              cpu: "500m"

---
# vmstorage StatefulSet(存储节点)
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: vmstorage
  namespace: monitoring
spec:
  serviceName: vmstorage
  replicas: 3
  selector:
    matchLabels:
      app: vmstorage
  template:
    metadata:
      labels:
        app: vmstorage
    spec:
      containers:
        - name: vmstorage
          image: victoriametrics/vmstorage:v1.146.0
          args:
            - "-storageDataPath=/storage"
            - "-httpListenAddr=:8400"
            - "-retentionPeriod=3month"
          volumeMounts:
            - name: storage
              mountPath: /storage
          resources:
            requests:
              memory: "4Gi"
              cpu: "1000m"
            limits:
              memory: "16Gi"
              cpu: "4000m"
  volumeClaimTemplates:
    - metadata:
        name: storage
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 100Gi

---
# vmselect Deployment(查询入口)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vmselect
  namespace: monitoring
spec:
  replicas: 2
  selector:
    matchLabels:
      app: vmselect
  template:
    metadata:
      labels:
        app: vmselect
    spec:
      containers:
        - name: vmselect
          image: victoriametrics/vmselect:v1.146.0
          args:
            - "-storageNode=vmstorage-0.vmstorage.monitoring.svc:8401,
                vmstorage-1.vmstorage.monitoring.svc:8401,
                vmstorage-2.vmstorage.monitoring.svc:8401"
            - "-httpListenAddr=:8481"
          ports:
            - containerPort: 8481
          resources:
            requests:
              memory: "1Gi"
              cpu: "500m"
            limits:
              memory: "4Gi"
              cpu: "2000m"

注意

Cluster 模式部署时,vmstorage 节点之间的数据同步需要通过 -replicationFactor 参数配置。建议设置为 2(每个分片复制到 2 个节点),在可用性和存储成本之间取得平衡。

九、FAQ:常见疑问

思考记忆提示FAQ 是全篇的"临考前速背"模块,20 组覆盖全链路

  • Q1-Q5 围绕 Single-Node 特性:容量、配置、限制
  • Q6-Q10 围绕 Cluster 架构:三层职责、分片策略
  • Q11-Q15 围绕多租户:accountID/projectID、隔离机制
  • Q16-Q20 围绕选型和迁移:什么时候选什么、如何迁移

Q1. Single-Node 模式下最大能存储多少数据?

理论上只受磁盘空间限制,实际建议控制在 1000 万 series 以内。VictoriaMetrics 的压缩率很高(通常 10x-50x),100GB 原始数据压缩后可能只有 5-10GB。但真正的瓶颈是内存:TSIDCache、blockCache 等缓存需要占用大量内存。建议使用 memory.Allowed 参数限制 VM 可用内存,并监控 vm_cache_entries_*/size_bytes 指标。

Q2. Single-Node 和 Cluster 模式的 HTTP API 兼容吗?

查询 API 完全兼容,写入 API 在 Cluster 模式下需要额外指定租户路径。PromQL 查询(/api/v1/query、/api/v1/query_range)在两种模式下完全兼容。写入 API 在 Cluster 模式下需要在 URL 中添加 /account/{id}/project/{id} 前缀,例如 /account/1/project/1/api/v1/write

Q3. Cluster 模式下,如果某个 vmstorage 节点宕机会怎样?

一致性哈希机制会自动将请求路由到其他节点,该节点负责的分片数据暂时不可用。如果配置了 -replicationFactor=2,每个分片有 2 份副本,宕机一个节点不影响可用性(另一份副本继续服务)。如果未配置副本,宕机节点的数据在恢复前无法写入或查询。建议生产环境至少设置 -replicationFactor=2

Q4. Single-Node 模式下如何实现多租户?

通过 vmauth 认证代理实现逻辑隔离,所有租户共享同一个数据库。在 Single-Node 模式下,无法实现物理隔离。只能用 vmauth 做 API Key 认证,不同租户通过不同的 Bearer Token 写入,数据通过标签(tenant_id)区分。这是一种"软隔离",无法防止恶意租户通过高基数标签破坏系统。

Q5. Cluster 模式下,vminsert 和 vmselect 可以只部署一个吗?

可以,但会增加单点故障风险。理论上可以用 vminsert 同时作为查询入口(直接指向 vmstorage),但这样所有查询都会经过同一个 vminsert 节点,成为瓶颈。标准做法是分离部署:vminsert 专注写入,vmselect 专注查询,各自独立扩展。

Q6. Cluster 模式下,查询延迟为什么比 Single-Node 高?

因为查询需要经过 vmselect → vmstorage 两跳,以及结果归并开销。Single-Node 的查询是:HTTP → Storage.Search() → 返回结果(一跳)。Cluster 的查询是:HTTP → vmselect → 并行查询多个 vmstorage → 归并结果(两跳 + 归并)。网络延迟和归并计算都会增加 P99 延迟。如果 vmstorage 节点之间网络延迟高(如跨机房部署),影响会更明显。

Q7. 什么情况下应该从 Single-Node 迁移到 Cluster?

当遇到 OOM、写入吞吐量瓶颈、查询超时、或需要高可用时。具体指标:1)频繁遭遇 Too many tsIDs currentStorageSizeBytes > Allowed 错误;2)写入吞吐量无法满足需求(< 50 万 samples/s);3)复杂查询 P99 延迟 > 5s;4)需要多副本高可用。这些信号表明 Single-Node 已接近容量边界。

Q8. Cluster 模式下,如何监控各组件健康状态?

每个组件都暴露 /metrics 端点,Cluster 模式下有专门的集群监控指标。vminsert 监控:vminsert_requests_totalvminsert_request_duration_seconds。vmstorage 监控:vmstorage_rows_queuedvmstorage_parts_being_merged。vmselect 监控:vmselect_search_duration_secondsvmselect_search_results_count。建议在 Grafana 中导入官方 Cluster Dashboard。

Q9. Single-Node 的数据可以迁移到 Cluster 吗?

可以,使用 vmctl 工具进行在线迁移。迁移步骤:1)在 Cluster 模式部署新的 VictoriaMetrics;2)使用 vmctl 将 Single-Node 的数据导出为快照;3)使用 vmrestore 将快照导入 Cluster;4)切换 Prometheus remote_write 指向新集群。官方推荐使用 vmctl prometheus 命令进行增量迁移。

Q10. Cluster 模式下,vmstorage 节点的数量如何规划?

建议至少 3 个节点,配合 replicationFactor=2 实现高可用。节点数量规划原则:1)数据量:每个 vmstorage 节点建议存储不超过 50TB 压缩数据;2)吞吐量:每个节点建议处理不超过 50 万 samples/s;3)副本数:replicationFactor=2 时,需要 2 倍存储空间;4)最小节点:生产环境至少 3 个,测试环境可用 2 个。

Q11. Cluster 模式下,如何确定 accountID 和 projectID?

accountID 和 projectID 由应用自行定义,VM 只做路由隔离。常见做法:accountID = 组织/租户 ID(从 SSO/LDAP 获取),projectID = 产品线/环境 ID(dev/staging/prod)。例如:accountID=1001, projectID=1 表示"租户 1001 的生产环境"。这两个 ID 是无符号 32 位整数,范围 0-4294967295。

Q12. Cluster 模式支持账号配额限制吗?

Enterprise 版本支持账号级存储配额,开源版本需要外部配额控制。Enterprise 版本可以通过 -limits.numTenant 参数限制租户数量,以及通过 VMSingleTenantQuota 资源设置存储上限。开源版本没有内置配额,需要通过 Prometheus 侧(如 metric_relabel_configs)或 API 网关(vmauth)限制写入速率。

Q13. 两种模式下的数据保留策略(retention)有什么区别?

Single-Node 在启动参数指定,Cluster 模式在每个 vmstorage 节点指定。Single-Node:-retentionPeriod=3month(全局)。Cluster 模式:每个 vmstorage 节点独立配置 -retentionPeriod,建议所有节点保持一致。如果不一致,可能导致某些分区数据已删除但查询仍在请求这些分区。

Q14. Cluster 模式下,如何实现查询路由到特定租户?

通过 vmauth 或 vmgateway 在查询请求前添加租户 Header/路径。vmauth 配置示例:在 vmauth 中配置多个后端,每个后端对应不同的 accountID/projectID。请求到达 vmauth 时,根据 API Key 选择对应的后端,后端在转发请求时自动添加租户路径。

Q15. Single-Node 模式下可以模拟 Cluster 的多租户路由吗?

无法完全模拟,但可以用 relabel 和标签实现逻辑隔离。一种实践是:在 Prometheus 端使用 metric_relabel_configs 为每个租户的数据添加不同的 tenant_id 标签,然后通过 vmauth 或 Grafana 的租户筛选实现逻辑隔离。但这只是"软隔离",无法防止恶意租户写入高基数标签。

Q16. 什么场景下必须使用 Cluster 模式?

数据量 > 1000 万 series、需要高可用、需要严格多租户隔离时。具体场景:1)多租户 SaaS 服务,每个租户需要独立存储隔离;2)超过 1000 万 series 的超大规模部署;3)需要 99.9%+ 可用性的生产系统;4)需要跨机房容灾的部署。

Q17. Cluster 模式的额外运维开销有哪些?

需要管理多个进程/容器、配置负载均衡、实现健康检查、处理节点扩缩容。具体开销:1)部署复杂度增加 3 倍(需要部署 3 种组件);2)监控维度增加(每个组件都需要单独监控);3)故障排查更复杂(需要定位是 vminsert/vmselect/vmstorage 哪一层的问题);4)升级时需要滚动升级多个组件。

Q18. Cluster 模式下,vminsert 能否直接查询 vmstorage?

技术上可以,但不推荐,会导致职责混乱。vminsert 的职责是写入,如果同时做查询,会导致资源竞争(写入请求和查询请求争抢 CPU/内存/网络)。标准架构是:写入走 vminsert,查询走 vmselect,各自独立扩展。

Q19. 如何估算 Cluster 模式的存储容量?

公式:存储容量 = 写入速率 × 保留时间 × 压缩率 × 副本数。例如:每秒写入 10 万 samples,保留 90 天,压缩率 10x,副本书 2,则存储容量 = 100000 × 86400 × 90 × 10 ÷ 2 ÷ 1e12 ≈ 38.9 TB。建议预留 30% 余量,实际需要约 50TB。

Q20. Single-Node 和 Cluster 模式可以混合使用吗?

可以,用 vmgateway 作为统一入口,后端混合部署。一种架构是:轻量级数据走 Single-Node(低延迟),重量级数据走 Cluster(高吞吐)。vmgateway 根据请求特征(租户 ID、数据量级别)动态路由到不同的后端。这是架构演进的中间态,随着业务增长,最终会全部迁移到 Cluster。

全篇必记总纲

VictoriaMetrics 的 Single-Node 和 Cluster 模式是同一套存储引擎的两种部署形态:Single-Node 将所有组件集成在单进程内,适合小规模高效部署;Cluster 模式将写入(vminsert)、存储(vmstorage)、查询(vmselect)拆分为独立进程,通过一致性哈希实现数据分片和水平扩展,适合大规模高可用场景。多租户隔离通过 accountID/projectID 两级体系实现,Cluster 模式原生支持,Single-Node 模式通过外部代理模拟。选型的核心原则是:小规模选 Single-Node 省心省力,大规模选 Cluster 可扩展性强

十、Roadmap:后续预告

本篇覆盖了 Single-Node 和 Cluster 模式的核心架构差异,但还有几条主线尚未展开:

  • #03 架构演进:从 TSDB 到 MergeSet 的设计取舍——理解 MergeSet 相比 LSM Tree 的优势
  • #04 整体数据流:一条监控数据的完整生命周期——从 HTTP 请求到 Part 文件的完整链路
  • #16 协议接入层:12 种数据协议的统一入口——Remote Write、InfluxDB、OpenTelemetry 如何被解析
  • #20 写入核心链路:Storage.Add() 的三段式处理——数据是如何被写入存储的
  • #201 Cluster 架构:vminsert/vmstorage/vmselect 分布式协作——Cluster 模式的完整源码分析

本文参考与源码链接:
  • app/victoria-metrics/main.go · Single-Node 入口
  • app/vminsert/ · Cluster 写入入口
  • app/vmstorage/ · Cluster 存储节点
  • app/vmselect/ · Cluster 查询节点
  • lib/storage/ · 存储核心层
  • lib/consistenthash/ · 一致性哈希路由
  • VictoriaMetrics Cluster 官方文档