[源码阅读]vmselect开了一个 `clusternativeListenAddr` 的端口后,级联的逻辑是怎么样的?

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


结论

vmselect 级联

先说结论:
我问了 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
}

posted on 2025-09-26 11:00  ahfuzhang  阅读(29)  评论(0)    收藏  举报