


























当你打开 VictoriaMetrics 的源码目录时,是否曾被数百个 Go 文件所困惑?每个目录承载什么职责?组件之间如何协作?数据从写入到查询的完整链路是什么?理解模块依赖关系是理解整个系统架构的关键。
读完本篇,你应该能回答:VictoriaMetrics 的源码目录结构是如何组织的?各模块之间的依赖关系是什么?从 import 语句能看出哪些设计模式?为什么某些模块不能相互依赖?
VictoriaMetrics 模块依赖 import 架构设计 Go 工程 目录结构 v1.146.0
学习重点提示 — 建议先通读全文,再重点回顾标注内容
重点掌握(必须)
- app/ 目录结构:三个主程序入口(app/vminsert/、app/vmselect/、app/vmstorage/)
- lib/ 目录结构:核心库(lib/storage/、lib/mergeset/、lib/encoding/)
- 依赖方向:高层依赖低层,应用依赖核心库
- 循环依赖检测:为什么某些依赖是禁止的
次重点(了解即可)
- vendor 目录的作用
- 第三方依赖的管理方式
- 模块内聚性原则
文章目录
思考记忆提示 — 理解模块依赖是理解架构的第一步——从 import 语句可以看出设计的精髓
当你接手一个大型 Go 项目时,首先面对的就是目录结构。VictoriaMetrics 有数百个 Go 文件,分布在几十个目录中。如果不理解这些目录的职责划分和依赖关系,阅读源码时会像在迷宫中行走。
我的理解的意思是说
理解模块依赖可以类比为理解一个公司的组织架构:
部门 = 模块
一个大公司有很多部门:市场部、研发部、财务部、人力资源部。每个部门有自己的职责(单一职责原则)。
汇报关系 = 依赖关系
汇报关系决定了谁向谁汇报:
依赖原则 = 组织原则
公司有很多组织原则:
Go 模块同理:
思考记忆提示 — app/ 和 lib/ 的划分是 VM 最核心的架构决策——理解这个划分就理解了整体架构
VictoriaMetrics 的顶层目录非常简洁,主要分为两大部分:
VictoriaMetrics 顶层目录结构:
victoria-metrics/
│
├── app/ # 应用层(Application)
│ ├── vminsert/ # 写入接入服务(可执行程序)
│ ├── vmselect/ # 查询执行服务(可执行程序)
│ ├── vmstorage/ # 数据存储服务(可执行程序)
│ ├── vmagent/ # 抓取代理(可执行程序)
│ ├── vmalert/ # 告警引擎(可执行程序)
│ ├── vmauth/ # 认证代理(可执行程序)
│ ├── vmbackup/ # 备份工具(可执行程序)
│ ├── vmrestore/ # 恢复工具(可执行程序)
│ ├── vmctl/ # 数据迁移工具(可执行程序)
│ ├── vmgateway/ # API 网关(可执行程序)
│ └── vmui/ # 前端 UI(可执行程序)
│
├── lib/ # 核心库(Library)
│ ├── storage/ # 存储引擎核心
│ ├── mergeset/ # MergeSet 存储引擎
│ ├── encoding/ # 压缩编码
│ ├── prompb/ # Prometheus 协议缓冲区
│ ├── protoparser/ # 协议解析器
│ ├── promql/ # PromQL 执行引擎
│ ├── promscrape/ # Prometheus 抓取
│ ├── promrelabel/ # Prometheus Relabel
│ ├── fs/ # 文件系统工具
│ ├── memory/ # 内存管理
│ └── ...
│
├── vendor/ # 第三方依赖
├── go.mod # Go 模块定义
├── go.sum # 依赖校验
└── Makefile # 构建脚本
设计原则:
- app/ 包含可执行程序的 main 函数
- lib/ 包含可复用的核心逻辑
- app/ 依赖 lib/,但 lib/ 不能依赖 app/
- lib/ 内部可以相互依赖,但必须保持单向
设计精髓
app/lib 划分的核心价值在于关注点分离:
这种划分是 Go 项目的最佳实践,也是 VictoriaMetrics 能够独立演进各个组件的关键。
思考记忆提示 — app/ 中的三个主程序(vminsert/vmselect/vmstorage)是 Cluster 模式的核心
vminsert 是 VictoriaMetrics Cluster 模式的写入入口,负责接收来自 Prometheus、vmagent 等客户端的写入请求。
// app/vminsert/main.go(vminsert 主入口)
package main
import (
// HTTP 服务器和路由
"net/http"
// 核心存储接口
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
// 网络存储客户端(用于转发请求到 vmstorage)
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netstorage"
// 协议解析器(支持多种写入协议)
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
)
// vminsert 的职责:
// 1. 接收 HTTP 请求(支持 Prometheus remote_write 协议)
// 2. 解析 tenant 信息(从 URL 路径中提取 accountID:projectID)
// 3. 路由请求到对应的 vmstorage 节点
// 4. 聚合多个 vmstorage 的响应
func main() {
// 注册 HTTP 路由
http.HandleFunc("/insert/", handleInsert)
// 启动 HTTP 服务器
http.ListenAndServe(":8480", nil)
}
func handleInsert(w http.ResponseWriter, r *http.Request) {
// 1. 解析 tenant
tenant := parseTenant(r.URL.Path)
// 2. 路由到 vmstorage
storageNode := routeToStorage(tenant)
// 3. 转发请求
netstorage.Write(r.Body, storageNode)
}
// 依赖关系分析:
// vminsert → lib/storage (接口定义)
// vminsert → lib/netstorage (网络通信)
// vminsert → lib/protoparser (协议解析)
// vminsert → lib/prompb (协议定义)
vmselect 是 VictoriaMetrics Cluster 模式的查询入口,负责接收来自 Grafana、API 客户端的查询请求。
// app/vmselect/main.go(vmselect 主入口)
package main
import (
// HTTP 服务器
"net/http"
// PromQL 执行引擎
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
// 网络存储客户端
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netstorage"
// 核心存储接口
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
)
// vmselect 的职责:
// 1. 接收 HTTP 查询请求(PromQL 表达式)
// 2. 解析 tenant 信息
// 3. 路由查询到对应的 vmstorage 节点
// 4. 聚合多个 vmstorage 的结果
// 5. 执行 PromQL(部分计算可以在 vmselect 完成)
func main() {
// 注册 HTTP 路由
http.HandleFunc("/select/", handleSelect)
// 启动 HTTP 服务器
http.ListenAndServe(":8481", nil)
}
func handleSelect(w http.ResponseWriter, r *http.Request) {
// 1. 解析 tenant
tenant := parseTenant(r.URL.Path)
// 2. 解析 PromQL
query := r.URL.Query().Get("query")
expr, err := promql.Parse(query)
if err != nil {
http.Error(w, err.Error(), 400)
return
}
// 3. 路由到 vmstorage
storageNodes := getStorageNodes(tenant)
// 4. 执行查询
results := netstorage.Select(expr, storageNodes)
// 5. 聚合结果
aggregated := promql.Aggregate(results)
// 6. 返回结果
writeJSON(w, aggregated)
}
// 依赖关系分析:
// vmselect → lib/promql (PromQL 执行)
// vmselect → lib/netstorage (网络通信)
// vmselect → lib/storage (接口定义)
vmstorage 是 VictoriaMetrics Cluster 模式的数据存储节点,负责实际的数据读写。
// app/vmstorage/main.go(vmstorage 主入口)
package main
import (
// 核心存储实现
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
// HTTP 服务器
"net/http"
)
// vmstorage 的职责:
// 1. 管理本地数据存储(Partition、Part、indexDB)
// 2. 处理来自 vminsert 的写入请求
// 3. 处理来自 vmselect 的查询请求
// 4. 维护数据索引(倒排索引)
// 5. 执行后台合并任务
func main() {
// 初始化存储
s := storage.New()
// 注册 HTTP 路由
http.HandleFunc("/storage/", s.Handle)
// 启动 HTTP 服务器
http.ListenAndServe(":8482", nil)
}
// 依赖关系分析:
// vmstorage → lib/storage (存储实现)
// vmstorage 不依赖其他 app 模块
思考记忆提示 — lib/ 中的核心库是 VM 最重要的部分——理解它们的层次关系是关键
lib/ 目录的模块层次(从底层到顶层):
┌─────────────────────────────────────────────────────────────────────────┐
│ lib/ 模块层次图 │
│ │
│ 第四层:应用层协议 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ promql/ - PromQL 执行引擎 │ │
│ │ graphite/ - Graphite API │ │
│ │ prometheus/ - Prometheus API │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 第三层:协议处理 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ prompb/ - Prometheus 协议缓冲区 │ │
│ │ protoparser/ - 协议解析器(支持 12+ 种协议) │ │
│ │ promscrape/ - Prometheus 抓取(vmagent 使用) │ │
│ │ promrelabel/ - Relabel 规则 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 第二层:存储实现 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ storage/ - 存储引擎核心(分区、索引、缓存) │ │
│ │ mergeset/ - MergeSet 存储引擎(数据块管理) │ │
│ │ netstorage/ - 网络存储(Cluster 模式通信) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 第一层:基础工具 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ encoding/ - 压缩编码(NearestDelta、ZSTD) │ │
│ │ bytesutil/ - 字节处理工具(零拷贝) │ │
│ │ fs/ - 文件系统工具(mmap、fadvise) │ │
│ │ memory/ - 内存管理(Allowed()) │ │
│ │ fastcache/ - 高性能缓存 │ │
│ │ logger/ - 日志 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 依赖原则:上层依赖下层,下层不能依赖上层 │
└─────────────────────────────────────────────────────────────────────────┘
lib/storage 是最核心的模块,定义了存储引擎的核心接口和数据结构。
// lib/storage/storage.go(存储引擎核心)
package storage
// Storage 是 VM 存储引擎的核心接口
// 定义了所有存储操作的标准方法
type Storage struct {
// 分区管理
tables map[string]*Table
// 缓存
tsidCache *workingsetcache.Cache
metricIDCache *workingsetcache.Cache
metricNameCache *workingsetcache.Cache
// 索引数据库
indexDB *IndexDB
// 配置
retentionPeriod uint64
maxSamplesPerSeries uint64
}
// 添加数据点
func (s *Storage) Add(rows []RawRow) error {
// 1. 解析 metric name
// 2. 查找/创建 TSID
// 3. 写入分区
// 4. 更新索引
}
// 查询数据
func (s *Storage) Search(q *SearchQuery) (*SearchResult, error) {
// 1. 解析标签过滤器
// 2. 查询倒排索引
// 3. 扫描数据块
// 4. 归并结果
}
// lib/storage 依赖的模块(按 import 分析):
// lib/storage → lib/encoding (压缩)
// lib/storage → lib/bytesutil (字节处理)
// lib/storage → lib/fs (文件系统)
// lib/storage → lib/memory (内存管理)
// lib/storage → lib/fastcache (缓存)
小贴士 — 模块依赖分析工具
可以使用 Go 的工具分析模块依赖:
go mod graph:查看模块依赖图go list -m all:列出所有依赖模块go mod why <module>:解释为什么依赖某个模块思考记忆提示 — 通过 import 语句可以还原完整的模块依赖图
以下是从 VictoriaMetrics 源码中提取的模块依赖关系:
┌─────────────────────────────────────────────────────────────────────────┐
│ VictoriaMetrics 模块依赖图 │
│ │
│ ┌──────────────┐ │
│ │ app/ │ │
│ │ (3个主程序) │ │
│ └───────┬───────┘ │
│ │ │
│ ┌─────────────────────┼─────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ vminsert │ │ vmselect │ │ vmstorage │ │
│ └───────┬──────┘ └───────┬──────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────┬───────┘ │ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ netstorage │ │ │
│ └───────┬──────┘ │ │
│ │ │ │
│ └─────────┬───────────────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ storage │ │
│ └───────┬──────┘ │
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ mergeset │ │ indexDB │ │ cache │ │
│ └───────┬──────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ └──────────────────┬───────────────────────────────────┐ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ encoding │ │ │
│ └──────────────┘ │ │
│ │ │
│ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ lib/ 基础工具层 │ │ │
│ │ bytesutil │ fs │ memory │ logger │ fastcache │ timerpool │ │ │
│ └──────────────────────────────────────────────────────────────────┘ │ │
│ │ │
└───────────────────────────────────────────────────────────────────────┘ │
│ │
│ 图例: │
│ A → B 表示 A 依赖 B │
│ 数据流向:app → storage → mergeset → encoding │
│ 依赖方向:相反 │
└─────────────────────────────────────────────────────────────────────────┘
从 import 语句可以分析出以下关键依赖关系:
| 模块 | 依赖 | 说明 |
|---|---|---|
| app/vminsert | storage, netstorage, protoparser | 写入入口,依赖存储接口 |
| app/vmselect | storage, netstorage, promql | 查询入口,依赖 PromQL 引擎 |
| app/vmstorage | storage | 存储节点,核心依赖 |
| lib/storage | mergeset, encoding, bytesutil, fs, memory | 存储核心,依赖底层库 |
| lib/mergeset | encoding, bytesutil, fs | 存储实现,依赖编码工具 |
| lib/promql | storage, netstorage | 查询引擎,依赖存储接口 |
| lib/encoding | bytesutil | 最底层之一,只依赖字节工具 |
注意
依赖关系必须是有向无环图(DAG)。如果出现循环依赖(如 A → B → C → A),Go 编译器会报错。可以使用 go mod why -m <module> 检测循环依赖。
思考记忆提示 — 理解依赖原则才能理解架构决策——这些原则不是凭空制定的
VictoriaMetrics 的模块组织遵循以下原则:
┌─────────────────────────────────────────────────────────────────────────┐
│ 模块依赖设计原则 │
│ │
│ 1. 依赖方向:上层依赖下层 │
│ ┌────────┐ │
│ │ app/ │ ← 应用层(依赖 lib/) │
│ └────┬───┘ │
│ │ │
│ ▼ │
│ ┌────────┐ │
│ │ lib/ │ ← 核心库(被 app/ 依赖) │
│ └────────┘ │
│ │
│ 2. 禁止循环依赖 │
│ ┌────────────────────────────────────────┐ │
│ │ A → B → C → A ❌ 编译错误 │ │
│ │ A → B → C → B ❌ 编译错误 │ │
│ └────────────────────────────────────────┘ │
│ │
│ 3. 就近依赖原则 │
│ ┌────────────────────────────────────────┐ │
│ │ A → B → C → D │ │
│ │ ✓ A 直接依赖 D(如果需要 D 的功能) │ │
│ │ ✗ A 绕过 B, C 直接依赖 D(可能破坏封装) │ │
│ └────────────────────────────────────────┘ │
│ │
│ 4. 接口隔离原则 │
│ ┌────────────────────────────────────────┐ │
│ │ 存储接口 vs 存储实现 │ │
│ │ lib/storage 定义接口 │ │
│ │ app/vmstorage 实现接口 │ │
│ │ 这样可以替换存储实现而不影响上层 │ │
│ └────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
设计精髓
良好的模块依赖结构带来以下好处:
VM 的架构是 Go 项目中"干净架构"的典范,很值得学习和借鉴。
让我们通过实际的 import 语句来验证依赖关系:
// app/vminsert/main.go 的 import
import (
// 标准库
"net/http"
"fmt"
// lib/ 核心库
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage" // 存储接口
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netstorage" // 网络存储
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb" // Prometheus 协议
// 第三方库
"github.com/golang/glog" // 日志
)
// 依赖方向:
// vminsert → storage (定义)
// vminsert → netstorage (实现)
// vminsert → prompb (定义)
// 注意:vminsert 不依赖 app/vmselect 或 app/vmstorage
// 这是关键:app 层内部不能相互依赖
思考记忆提示 — FAQ 是全篇的"临考前速背"模块,20 组覆盖全链路
app/ 包含 main 函数和可执行程序的入口,lib/ 包含可复用的逻辑库。app/ 中的每个子目录是一个可执行程序(如 vminsert、vmselect、vmstorage),每个都有自己的 main.go。lib/ 中的每个子目录是一个 Go 包(如 storage、mergeset、encoding),提供特定功能。
为了避免循环依赖和保持关注点分离。lib/ 是底层库,应该独立于应用。如果 lib/ 依赖 app/,那么修改 app/ 就可能影响 lib/,破坏了模块的独立性。
可以,但必须是单向无环的。lib/ 内部的依赖规则是:下层可以依赖更下层,但上层不能依赖上层。例如,storage 可以依赖 mergeset,mergeset 可以依赖 encoding,但 encoding 不能依赖 storage(会形成循环)。
高层定义接口,低层实现接口,依赖方向从低层指向高层。例如,vminsert 定义了"存储接口",vmstorage 实现了这个接口。这样 vminsert 不需要知道具体是哪个 vmstorage 实例在处理数据。
Go 编译器会在构建时自动检测循环依赖,如果发现会报错。错误消息类似于 "import cycle not allowed"。可以通过 go mod why -m <module> 来追踪依赖路径。
使用 go mod graph 查看完整依赖图,或使用 IDE 的依赖分析功能。IDE(如 GoLand、VSCode)通常有"Show Dependencies"功能,可以可视化模块依赖图。
vendor/ 存储所有第三方依赖的副本,用于离线构建和版本锁定。Go 1.6+ 会优先使用 vendor/ 中的依赖,而不是从网络下载。这确保了构建的可重复性。
有 main 函数的就是 app/,没有 main 函数的就是 lib/。这是最简单也最有效的划分原则。vmagent、vmalert、vmauth 等是独立工具,自然放在 app/ 中。
帮助定位问题和理解数据流。当你遇到一个问题时,可以沿着依赖方向向上追踪,找到问题的根源。当你需要修改某个模块时,可以沿着依赖方向向下检查,影响的范围。
app/lib 划分清晰、依赖方向单一、模块职责明确。这种结构是 Go 项目的最佳实践,适合大型项目的组织和维护。
Go 中小写字母开头的标识符是包内私有的,只能在同一个包内访问。这实现了封装性,外部只能通过导出的(首字母大写)标识符访问包的内部实现。
提取公共接口到独立包,让依赖双方都依赖这个接口包。例如,A 和 B 相互依赖,可以创建一个 ABlib 包,让 A 和 B 都依赖 ABlib。
Go modules 是 Go 1.11+ 引入的依赖管理机制,通过 go.mod 和 go.sum 文件管理依赖版本。go.mod 声明模块名和依赖,go.sum 记录依赖的校验和。
module 路径是模块的唯一标识符,用于定位模块和解析 import 路径。VictoriaMetrics 的 module 路径是 github.com/VictoriaMetrics/VictoriaMetrics。
通过 go.mod 中的版本号,如 v1.146.0。Go modules 使用语义化版本(semver),版本号格式为 vMAJOR.MINOR.PATCH。
语义化版本(semver)是一种版本命名规范,格式为 MAJOR.MINOR.PATCH。MAJOR 是不兼容的修改,MINOR 是向后兼容的功能增加,PATCH 是向后兼容的问题修复。
module proxy 是一个 HTTP 服务器,代理 Go 模块的下载和缓存。可以使用私有 proxy 来加速下载和提高构建的可重复性。
replace 指令用于在本地开发时替换依赖的模块路径。例如,replace github.com/foo/bar => ../bar 可以让 go build 使用本地的 bar 模块而不是远程的。
go.sum 记录了每个依赖包的加密校验和,用于验证下载的包是否被篡改。每次添加新依赖时,go mod tidy 会自动更新 go.sum。
使用 go mod tidy 命令,它会自动添加缺失的依赖和删除未使用的依赖。
全篇必记总纲
VictoriaMetrics 的模块依赖结构遵循app/lib 划分 + 单向无环依赖原则:app/ 包含可执行程序的入口,lib/ 包含可复用的核心库;高层(app)依赖低层(lib),低层不能依赖高层;lib 内部依赖方向也是单向的(encoding → mergeset → storage)。这种结构是 Go 项目"干净架构"的典范。
本篇覆盖了 VictoriaMetrics 的模块依赖图,但还有很多细节尚未展开:
本文参考与源码链接:
• app/ · 应用层入口
• lib/ · 核心库
• go.mod · 模块定义
• Go Modules 官方文档
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。