前言笔者在上一篇博客中详细阐述了Prometheus数据的插入过程。但是我们最常处理的事情是数据查询。Prometheus提供了强大的Promql来满足我们不断变化的查询需求。在这篇文章中,作者以一个简单的Promql为例,描述了Prometheus查询的过程。Promql一个Promql表达式可以计算为以下四种类型:瞬时向量(InstantVector)——一组具有相同时间戳的时间序列(取自不同的时间序列,比如不同机器的CPUidle同时存在)区间向量(Rangevector)-一个时间序列标量(Scalar)-一个浮点数据值字符串(String)-一个简单的字符串我们也可以在Promql中使用集合表达式,例如svm/avg,但只在InstantVector上使用。为了解释Prometheus的聚合计算以及篇幅原因,作者在本文中只详细分析了即时向量(InstantVector)的执行过程。即时向量(InstantVector)如前所述,即时向量是一组具有相同时间戳的时间序列。但是在实际过程中,我们不可能采样到不同Endpoints的时间完全一样。因此,Prometheus取最接近指定时间戳的数据(Sample)。如下图所示:当然,如果距离当前时间戳1小时的数据,从直观上看,它肯定不包含在我们返回的结果中。所以Prometheus通过一个指定的时间窗口(由启动参数query.lookback-delta指定,默认5分钟)来过滤数据。我们来分析一个简单的Promql。解释完InstantVector的概念,我们就可以开始分析了。直接使用具有聚合函数的Promql。SUMBY(group)(http_requests{job="api-server",group="production"})首先,对于这样一个有语法结构的句子,要解析并构建成AST树。调用promql.ParseExpr由于Promql比较简单,Prometheus直接使用LL语法分析。这里直接给出上面Promql的AST树结构。Prometheus遍历语法树的过程是通过访问者模式。具体代码为:ast.govistor设计模式funcWalk(vVisitor,nodeNode,path[]Node)error{varerrerrorifv,err=v.Visit(node,path);v==nil||err!=nil{returnerr}path=append(path,node)for_,e:=rangeChildren(node){iferr:=Walk(v,e,path);err!=nil{returnerr}}_,err=v.Visit(nil,nil)returnerr}func(finspector)Visit(nodeNode,path[]Node)(Visitor,error){iferr:=f(node,path);err!=nil{returnnil,err}returnf,nil}中使用了非常方便的功能函数golang直接通过评估函数inspector在不同情况下进行评估。typeinspectorfunc(Node,[]Node)错误评估过程具体评估过程核心函数为:func(ng*Engine)execEvalStmt(ctxcontext.Context,query*query,s*EvalStmt)(Value,storage.Warnings,error){......querier,warnings,err:=ng.populateSeries(ctxPrepare,query.queryable,s)//这里获取对应序列的数据......val,err:=evaluator.Eval(s.Expr)//此处聚合计算...}populateSeries首先通过populateSeries的计算计算出VectorSelectorNode对应的series(时间序列)。这里给出了评估函数func(nodeNode,path[]Node)error{......querier,err:=q.Querier(ctx,timestamp.FromTime(mint),timestamp.FromTime(s.End))直接)...case*VectorSelector:....set,wrn,err=querier.Select(params,n.LabelMatchers...)...n.unexpandedSeriesSet=set......case*MatrixSelector:...}returnil可以看到这个求值函数,它只对VectorSelector/MatrixSelector进行操作。对于我们的Promql来说,只对叶节点VectorSelector有效。select获取对应数据的核心功能在queryer.Select中。我们先看看qurier是如何获取的。querier,err:=q.Querier(ctx,timestamp.FromTime(mint),timestamp.FromTime(s.End))根据时间戳范围生成querier,最重要的which就是计算这个时间范围内有哪些块,附加到查询器上。详见函数func(db*DB)Querier(mint,maxtint64)(Querier,error){for_,b:=rangedb.blocks{...//遍历块选择块}//ifmaxt>head.mint(也就是内存中的block),然后也加到querier里面。ifmaxt>=db.head.MinTime(){blocks=append(blocks,&rangeHead{head:db.head,mint:mint,maxt:maxt,})}......}知道数据在哪些块,我们就可以开始计算VectorSelector的数据了。//labelMatchers{job:api-server}{__name__:http_requests}{group:production}querier.Select(params,n.LabelMatchers...)有了matchers,我们就可以很容易的通过倒排索引系列得到对应的。为了篇幅,我们假设数据在headBlock中(也就是在内存中)。那么我们的反转计算如下图所示:这样我们的VectorSelector节点就已经有了最终的数据存储地址信息,比如图中的memSeriesrefId=3和4。如果想了解数据在磁盘中的寻址,可以参考笔者之前的博客<
