[源码阅读]vmselect开了一个 `clusternativeListenAddr` 的端口后,级联的逻辑是怎么样的?
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!
结论

先说结论:
我问了 ChatGPT 如下内容:
VictoriaMetrics 的 vmselect 级联的时候:
vmselect_1 连接到了 vmselect_2 的 clusternativeListenAddr 配置的端口。逻辑是否是:vmselect_2 只是把 vmselect_1 的请求广播到后端的多个 storage 节点上,而不做任何计算?
也就是说:计算总是在靠近请求最近的 vmselect 节点上完成?
ChatGPT 的回答如下:
我帮你梳理一下 VictoriaMetrics vmselect 级联 的工作逻辑:
⸻
1. 基本架构
• vmselect:查询层,无状态,负责接收查询请求、向后端发 RPC、聚合结果、返回给用户。
• vmstorage:存储层,负责保存 time series 的数据点,支持高效扫描与压缩。
当你配置:
vmselect_1 -> vmselect_2 (clusternativeListenAddr) -> vmstorage
其实就是让 vmselect_1 把 vmselect_2 当作 下游的 storage 节点 来看待。
⸻
2. vmselect 之间的逻辑
• vmselect_1 收到查询 → 它会像对待 vmstorage 一样,把请求发给 vmselect_2。
• vmselect_2 的行为:
• 并不会自己去做完整的 query 执行,而是把请求再转发/广播给它所连接的 vmstorage 节点;
• 它会把结果流式地返回给 vmselect_1(相当于一个“代理/转发”角色)。
👉 所以 vmselect_2 并不会合并计算,只是做 request fan-out 和 response relay。
⸻
3. 计算发生在哪里?
• 计算(parse query、执行算子、聚合、limit、group by、排序等)只在最靠近用户的 vmselect 节点上进行。
• 下游的 vmselect(包括 vmstorage)主要是提供原始数据点,或者做一些最小化的过滤(例如根据时间范围扫描数据块)。
这意味着:
• vmselect_2 不做 heavy compute,它的 CPU 压力小;
• vmselect_1 才是 query aggregator,它要把下游所有返回的数据合并计算。
⸻
4. 为什么要这样设计?
• 简化:保证计算逻辑单点(最靠近用户的 vmselect),不需要考虑跨层级的部分聚合逻辑。
• 灵活:下游 vmselect 可以当作一个 cluster proxy 使用,方便做拓扑扩展。
• 性能:减少重复计算的开销,避免多级 vmselect 各自聚合导致结果不一致。
⸻
✅ 结论
是的,你的推测正确:
• vmselect_2 只是负责转发请求给它的 vmstorage 集群,不做聚合计算;
• 最终的计算总是在最上层、最接近请求的 vmselect 上完成。
烟雾弹
这里有个疑惑的信息是:vmselect 中还有另一个参数 -selectNode= 用于指向下游的 vmselect 的 http 端口。
这个参数只有一个用途: **当上游发现数据有删除逻辑时,通过广播的方式通知所有下游节点,调用接口:/internal/resetRollupResultCache 来删除缓存,避免缓存中的脏数据影响查询结果 **
源码阅读
| 文件 | 函数 | 调用代码 | 备注 |
|---|---|---|---|
| app/vmselect/main.go | func main() | ||
| clusternative.NewVMSelectServer(*clusternativeListenAddr) | clusternativeListenAddr 参数不为空时,创建级联的 storage 协议服务 |
||
| app/vmselect/clusternative/vmselect.go | func NewVMSelectServer(addr string) | ||
| api := &vmstorageAPI{} | 用一个实现了公共接口的类,来处理具体的收到数据包后的逻辑 | ||
| func (api *vmstorageAPI) InitSearch | 收到查询请求后的处理函数 | ||
| newBlockIterator(qt, denyPartialResponse, sq, dl) | 处理级联的逻辑 | ||
| func newBlockIterator | |||
| go func() | 由一个独立的协程来处理,这个函数是关键位置 |
// VictoriaMetrics/app/vmselect/clusternative/vmselect.go
func newBlockIterator(qt *querytracer.Tracer, denyPartialResponse bool, sq *storage.SearchQuery, deadline searchutil.Deadline) *blockIterator {
var bi blockIterator
bi.workCh = make(chan workItem, 16)
bi.wg.Add(1)
go func() { // 直接向另一个服务发起请求
_, err := netstorage.ProcessBlocks(qt, denyPartialResponse, sq, func(mb *storage.MetricBlock, _ uint) error {
wi := workItem{
mb: mb,
doneCh: make(chan struct{}),
}
bi.workCh <- wi // 没有计算逻辑,对结果只做透传
<-wi.doneCh
return nil
}, deadline)
close(bi.workCh)
bi.err = err
bi.wg.Done()
}()
return &bi
}

浙公网安备 33010602011771号