在前面的章节我们通过阅读Tempo的源码了解了Distributor组件和Ingester组件完成数据的接收和写入存储的过程,Compactor组件对存储的Block数据进行压缩和清理的过程。这一节开始将阅读Tempo的查询Trace过程,将涉及Query Frontend组件和Querier组件。本节将先了解Query Frontend组件中一次请求经过了哪些MiddleWare,以及Query Frontend组件如何使用MiddleWare。
traceByIDHandler
当在grafana界面上发起查询某个trace时,会看到queryFronted组件有一条日志输出:
level=info ts=2023-12-13T12:58:39.786900347Z caller=handler.go:135 tenant=PE-Hawk method=GET traceID=58a56149ccadb246 url="/api/traces/9845b56b9f9aa8cc879eba73d7128eb4?start=1702466900&end=1702474100" duration=9.444251ms response_size=1328 status=200
通过查找全局查找/api/traces/
,定位到traceByIDHandler
函数的定义

通过日志的关键字查找可以定位到ServerHTTP
函数

通过这两个信息倒推traceByIDHandler
的封装和逻辑,发现traceByIDHandler
有多次middleware.Merge
、middleware.Wrap
等中间件函数的处理,而且在处理过程中的对象有着多层级的middleware.Wrap
操作。
在对这块代码多次阅读后,还是无法理解这些middleware的封装,无法确定traceByIDHandler
的执行过程以及各middleware的执行顺序。
Debug代码
无法理解Query Frontend
组件的middleware代码逻辑,那我只有本地对代码进行debug了,并且在相应的中间件处理函数打上日志。这才理出了完整的调用顺序。
打印的调用的顺序和依赖关系如下:
HTTPAuthMiddleware ...
httpGzipMiddleware ...
handler ServeHTTP ...
newDeduper ...
newTraceByIDSharder ...
newHedgedRequest ...
retryWare ...
grpcRoundTripperAdapter ...
Frontend RoundTrip...
Frontend RoundTrip out...
grpcRoundTripperAdapter out...
retryWare out...
newHedgedRequest out...
newTraceByIDSharder out...
newDeduper out...
handler ServeHTTP out ...
httpGzipMiddleware out...
HTTPAuthMiddleware out...
得到MiddleWare的执行顺序后,按照这些middleware的调用顺序,依次解读各层middleware的实现和处理逻辑。这才把Query Frontend
的处理过程理解清楚。
middleware.Wrap
在解读各层middleware的实现之前,需要先了解middleware的两个重要函数middleware.Merge
、middleware.Wrap
。
Interface
定义了一个实现Wrap
方法的接口,这些复杂的中间价都定义了Wrap
方法的结构就自然实现了该接口。
Wrap
方法将调用传入的next handler
函数。
// Interface is the shared contract for all middlesware, and allows middlesware
// to wrap handlers.
type Interface interface {
Wrap(http.Handler) http.Handler
}
// Func is to Interface as http.HandlerFunc is to http.Handler
type Func func(http.Handler) http.Handler
// Wrap implements Interface
func (m Func) Wrap(next http.Handler) http.Handler {
return m(next)
}
假设A、B都实现了Interface,那么如下方法定义:
handler := A.Wrap(B).Wrap(handlerFunc)
http.Handle("/a/b", handlerFunc)
函数的执行顺序为首先调用handlerFunc
,然后依次调用B
的handler函数、A
的handler函数。
middleware.Merge
Merge函数的定义如下:
// Merge produces a middleware that applies multiple middlesware in turn;
// ie Merge(f,g,h).Wrap(handler) == f.Wrap(g.Wrap(h.Wrap(handler)))
func Merge(middlesware ...Interface) Interface {
return Func(func(next http.Handler) http.Handler {
for i := len(middlesware) - 1; i >= 0; i-- {
next = middlesware[i].Wrap(next)
}
return next
})
}
可以看到Merge将会按传入的middlesware倒序依次执行middlesware
的Wrap
方法。根据Wrap函数的定义,那么也就是按照传入的middlesware 顺序,依次执行对应的handler函数。
middleWare顺序理解
在initQueryFrontend()
中,会依次执行t.HTTPAuthMiddleware
、httpGzipMiddleware()
、queryFrontend.TraceByIDHandler

在QueryFrontend.New()
里依次执行newTraceByIDMiddleware(cfg, o, logger)
、retryWare
、newHandler。

在newTraceByIDMiddleware()
里依次执行newDeduper(logger)
、newTraceByIDSharder(&cfg.TraceByID, o, logger)
、newHedgedRequestWare(cfg.TraceByID.Hedging)
、next
。

到这里了解到了Query Frontend组件中的MiddleWare是如何加载和使用的。如果不进行debug输出日志,还是很难理解多层级的MiddleWare封装的调用过程的。
下一节将带来Query Frontend组件中加载这些MiddleWare总的顺序和每个MiddleWare具体完成工作的解析。
往期回顾:
Grafana Tempo源码解读(七)Compactor将Block进行压缩和删除的过程
Grafana Tempo源码解读(六)Compactor的代码结构和同步Block过程
Grafana Tempo源码解读(五)总结Tempo接收数据的限制以及配置调整
Grafana Tempo源码解读(四)Ingester组件将数据写入持久化存储
Grafana Tempo源码解读(三)Ingester组件接收Trace数据的过程
Grafana Tempo源码解读(二)Distributor对Trace数据的处理和发送至Ingester
Grafana Tempo源码解读(一)Distributor建立监听接收Trace数据




