[源码阅读][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 / unlessmetricsql.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…
从拆盒到共创:手办盲盒抽赏小程序的多元体验与文化联结 - 实践
从拆盒到共创:手办盲盒抽赏小程序的多元体验与文化联结 - 实践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)来实现类似的功能。嵌入类型允许一个结构…