[源码阅读][vmselect] 从promql 到一条曲线,计算过程是怎么样的?

news/2025/9/29 17:43:02/文章来源:https://www.cnblogs.com/ahfuzhang/p/19119114

[源码阅读][vmselect] 从promql 到一条曲线,计算过程是怎么样的?

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

  • cnblogs博客
  • zhihu
  • Github
  • 公众号:一本正经的瞎扯

以最经典的计算 qps 的曲线为例,vmselect 内部是如何计算的?

1 grafana 通过 query_range 接口发起请求

通常会在 grafana 中配置一个 line chart,然后使用以下的 promql 表达式来计算每分钟的请求量:

sum by (path) (increase(http_request_total{job="myApp"}[1m]))

grafana 会向所配置数据源的 vmselect 发送类似的请求:

POST /select/0/prometheus/api/v1/query_range HTTP/1.1
Host: xxx
Content-Type: application/x-www-form-urlencodedstart=${开始时间}&end=${结束时间}&step=15s&query=sum by (path) (increase(http_request_total{job="myApp"}[1m]))
  • queuy_range 的 API 格式请看:https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries

2. vmselect 中的处理流程

2.1 函数调用过程:

TL;DR
可以直接跳到下一节看源码分析

文件 函数 调用代码 说明
app/vmselect/main.go func main() Main 函数
go httpserver.Serve(listenAddrs, requestHandler 启动http 服务
func requestHandler http的callback 函数
return selectHandler(qt, startTime, w, r, p, at) 执行 http://vmselect:8481/select/ 这个路径
func selectHandler 查询的处理函数
prometheus.QueryRangeHandler(qt, startTime, at, w, r) promql 查询的处理函数
/query_range 这条API 的处理代码
app/vmselect/prometheus/
prometheus.go
func QueryRangeHandler
queryRangeHandler(qt, startTime, at, w, query 从http协议中取出参数,执行范围查询
func queryRangeHandler
result, err := promql.Exec(qt, ec, query, false) 组织好 promql.EvalConfig 对象
app/vmselect/promql/exec.go func Exec 执行查询表达式的函数
e, err := parsePromQLWithCache(q) 解析查询表达式
qid := activeQueriesV.Add(ec, q) 记录当前正在查询哪个表达式
rv, err := evalExpr(qt, ec, e) 执行解析后的表达式, metricsql.Expr对象
app/vmselect/promql/eval.go func evalExpr evalExpr会根据 promql的结构嵌套执行,直到叶子节点。
rv, err := evalExprInternal(qt, ec, e)
func evalExprInternal 逐个种类判断,一共八个种类。 是哪种表达式,就执行对应的分支
rv, err := evalAggrFunc(qtChild, ec, ae) 执行聚合表达式。(选择最常见的一种表达式来分析)
func evalAggrFunc
callbacks := getIncrementalAggrFuncCallbacks(ae.Name) 根据表达式中的聚合函数名,找到对应的执行代码。 例如:函数 sum() 对应着一个 golang 的 func
fe, nrf := tryGetArgRollupFuncWithMetricExpr(ae) 如果 sum() 里面还有类似 increase() 这样的 rollup 函数,则执行这一步
args, re, err := evalRollupFuncArgs(qt, ec, fe) 先执行 rollup() 函数里面的表达式
rf, err := nrf(args) 得到表达式的结果后,再执行 rollup() 函数
func evalRollupFuncArgs
ts, err := evalExpr(qt, ec, arg) 嵌套执行表达式,又回到函数 func evalExpr
内层一般都是 metrics 表达式 这里开始展示执行到了叶子节点的情况。
rv, err := evalRollupFunc(qt, ec, "default_rollup", rollupDefault, e, re, nil)
func evalRollupFunc
return evalRollupFuncWithoutAt(qt, ec, funcName, rf, expr, re, iafc)
func evalRollupFuncWithoutAt
rvs, err = evalRollupFuncWithMetricExpr(qt, ecNew, funcName, rf, expr, me, iafc, re.Window)
func evalRollupFuncWithMetricExpr
tss, err := evalRollupFuncNoCache(qt, ec, funcName, rf, expr, me, iafc, window, pointsPerSeries)
func evalRollupFuncNoCache
tfss := searchutil.ToTagFilterss(me.LabelFilterss) 把 metrics 相关的表达式,变成标签过滤的对象
sq = storage.NewMultiTenantSearchQuery(ts, minTimestamp, ec.End, tfss, ec.MaxSeries) 把 [][]storage.TagFilter 构造成 SearchQuery 对象
rss, isPartial, err := netstorage.ProcessSearchQuery(qt, ec.DenyPartialResponse, sq, ec.Deadline) 把请求发到 storage 节点,得到了 Results 对象
evalRollupNoIncrementalAggregate(qt, funcName, keepMetricNames, rss, rcs, preFunc, sharedTimestamps)
func evalRollupNoIncrementalAggregate
rss.RunParallel(qt, ... 当 vmstorage 返回 metricBlock 数据块后,开始并行执行,做各种聚合运算。
app/vmselect/netstorage/
netstorage.go
func (rss *Results) RunParallel
rowsProcessedTotal, err := rss.runParallel(qt, f) 在与核数相匹配的协程中并行执行
func (rss *Results) runParallel
err = tsw.do(&tmpResult.rs, 0) 每个 time series的数据上调用 do 方法
func (tsw *timeseriesWork) do
err := tsw.pts.Unpack(r, rss.tbfs, rss.tr) 把 metricBlock 数据进行反序列化,变成与 data point 数量相等的 []timestamp 和 []values
func (pts *packedTimeseries) Unpack
dedupInterval := storage.GetDedupInterval() 查询时,拉取全局的 dedup 间隔配置
mergeSortBlocks(dst, sbh, dedupInterval) 去重逻辑。去掉重复的 timestamp

2.2 查询过程概述

  • 服务启动流程
    • main() 中启动了http服务
    • 提供 callback 函数来对应到 /select/0/prometheus/api/v1/xxx 下面的查询
    • 最终请求触发时,走到 QueryRangeHandler 中进行处理
  • 查询过程
    • 通过 Exec() 函数来处理查询
    • 使用 parsePromQL() 来解析 promql 表达式,把表达式变成 8 中基本语句的嵌套。8种语句包含:
      • metricsql.MetricExpr: metric 的过滤表达式,主要是 tag 层面的过滤
      • metricsql.RollupExpr: 可以理解为 increase(), rate() 这样的区间聚合函数
      • metricsql.FuncExpr: 执行 metricsQL 内部提供的函数,例如 label_replace() 等
      • metricsql.AggrFuncExpr: 执行聚合函数,例如 max, sum, avg 等
      • metricsql.BinaryOpExpr: 执行布尔表达式的运算,主要有:and / or / unless
      • metricsql.NumberExpr: 数值常量表达式
      • metricsql.StringExpr: 把字符串看成一个独立的时间序列
      • metricsql.DurationExpr: 产生新的 timestamp 的序列
    • 根据 promQL 表达式的结构,在不同的部分嵌套执行 evalExpr(), 直到执行完成整个表达式
  • 调用存储后端:
    • 当 promql 的表达式是 MetricExpr 时,通过 vm-select 与 vm-storage 之间的二进制 rpc 协议来通讯
    • vmselect 向所有的 vmstorage 广播
    • 发送到 vmstorage 上的请求都是要求返回 metricName + timestamp + value 这样形式的请求
    • vmstorage 根据 tag 的过滤表达式,先在索引中找到符合条件的 tsid
    • 再根据 tsid 在数据去寻找每个 tsid 对应的 block,然后直接返回未解码的 block 数据
    • vmselect 会收到来自 vmstorage 的 MetricBlock 结构
    • 最终收到的数据类似这样的结构: map[metricName] -> block list
  • 数据反序列化及其去重
    • 当收到多个 vmstorage 节点返回的数据后,创建 N 个协程(N一般与CPU核数相等)来做反序列化、dedup 和 表达式计算
    • 每条 metricName 下的 block list 会逐个调用 unpack 来变成 []timestamp 和 []values 的数组
    • 所有的 block list 会建立成一个堆,按照 timestamp 来排序
    • 以 dedupInterval 为时间窗口,逐个检查 timestamp。在 dedupInterval 时间窗口内的多条数据,只取一条
  • 表达式计算
    • 重新回到 evalExpr() 函数,做 sum 等表达式的计算
    • 全部计算完成后返回 json 数据给 grafana

3. 总结

  • vmselect / vmstorage 是存算分离的架构
    • vmstorage 内部组织了索引和数据两部分
    • vmstorage 通过 tag 来在索引中过滤,找到后返回整个 block 数据
    • vmselect 把查询广播到所有的 vmstorage,得到 metricBlock 后进行 promql 表达式的计算
  • promQL 被解析为一颗树,通过递归执行。
    • 表达式从树梢执行到树根

最后,为了便于分析 VictoriaMetrics 系列的源码,我又专门建立了一个源码仓库来存放增加了注释的源码:
https://github.com/ahfuzhang/code_comments

Have Fun. 😃

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/922072.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kuboard使用的etcd空间满了如何处理

1.在master节点通过命令进入etcd容器,pod名称替换成自己的[root@master ~]# kubectl get pods -n kuboard NAME READY STATUS RESTARTS AGE kuboard-etcd-6vmkm 1/1 R…

国内信创领域的PostgreSQL技术能力认证

工信部人才交流中心颁发的 PostgreSQL 数据库管理员认证(以下简称 “工信人才 PG 认证”)是国内信创领域的技术能力认证,随着信创战略推进,PostgreSQL 作为开源可控的数据库代表,已成为国产替代的标杆技术。在中央企…

redis-AOF持久化机制

redis-AOF持久化机制AOF,Appedn Only File,指Redis将每一次的写操作都以日志的形式记录到一个AOF文件中的持久化技术。 当需要恢复内存数据时,只这些写操作重新执行一次就可以将之前的内存数据恢复。 AOF配置开启AO…

03-控制台项目创建与结构说明

项目创建 项目名称和存放位置 Main函数

从拆盒到共创:手办盲盒抽赏小程序的多元体验与文化联结 - 实践

从拆盒到共创:手办盲盒抽赏小程序的多元体验与文化联结 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Co…

Nginx技术文档与LNMP架构部署指南 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

海康威视WEB视频监控插件3.3 解决视频画面遮挡 无法隐藏的问题 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

怎么做类似淘宝一样的网站吗wordpress维护服务

vue-cli项目首页加载缓慢想要使用骨架屏效果,经过几天的踩坑,这里学习并记录一下vue项目自动生成骨架屏方法。 添加骨架屏,其优势在于: 写于HTML文件中,独立于Vue框架,节省了JS加载时间JS全局环境创建的执…

YACS2025年9月乙组

YACS2025年9月乙组T1. 数学作业 发现 \(a-b\) 太大会很快超过题目所限的范围,所以 \(a-b\) 值并不大。 然后枚举差值 \(d\),发现 \(\frac{(x+d)!}{x!}\) 关于 \(x\) 单调递增。所以可以二分判断存不存在 \(x\) 满足 …

做网站需要源码网站主机购买

这篇文章主要介绍了oracle导入导出数据的二种方法,利用PL/SQL Developer工具导出和利用cmd的操作命令导出的出方法,大家参考使用吧方法一:利用PL/SQL Developer工具导出:菜单栏---->Tools---->Export Tables,如下…

赋能智慧应急:国标GB28181平台EasyGBS视频技术如何成为气象灾害预警新工具

赋能智慧应急:国标GB28181平台EasyGBS视频技术如何成为气象灾害预警新工具中国地理广阔,人口众多,自然环境复杂,气象灾害频发,是全球气象灾害最严重的国家之一。气象灾害约占自然灾害的70%,种类繁多、分布广泛,…

做视频免费模板下载网站seo网站导航建设技巧

##数据库事务 ###含义 通过一组逻辑操作单元(一组DML——sql语句),将数据从一种状态切换到另外一种状态 ###特点 (ACID) 原子性:要么都执行,要么都回滚 一致性:保证…

NET各个版本新增的特性和语法糖

以下是按C#版本从低到高整理的.NET相关版本特性,补充了发布年份及对应的.NET Core/.NET版本信息,包含特性概念、作用、优势及示例: C# 6.0对应版本:.NET Framework 4.6(2015年)、.NET Core 1.0(2016年) 核心特…

xinference推理embedding等小模型

使用容器方式的xinference管理小模型,带鉴权、带本地模型加载embedding、rerank模型不少,需要一个框架来集中管理,选用了xinference,使用简单。采取容器化部署: 1、镜像下载:原始模型下载慢,采用渡渡鸟,下载 2…

day15-项目上线

今日内容 1 项目上线架构# 1 购买云服务---操作系统--》centos9-上线到公网 # 2 nginx-转发用户的请求--》到uwsgi的django项目# 3 mysql8-后端项目数据存储在mysql中# 4 上传我们后端项目-导入项目依赖:requirements…

Docker入门 - 实践

Docker入门 - 实践2025-09-29 17:19 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-fam…

长沙百度推广公司电话太原seo关键词排名优化

了解哪一种 for 循环或迭代器适合我们的需求,防止我们犯下一些影响应用性能的低级错误。 由 Artem Sapegin 上传至 Unsplash JavaScript 是 Web 开发领域的“常青树”。无论是 JavaScript 框架(如 Node.js、React、Angular、Vue 等)&#x…

个人做美食视频网站wordpress自定义字段插件

本文实例讲述了Python使用matplotlib绘图无法显示中文问题的解决方法。分享给大家供大家参考,具体如下: 在python中,默认情况下是无法显示中文的,如下代码: import matplotlib.pyplot as plt # 定义文本框和箭头格式 d…

自己怎么做企业网站烟台主流网站

前言最近看了某客时间的《Java业务开发常见错误100例》,再结合平时踩的一些代码坑,写写总结,希望对大家有帮助,感谢阅读~1. 六类典型空指针问题包装类型的空指针问题级联调用的空指针问题Equals方法左边的空指针问题ConcurrentHas…

网站内页上海机械网站建设

完整课程请点击以下链接 Go 语言项目开发实战_Go_实战_项目开发_孔令飞_Commit 规范_最佳实践_企业应用代码-极客时间 Go语言中没有传统意义上的类和继承的概念,但可以通过嵌入类型(embedded types)来实现类似的功能。嵌入类型允许一个结构…