thanos-query、thanos-side部分源码阅读

查询流程总结:

(本文章仅设计thanos-query查sidecar,而sidecar直接查prometheus而不是开启pushgateway)

流程就是thanos-query通过grpc从sidecar端口拉取数据,然后sidecar收到请求以后就会对请求进行处理后再构造一个url去prometheus查询数据,然后把结果返回给thanos-query,thanos-query再去重。
1:前端js检查
	当你在thanos-query中输入一条查询语句的时候,前端js会对它先做一个简单的检查,如果没有通过检查,那么就直接报错,就不会进入后面的流程了,比如只有一个数据源z,对应的external_label集合为{prometheus_replica="a"},但是我们输入的promql查询语句为:kube_pod_owner{prometheus_replica="b"},js检查发现排除了唯一的数据源z以后,没有任何数据源的exterl_label值为b,所以js此处就报错返回了。


2:thanos-query
	1:获取要查询的数据源。一个数据源就是由--store启动参数指定的一个数据源,首先获取所有数据源,然后判断这个数据源是否可能存在我们所需要的数据,如果不需要,那么查询的时候就会过跳过该数据源。比如一个数据源x的external_label集合为{prometheus_replica="a"},但是我们的查询语句为kube_pod_owner{prometheus_replica="b"},所以prometheus_replica字段就会不匹配,所以本次查询thanos-query就不会去请求数据源x
	2:查询。通过grpc,从所有可能存在所需数据的数据源拉取数据,然后再汇总
	3:去重。因为sidecar返回的数据中不会包含由--query.replica-labels指定的标签,所以就可能多个数据源拉取到部分相同的数据,从而导致同一套lable对应多条数据,所以要去重
	4:返回响应给用户。(略)

3:thanos-sidecar
	1:检查请求。对thanos-query发过来的请求进行简单检查,如果不满足要求就会返回失败。对于一个promql:{prometheus_replica="xxx"},thanos-sidecar会首先剔除由--query.replica-labels指定的标签,即会删除prometheus_replica="xxx",然后这个promeql没有其他查询条件了,所以thanos-sidecar此处就报一个错,就不继续往下走了,直接返回失败给thanos-query
	2:查询。根据请求中的查询条件构造url,直接去请求prometheus。查询路径为/api/v1/read,prometheus可以根据查询路径来判断是否需要导出external_labels,普通查询使用的是/api/v1/query,此时是不会导出external_labels
	3:返回响应给thanos-query。(略)

thanos-query:

//thanos-query启动流程
main:
  registerQuery //注册query,并添加启动参数选项。这里是注册,并不是启动,根据启动参数决定是否启动
    runQuery //创建query
      dns.NewProvider //解析启动参数里的--store
	  prepareEndpointSet //获取所有抓取目标,即--store指定的所有目标,查询的时候就是根据需要去查对应的endpoint子集
      ui.NewQueryUI  //注册ui界面,pkg\ui\static\react\index.html ,里面引用了main.js,main.js里实现在前端对promeql语句进行检查的功能
	  v1.NewQueryAPI 
      v1.Register //注册query相关的所有url,比如/api/v1/query
		route.Router.Get("/query",v1.QueryApi.query) //注册query对应的处理函数,qapi.query这是我们后面要解析的
      http.Server.ListenAndServe //这是thanos自带的httpserver


//thaons-query v1.QueryApi.query
v1.QueryApi.query
  v1.QueryApi.parseXxx //parse系列函数解析请求
  query.NewQueryableCreator //返回一个实现了promql.Queryable接口的对象的函数,该对象里对查询进行了配置,比如配置了replica-labels、是否dedup等,
						    //这个接口是prometheus对外暴露的,可以通过实现这个接口直接在prometheus的存储层面进行定制化的查询
							//dedup=true:从查询中去掉replica-labels,也就是说如果有两条指标,除了replica-labels外其他标签都一样,
							//如果开启dedup,那么就会把他们当做两条重复的指标,也就是说只会返回一条指标
							//如果关闭dedup,那么就会保留所有标签,也就是说会把上述来两条指标看做不同的指标
							//localhost:9090/api/v1/query?query=kube_pod_owner{prometheus_replica!=""}&dedup=false,可以直接通过url参数动态设置
						    //这个QueryableCreator是prometheus要求低,我们可以自定义一个配置对象,然后实现prometheus指定接口,然后prometheus调用我们这个对象的时候
							//会调用我们自己实现的方法,从而实现自定义行为而无需改动prometheus框架,类似于kube-state-metrics源码里面我们自己实现一个store对象,
							//然后复用k8s的reflector,这样k8s的reflector就会调用我们的store对象从而调用我们自己的add方法,从而实现自定义行为
  promql.Engine.NewInstantQuery //这是prometheus包里面的,根据查询配置,新建一个查询对象
  promql.query.Exec             //这是prometheus包里面的,执行查询,然后获取指标,注意,这里是去查sidecar,不是prometheus,只是复用prometheus的这个包
	promql.Engine.exec
      promql.Engine.execEvalStmt //这里没有执行查询
		promql.Queryable.Querier //这个Queryable对象就是上面query.NewQueryableCreator函数创建的对象
		  query.queryable.Querier //这个是thanos包下面的	 
            query.newQuerier //返回一个query.querier对象
      promql.Engine.populateSeries //查询
		query.querier.Select //thanos下querier对象实现了prometheus中的queryable接口
  		  go func query.querier.selectFn{ //异步执行查询
			  query.aggrsFromFunc //获取聚合函数
			  {   //根据是否开启dedup来设置查询的时候要不要用without去掉replica-label
			    req=&storepb.SeriesRequest{xxx}
				req.WithoutReplicaLabels = q.replicaLabels
			  }
			  store.ProxyStore.Series //通过grpc去thanos-sidecar查询结果
			    store.matchesExternalLabels //把查询语句中和--selector-label参数指定的标签相同的查询条件去掉 
			    store.storeMatches //获取所有可能存在所需数据的store对象,一个store对象是一个由--store参数指定的端口
								 //通过判断查询语句中的所有匹配规则中是否有和该store对象的exterlnal标签冲突的规则,如果有,则过滤掉该store,即该store上不可能存在数据
								 //举例:查询promql为:kube_pod_owner{prometheus_replica="a"},store的external_label为prometheus_replica="b",冲突,所以该store对象会被过滤掉
								 //sidecar可以看成是prometheus的一个代理
				  store.labelSetsMatch //执行上面所说的判断规则
			    store.newAsyncRespSet //通过grpc异步请求所有可能存在数据的store对象
				  storepb.storeClient.Series //grpc从store对象查询数据
				    grpc.ClientStream.SendMsg
						---->query from thanos sidecar
	                    <----result from thanos sidecar
				  if !store.Client.SupportsWithoutReplicaLabels()&& len(req.WithoutReplicaLabels) > 0{ 
					/*
	                   设置removelabels,这个removelabels告诉thanos,再下面的堆排序操作中在比较标签序的时候要忽略掉哪些标签
	                   thanos-sidecar会在查询prometheus的时候通过without操作去掉replica-labels,因为一个sidecar只查一个prometheus,sidecar确定了,
					   prometheus也就确定了,即replica-labels也就确定了,即返回的结果中是不包含replica-labels中包含的标签的
					   如果一个store对象不支持without,说明他返回的结果中是含有replica-labels,所以我们需要告诉thaons排序的时候忽略掉这些标签
	                 */
				  }
			      store.newEagerRespSet     //创建一个保存了从一个store对象查到的的所有数据的集合
			    store.NewProxyResponseHeap  //把所有response放到一个heap(最小堆)里,这个heap后面会被用来做k路归并来去重,排序方法是比较他们的所有标签,是比较标签不是比较值
										    //response里数据部分叫seriesSet,seriesSet中每个元素叫series,表示一条数据,每条数据包含两部分,标签部分和数据部分
										    //!!注意,数据部分是一个数组,数组每个元素叫做chunk,即一个series包含多个具有相同标签的chunk
										    //这个chunk和和thanos-compaction组件以及thanos降采样相关,chunk这一块还有点模糊,这一块就跳过了,反正大概意思就是读时合并,给series排序
                                            //还有,prometheus导出的数据本身就是chunk,然后thanos-sidecar再把prometheus导出的chunk包装秤自己的AggrChunk
				  heap.Init                
			    store.NewDedupResponseHeap   //创建一个dedup堆,并同时获取第一个元素(即标签序最小的堆的第一个元素),这里的每个元素指series
										     //这个dedup堆是对上面heap的一个包装,不过这个新的dedup堆的方法会对series做去重处理
			    store.dedupResponseHeap.Next //判断堆内是否还有下一个元素,并同时调整读取指针。如果有下一个元素,就会把指针调整到下一个元素的位置
										     //这个全新的数据是指该元素的标签序和上一条指标的标签序不相同,凡是和上一个元素的标签序相同的,我们都算作重复数据,都需要跳过
										     //这里本质就是一个读时合并的操作
			    store.dedupResponseHeap.At   //直接取dedupResponseHeap的最小的resp的第一个series。因为我们是在next函数里完成调整堆的操作
			    query.seriesServer.Send      //每次一个series,然后把这个数据保存到内部的数组中,这个server对象是前面创建的querier对象的内部对象,也就是说querier对象是查询源码的一个重要对象
				dedup.NewSeriesSet           //返回一个迭代器,可以迭代所有chunks
											 //上面的操作是给series去重和排序,我们这里就是把排好序的series的数据部分展开,即把排序序的series的chunk都按顺序收集起来
										     //即原先是数组seriesA{chunk1,chunk2}、seriesB{chunk1,chunk2}、seriesC{chunk1,chunk2}
											 //展开后就变成了seriesA.chunk1、seriesA.chunk2、seriesB.chunk1、seriesB.chunk2、seriesC.chunk1、seriesC.chunk2
											 //一样,thanos-compaction这一块不太清楚,所以这一块代码大致逻辑就是上面说的展开series的chunk
                                             //dedupserieseSet内放了一个promeSeriesSet对象,然后promeSeriesSet对象里又放了一个overlapSplitSet对象,
											 //最终会把所有chunk按顺序放到这个overlapSplitSet对象的replicas数组中
				promise<-set                 //异步执行完毕,通知等待线程
  		  }
          <-promise
      promql.evaluator.Eval                  //生成查询结果。反序列化所有chunk,就是前面都是序列化以后的二进制数据块,即chunk,这里就是解析chunk并组装成最终的result对象
        promql.evaluator.eval                //执行解析操作
		  promql.checkAndExpandSeriesSet     //获取一个chunk
            promql.expandSeriesSet           //获取一个series
              query.lazySeriesSet.Next       //这里query又回到thanos包了,即thanos的lazySeriesSet实现了prometheus里面的某个接口,所以prometheus sdk就会调用我们自己对象的制定方法
                dedup.dedupSeriesSet.Next
                  dedup.dedupSeriesSet.next
                    query.promSeriesSet.At
                      dedup.overlapSplitSet.At   //返回一个chunk,即按顺序从dedupserieseSet.promeSeriesSet.overlapSplitSet对象的replicas数组中返回chunk
		  ...上面是获取一个chunk,这里是把chunk解析成对应的指标对象,逻辑太长,故直接省略了反序列化代码注释...

thanos-sidecar

//略去前面一些函数,直接进入最核心的
store.PrometheusStore.Series
  store.PrometheusStore.externalLabelsFn //获取所有external_labels
  store.matchesExternalLabels     //把查询表达式中所有在external_labels中的查询条件去掉,
                                  //因为prometheus保存的指标中是没有external_labels中的标签的,如果经过处理后查询条件为空,则直接返回报错
  store.startPromRemoteRead       //直接构造请求查prometehus: POST http://prometheusIp:port/api/v1/read
	http.Client.Do                //请求
	io.ReadAll                    //读取结果
  store.PrometheusStore.handleSampledPrometheusResponse
	store.rmLabels //获取完整标签集。查的时候会把查询条件中的external_labels去掉,但是查完以后我们还要把external_labels中除了query.replica-label指定的标签外的其他标签组装到指标中
                   //query.replica-label中的标签thanos-query会用一个withoutReplicaLabels参数标识
                           
	for{                                 //prometheus的数据是按chunk来组织的,thanos读到的也是chunk,这里把prometheus的chunk包装成thanos的AggrChunk,仅仅是添加了一些额外的信息
		store.hashChunk                  //计算chunk的hash值
		thanosChunk:=&storepb.AggrChunk{ ...prometheus chunk...} //这里把prometheus的chunk包装一下,包装成thanos的AggrChunk
		storepb.NewSeriesResponse       //创建一个响应
        storepb.Store_SeriesServer.Send //返回响应给thanos-query
	}



  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值