[ thanos原始碼分析系列 ]thanos query元件原始碼簡析
1 概述:
1.1 原始碼環境
版本資訊如下:
a、thanos元件版本:v0.16.0
1.2 Thanos Query的作用
Thanos Query元件是http伺服器 + grpc伺服器,它的資料來源是位於下游的已發現的實現STORE API的元件(例如Thanos Sidecar元件、Thanos Store元件、Thanos Ruler元件),同時實現了Prometheus官方的HTTP API。Thanos Query元件從下游處獲得資料後,能進行合併、去重等操作,最後將結果返回給外部的客戶端。因此,Thanos Query就是資料庫中介軟體的角色。
2 原始碼簡析:
使用github.com/oklog/run包來啟動一組協程,這些協程的邏輯主要是啟動了http server、grpc server、動態發現位於下游的實現STORE API的元件等。
2.1 main方法
Thanos的啟動命令格式如下,格式都是thanos開頭(因為是同一個可執行二進位制檔案)。啟動哪個元件,在於第一個引數,在本例子中是query,因此這條命令是啟動query元件的邏輯。
thanos query \
--log.level=debug \
--query.auto-downsampling \
--grpc-address=0.0.0.0:10901 \
--http-address=0.0.0.0:9090 \
--query.partial-response \
--query.replica-label=prometheus_replica \
--query.replica-label=rule_replica \
--store=dnssrv+_grpc._tcp.prometheus-headless.thanos.svc.cluster.local \
--store=dnssrv+_grpc._tcp.thanos-rule.thanos.svc.cluster.local \
--store=dnssrv+_grpc._tcp.thanos-store.thanos.svc.cluster.local
來具體看看main方法。建立app物件,app物件包含了所有Thanos元件的啟動函式,但真正啟動時只從map中取出一個函式進行啟動,取出哪個函式取決於啟動命令。
func main() {
/*
其他程式碼
*/
app := extkingpin.NewApp(kingpin.New(filepath.Base(os.Args[0]), "A block storage based long-term storage for Prometheus").Version(version.Print("thanos")))
/*
其他程式碼
*/
// 把所有元件的啟動邏輯都放進app物件中的setups列表中
registerSidecar(app)
registerStore(app)
registerQuery(app)
registerRule(app)
registerCompact(app)
registerTools(app)
registerReceive(app)
registerQueryFrontend(app)
// 根據命令列的資訊,從app物件的setups列表中取出一個元件邏輯
cmd, setup := app.Parse()
logger := logging.NewLogger(*logLevel, *logFormat, *debugName)
/*
其他程式碼
*/
var g run.Group
var tracer opentracing.Tracer
/*
tracing相關的程式碼
*/
reloadCh := make(chan struct{}, 1)
// 啟動特定的一個元件(sidecar、query、store等元件中的一種),底層還是執行g.Add(...)
if err := setup(&g, logger, metrics, tracer, reloadCh, *logLevel == "debug"); err != nil {
os.Exit(1)
}
// 監聽來自系統的殺死訊號.
{
cancel := make(chan struct{})
g.Add(func() error {
return interrupt(logger, cancel)
}, func(error) {
close(cancel)
})
}
// 監聽來配置過載的訊號
{
cancel := make(chan struct{})
g.Add(func() error {
return reload(logger, cancel, reloadCh)
}, func(error) {
close(cancel)
})
}
// 阻塞地等待所有協程中的退出
// 有一個協程返回,其他協程也會返回
if err := g.Run(); err != nil {
level.Error(logger).Log("err", fmt.Sprintf("%+v", errors.Wrapf(err, "%s command failed", cmd)))
os.Exit(1)
}
// 到達此處,說明整個程式結束了。
level.Info(logger).Log("msg", "exiting")
}
2.2 registerQuery方法
func registerQuery(app *extkingpin.App) {
cmd := app.Command(comp.String(), "query node exposing PromQL enabled Query API with data retrieved from multiple store nodes")
/*
解析命令列引數
secure := cmd.Flag("grpc-client-tls-secure", "Use TLS when talking to the gRPC server").Default("false").Bool()
等等諸如此類
*/
//Setup()的入參方法,會被放入app物件的setups列表中
//最核心的是runQuery()方法
cmd.Setup(func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ <-chan struct{}, _ bool) error {
return runQuery(
g,
logger,
reg,
tracer,
*requestLoggingDecision,
*grpcBindAddr,
time.Duration(*grpcGracePeriod),
*grpcCert,
*grpcKey,
*grpcClientCA,
/*
其他程式碼
*/
)
)
}
2.3 runQuery方法
使用run.Group物件來啟動http server、grpc server、服務發現協程。
func runQuery(
g *run.Group, //其實來自main()方法
logger log.Logger,
reg *prometheus.Registry,
tracer opentracing.Tracer,
requestLoggingDecision string,
grpcBindAddr string,
grpcGracePeriod time.Duration,
grpcCert string,
grpcKey string,
grpcClientCA string,
/*
其他程式碼
*/
) error {
var (
// stores物件的型別StoreSet。它包含了一組store元件(位於下游的實現Store API的元件),這一組store元件是可以動態變化的
/*
type StoreSet struct {
//其他屬性
stores map[string]*storeRef
}
*/
stores = query.NewStoreSet(...)
// proxy物件,即下游的Store API元件的代理
// 下游的Store API元件的列表,其實就是構造方法的入參stores.Get這個方法來獲取
proxy = store.NewProxyStore(logger, reg, stores.Get, component.Query, selectorLset, storeResponseTimeout)
rulesProxy = rules.NewProxy(logger, stores.GetRulesClients)
/*
queryableCreator是一個方法,用於建立一個querier結構體物件;
querier結構體的屬性proxy就是proxy物件,它包含了一組會動態變化的thanos store元件(動態變化是因為啟動了一些額外的專門的協程來動態地修改這個切片);
*/
queryableCreator = query.NewQueryableCreator(
logger,
extprom.WrapRegistererWithPrefix("thanos_query_", reg),
proxy,
maxConcurrentSelects,
queryTimeout,
)
/*
這一段程式碼都是啟動一些協程,定時發現和動態發現Store API元件的變化,隨即更新stores物件中的型別為map[string]*storeRef的屬性
*/
// 建立http server,註冊http handler,並啟動server
{
router := route.New()
//新建QueryAPI結構體物件
api := v1.NewQueryAPI(
logger,
stores,
engine,
queryableCreator,
rules.NewGRPCClientWithDedup(rulesProxy, queryReplicaLabels),
enableAutodownsampling,
enableQueryPartialResponse,
enableRulePartialResponse,
queryReplicaLabels,
flagsMap,
instantDefaultMaxSourceResolution,
defaultMetadataTimeRange,
gate.New(
extprom.WrapRegistererWithPrefix("thanos_query_concurrent_", reg),
maxConcurrentQueries,
),
)
// 為router物件註冊http方法
api.Register(router.WithPrefix("/api/v1"), tracer, logger, ins, logMiddleware)
srv := httpserver.New(logger, reg, comp, httpProbe,
httpserver.WithListen(httpBindAddr),
httpserver.WithGracePeriod(httpGracePeriod),
)
// http伺服器使用router物件
srv.Handle("/", router)
g.Add(func() error {
statusProber.Healthy()
// 啟動http server
return srv.ListenAndServe()
}, func(err error) {
statusProber.NotReady(err)
defer statusProber.NotHealthy(err)
srv.Shutdown(err)
})
}
// 建立gprc server,註冊grpc handler,並啟動server
{
tlsCfg, err := tls.NewServerConfig(log.With(logger, "protocol", "gRPC"), grpcCert, grpcKey, grpcClientCA)
if err != nil {
return errors.Wrap(err, "setup gRPC server")
}
s := grpcserver.New(logger, reg, tracer, comp, grpcProbe,
grpcserver.WithServer(store.RegisterStoreServer(proxy)), // 註冊grpc handler
grpcserver.WithServer(rules.RegisterRulesServer(rulesProxy)), // 註冊grpc handler
grpcserver.WithListen(grpcBindAddr),
grpcserver.WithGracePeriod(grpcGracePeriod),
grpcserver.WithTLSConfig(tlsCfg),
)
g.Add(func() error {
statusProber.Ready()
// 啟動grpc server
return s.ListenAndServe()
}, func(error) {
statusProber.NotReady(err)
s.Shutdown(err)
})
}
// 至此,http server和grpc server都啟動了。
level.Info(logger).Log("msg", "starting query node")
return nil
)
}
2.4 QueryAPI結構體及其方法
// QueryAPI is an API used by Thanos Query.
type QueryAPI struct {
baseAPI *api.BaseAPI
logger log.Logger
gate gate.Gate
// 構造方法,用於建立一個querier結構體物件
queryableCreate query.QueryableCreator
queryEngine *promql.Engine
ruleGroups rules.UnaryClient
/*
其他程式碼
*/
replicaLabels []string
storeSet *query.StoreSet
}
func (qapi *QueryAPI) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger, ins extpromhttp.InstrumentationMiddleware, logMiddleware *logging.HTTPServerMiddleware) {
qapi.baseAPI.Register(r, tracer, logger, ins, logMiddleware)
instr := api.GetInstr(tracer, logger, ins, logMiddleware)
/*
其他程式碼
*/
// 把qapi.query、qapi.series、 qapi.stores註冊到入參r,從而完成http handler的註冊
// 不管是/query介面和/series介面,每次請求到達都會建立querier物件,而querier物件內含了一組的Store API元件
r.Get("/query", instr("query", qapi.query))
r.Get("/series", instr("series", qapi.series))
r.Get("/stores", instr("stores", qapi.stores))
}
看看qapi.series。
//返回指標資料
func (qapi *QueryAPI) series(r *http.Request) (interface{}, []error, *api.ApiError) {
/*
其他程式碼
*/
// 建立一個querier物件
// querier物件的屬性proxy則包含了一組thanos store元件
q, err := qapi.queryableCreate(enableDedup, replicaLabels, storeDebugMatchers, math.MaxInt64, enablePartialResponse, true).
Querier(r.Context(), timestamp.FromTime(start), timestamp.FromTime(end))
/*
其他程式碼
*/
var (
metrics = []labels.Labels{}
sets []storage.SeriesSet
)
for _, mset := range matcherSets {
// 呼叫querier物件的Select()方法獲取指標
sets = append(sets, q.Select(false, nil, mset...))
}
set := storage.NewMergeSeriesSet(sets, storage.ChainedSeriesMerge)
for set.Next() {
metrics = append(metrics, set.At().Labels())
}
return metrics, set.Warnings(), nil
}
2.5 querier結構體及其方法
實現了 Querier介面(github.com/prometheus/prometheus/storage/interface.go),此介面的核心方法是Select(…),這個方法在/query和/series等介面中都會被使用到。
type querier struct {
ctx context.Context
logger log.Logger
cancel func()
mint, maxt int64
replicaLabels map[string]struct{}
storeDebugMatchers [][]*labels.Matcher
// proxy包含了一組動態的thanos store元件
proxy storepb.StoreServer
deduplicate bool
maxResolutionMillis int64
partialResponse bool
skipChunks bool
selectGate gate.Gate
selectTimeout time.Duration
}
func (q *querier) Select(_ bool, hints *storage.SelectHints, ms ...*labels.Matcher) storage.SeriesSet {
/*
其他程式碼
*/
promise := make(chan storage.SeriesSet, 1)
go func() {
defer close(promise)
var err error
/*
其他程式碼
*/
//獲取到指標資料
set, err := q.selectFn(ctx, hints, ms...)
if err != nil {
// 把錯誤送至管道,並退出本協程
promise <- storage.ErrSeriesSet(err)
return
}
//將指標資料送至管道
promise <- set
}()
// 返回指標的封裝
return &lazySeriesSet{
create: func() (storage.SeriesSet, bool) {
/*
其他程式碼
*/
// 從管道中讀取指標
set, ok := <-promise
return set, set.Next()
}
}
}
// 獲取指標,呼叫的是屬性proxy的Series(...)方法
func (q *querier) selectFn(ctx context.Context, hints *storage.SelectHints, ms ...*labels.Matcher) (storage.SeriesSet, error) {
/*
其他程式碼
*/
// seriesServer結構體重寫了Send()方法,在Sender()方法中將gprc返回的資料資料儲存到它的seriesSet屬性
resp := &seriesServer{ctx: ctx}
// q.proxy的實現是ProxyStore結構體
// q.proxy.Series()是grpc方法(流式)
// q.proxy.Series()呼叫完畢後,resp的seriesSet屬性的值會被填充
if err := q.proxy.Series(&storepb.SeriesRequest{
MinTime: hints.Start,
MaxTime: hints.End,
Matchers: sms,
/*
其他程式碼
*/
}, resp); err != nil {
return nil, errors.Wrap(err, "proxy Series()")
}
/*
其他程式碼
*/
set := &promSeriesSet{
mint: q.mint,
maxt: q.maxt,
set: newStoreSeriesSet(resp.seriesSet), // 把resp的seriesSet屬性抽出來
aggrs: aggrs,
warns: warns,
}
// set就是指標
return newDedupSeriesSet(set, q.replicaLabels, len(aggrs) == 1 && aggrs[0] == storepb.Aggr_COUNTER), nil
}
2.6 ProxyStore物件
// ProxyStore implements the store API that proxies request to all given underlying stores.
type ProxyStore struct {
logger log.Logger
// 返回位於下游的實現Store API介面的元件,查詢指標時會用到此屬性
stores func() []Client
component component.StoreAPI
selectorLabels labels.Labels
responseTimeout time.Duration
metrics *proxyStoreMetrics
}
查詢指標時,會從下游的所有的Store API的元件中查詢指標以及進行合併、去重(如果設定了)
/*
根據客戶端的請求,從下游的所有的Store API的元件中查詢指標以及進行合併、去重,最後將指標傳輸給入參srv.
這是一個gprc流式介面。
*/
func (s *ProxyStore) Series(r *storepb.SeriesRequest, srv storepb.Store_SeriesServer) error {
/*
其他程式碼
*/
g, gctx := errgroup.WithContext(srv.Context())
respSender, respCh := newCancelableRespChannel(gctx, 10)
// 生產者協程
g.Go(func() error {
/*
本協程會從後端的thanos store元件中獲取指標,並進行指標合併操作。
本協程的關閉,消費者協程也會關閉。
*/
var (
seriesSet []storepb.SeriesSet
storeDebugMsgs []string
wg = &sync.WaitGroup{}
)
defer func() {
wg.Wait()
//close()方法會引發消費者協程退出
close(respCh)
}()
// 遍歷後端的Store API元件
for _, st := range s.stores() {
/*
其他程式碼
*/
sc, err := st.Series(seriesCtx, r)
seriesSet = append(seriesSet, startStreamSeriesSet(seriesCtx, s.logger, closeSeries,
wg, sc, respSender, st.String(), !r.PartialResponseDisabled, s.responseTimeout, s.metrics.emptyStreamResponses))
/*
其他程式碼
*/
// 獲得合併後的指標,再傳送給respCh管道
mergedSet := storepb.MergeSeriesSets(seriesSet...)
for mergedSet.Next() {
lset, chk := mergedSet.At()
// respSender.send(...)其實是將指標傳送給respCh管道
respSender.send(storepb.NewSeriesResponse(&storepb.Series{Labels: labelpb.ZLabelsFromPromLabels(lset), Chunks: chk}))
}
return mergedSet.Err()
})
// 消費者協程
g.Go(func() error {
// 響應(已被merged)被本協程獲取,並將響應輸送給方法入參srv.
for resp := range respCh {
if err := srv.Send(resp); err != nil {
return status.Error(codes.Unknown, errors.Wrap(err, "send series response").Error())
}
}
return nil
})
// 等待生產者協程和消費者協程結束
if err := g.Wait(); err != nil {
return err
}
return nil
}
3 總結:
本文分析了程式碼的輪廓,還有許多細節沒有被提及,但Thanos Query元件的程式碼結構清晰易懂,使用了github.com/oklog/run包來啟動一組協程,編寫http server和grpc server的思路、動態發現下游Store API元件的套路都值得模仿。
相關文章
- Thanos工作原理及元件簡介元件
- Spring原始碼系列(二)--bean元件的原始碼分析Spring原始碼Bean元件
- Flutter 原始碼系列:DropdownButton 原始碼淺析Flutter原始碼
- SpringMVC原始碼分析系列(精簡)SpringMVC原始碼
- minipack 原始碼簡析原始碼
- vuex 原始碼簡析Vue原始碼
- 【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析JDK原始碼BloC
- ElementUI 原始碼簡析——原始碼結構篇UI原始碼
- 併發系列(二)——FutureTask類原始碼簡析原始碼
- Element(React)原始碼分析系列4--Radio元件React原始碼元件
- Spark 原始碼分析系列Spark原始碼
- Gulp.task() 原始碼簡析原始碼
- Gin使用及原始碼簡析原始碼
- DRF-Permission元件原始碼分析及改編原始碼元件原始碼
- DRF-Throttle元件原始碼分析及改編原始碼元件原始碼
- SparseArray詳解及原始碼簡析原始碼
- LinkedHashMap 詳解及原始碼簡析HashMap原始碼
- AntV X6原始碼簡析原始碼
- DelayQueue系列(一):原始碼分析原始碼
- 原始碼分析系列1:HashMap原始碼分析(基於JDK1.8)原始碼HashMapJDK
- flutter原始碼系列 PageView原始碼分析以及監聽事件Flutter原始碼View事件
- MediaScanner原始碼簡單分析原始碼
- 併發系列(一)——執行緒池原始碼(ThreadPoolExecutor類)簡析執行緒原始碼thread
- 3.2spring原始碼系列----迴圈依賴原始碼分析Spring原始碼
- gfx-hal Texture操作原始碼簡析原始碼
- Vue原始碼簡析(版本vue-2.4.4)Vue原始碼
- app直播原始碼,AnimatedOpacity 漸變元件淺析APP原始碼元件
- Retrofit原始碼分析三 原始碼分析原始碼
- Vue原始碼分析系列三:renderVue原始碼
- Java容器系列-LinkedList 原始碼分析Java原始碼
- Android Jetpack系列——ViewModel原始碼分析AndroidJetpackView原始碼
- 淺析 及整體分析 Relay 原始碼原始碼
- Java併發包原始碼學習系列:同步元件CountDownLatch原始碼解析Java原始碼元件CountDownLatch
- Java併發包原始碼學習系列:同步元件CyclicBarrier原始碼解析Java原始碼元件
- Java併發包原始碼學習系列:同步元件Semaphore原始碼解析Java原始碼元件
- prometheus的協助方案thanosPrometheus
- 容器類原始碼解析系列(一) ArrayList 原始碼分析——基於最新Android9.0原始碼原始碼Android
- 容器類原始碼解析系列(三)—— HashMap 原始碼分析(最新版)原始碼HashMap