Druid.io 是CPU和IO雙密集型的大數(shù)據(jù)組件疾渴,因為Druid架構(gòu)中無論是處理實時數(shù)據(jù)攝入的peon進程還是存儲歷史數(shù)據(jù)的歷史節(jié)點,在負責(zé)數(shù)據(jù)存儲的同時還需要處理其節(jié)點上數(shù)據(jù)的查詢髓堪。因為這樣的架構(gòu)送朱,導(dǎo)致Druid的服務(wù)節(jié)點對磁盤,內(nèi)存和CPU都有著比較高的要求干旁。而Druid架構(gòu)本身是無法對查詢進行隔離的驶沼,所以單個節(jié)點上可能并行處理著多條查詢。而處理查詢的線程資源和內(nèi)存資源是有限的争群,所以查詢之間可能存在互相影響回怜。在這樣的背景下,當(dāng)查詢耗時比較久需要定位原因時换薄,面對紛繁的指標(biāo)玉雾,如果沒有正確的思路,那么問題定位可能會浪費開發(fā)或運維人員較多的時間轻要。為此抹凳,將自己日常工作中學(xué)習(xí)到的查詢問題定位流程總結(jié),方便日后不斷優(yōu)化問題分析流程伦腐。此次總結(jié)主要以歷史數(shù)據(jù)查詢?yōu)橹鳌?/p>
1. 預(yù)備備
1.1 查詢鏈路
拿出一張精簡的查詢流程圖,我們先簡單的看一下Druid查詢的大概流程失都,不考慮太多的外部依賴柏蘑。
①首先客戶端發(fā)送http請求,請求Broker節(jié)點(假設(shè)集群中沒有設(shè)置Router節(jié)點)粹庞;
②接著Broker就會根據(jù)zookeeper上信息咳焚,把客戶端查詢拆分成不同的subquery,分發(fā)給不同的查詢節(jié)點庞溜;
③實時數(shù)據(jù)查詢路由給middleManager上的peon進程革半,歷史數(shù)據(jù)查詢路由給相應(yīng)的historical節(jié)點;
④各個查詢節(jié)點處理完查詢之后把查詢節(jié)點返回給Broker流码;
⑤Broker節(jié)點把各個節(jié)點返回的結(jié)果進行合并又官,返回給客戶端。至此漫试,一個完整的查詢鏈路結(jié)束六敬。
1.2 Druid監(jiān)控指標(biāo)
在之前的文章中也有提過,Druid組件會將自身的指標(biāo)信息通過emitter發(fā)送出來驾荣。這一部分信息我們是可以采集到的外构,Druid原生也支持httpEmitter普泡。通過這個emitter我們可以將指標(biāo)信息發(fā)送給本地或者其他的http服務(wù),通過http服務(wù)我們可以將指標(biāo)信息發(fā)送到kafka的topic中审编,這樣我們就可以像攝入一般實時數(shù)據(jù)那樣保存實時的Druid指標(biāo)信息了撼班。
目前我們生產(chǎn)上是這樣采集指標(biāo)信息的,流程架構(gòu)如下:
社區(qū)還有一些其他的emitter垒酬,沒有嘗試過砰嘁,感興趣的可以去官網(wǎng)了解一下。
Druid的指標(biāo)還是相當(dāng)豐富的伤溉,這里不做一一介紹般码,主要說幾個跟查詢分析有關(guān)的指標(biāo)。
Broker端
metric | Description | 關(guān)聯(lián)維度 |
---|---|---|
query/time |
查詢耗時乱顾,這個耗時是相對于整個查詢而言 | Common: dataSource, type, interval, hasFilters, duration, context, remoteAddress, id. Aggregation Queries: numMetrics, numComplexMetrics. GroupBy: numDimensions. TopN: threshold, dimension. |
query/node/bytes |
各個節(jié)點返回的數(shù)據(jù) | id, status, server. |
query/node/ttfb |
子查詢從節(jié)點上返回第一個結(jié)果的耗時 | id, status, server |
Historical端
metric | Description | 關(guān)聯(lián)維度 |
---|---|---|
query/time |
查詢耗時,這個耗時是指某條查詢在這個單個節(jié)點上的耗時 | Common: dataSource, type, interval, hasFilters, duration, context, remoteAddress, id. Aggregation Queries: numMetrics, numComplexMetrics. GroupBy: numDimensions. TopN: threshold, dimension |
query/segment/time |
單個segment的查詢耗時 | id, status, segment. |
query/wait/time |
某條查詢或者某個segment等待給執(zhí)行的時間 | id, segment |
Jetty
metric | Description |
---|---|
jetty/numOpenConnections |
jetty的連接數(shù)量 |
以上板祝,分析大查詢的主要指標(biāo)就是這些了。
2. 淡定分析
一般情況走净,當(dāng)大查詢出現(xiàn)之后券时,我們能獲取到的信息一般有兩個:
- queryID
- 查詢時間
那我們的分析也就從這兩個已知信息入手。首先我們要知道的是伏伯,一個查詢耗時很長橘洞,可能并不是它本身涉及到的數(shù)據(jù)量過大或查詢語句過于復(fù)雜,也可能是因為其他的大查詢占用了太多的資源说搅,導(dǎo)致它的耗時增加炸枣。所以,分析這類查詢問題的時候我們不能只關(guān)注于這個點的問題弄唧,更要在縱向上分析其他的的查詢是否有問題适肠。
以下為了方便描述,我就使用plyql
來查詢Druid的指標(biāo)信息候引。這個是一款可以將SQL語句轉(zhuǎn)換成Druid查詢的工具侯养。沒有的話,也可以人工把下面的SQL語句轉(zhuǎn)換成json查詢澄干。
2.1 首先確認查詢在哪些節(jié)點上耗時比較長
./plyql -h druid-broker-host:8082 -q "select __time, host, value_max from druid_metrics where metric in ('query/time') and __time>='2019-12-09T13:50:00+08:00' and __time<='2019-12-09T14:05:00+08:00' and id = ''test-query-id" '
返回結(jié)果如下
注意:返回的結(jié)果中有個8082端口逛揩,這個是broker節(jié)點的端口號,后面我們可以通過這個確認查詢路由到的broker麸俘,然后從這個broker日志中撈出queryID對應(yīng)的查詢語句辩稽。
我們可以看到這個查詢被分發(fā)到了不同的歷史節(jié)點上,而且每個節(jié)點的查詢耗時都比較長从媚。這里我們可以先從一個歷史節(jié)點分析搂誉,例如:historical01。我們要分析這個查詢在這個節(jié)點上是否是因為其他的查詢才導(dǎo)致的耗時較長。這里我們就要用到兩個指標(biāo)炭懊,一個
query/wait/time
,quer/sgment/time
并级。如果前者過大,則可能是因為節(jié)點要處理的查詢過多侮腹。如果后者過大嘲碧,則大概率是因為查詢本身過于復(fù)雜導(dǎo)致,但是這種情況下父阻,一般通過判斷查詢語句就能看出了愈涩,這里就不考慮這種情況了。其實加矛,還有第三種情況就是query/wait/time
,quer/sgment/time
這兩個指標(biāo)值都不大履婉,但是查詢的耗時很長。目前我們生產(chǎn)環(huán)境中斟览,第三種情況是比較常見的毁腿。這個和第一種情況一樣,一般都是因為其他查詢的影響導(dǎo)致的苛茂。所以我們要查看已烤,這個query所在節(jié)點對應(yīng)時間點的前一段時間是否出現(xiàn)了大查詢,即耗時比較長的查詢妓羊。我們先查看query/wait/time
和quer/sgment/time
這兩個指標(biāo)胯究。
./plyql -h druid-broker-host:8082 -q "select segment,metric, value_max from druid_metrics where metric in ('query/segment/time','query/wait/time') and __time>='2019-12-09T13:50:00+08:00' and __time<='2019-12-09T14:05:00+08:00' and id = 'test-query-id' and host='historical01:8083' "
從返回的結(jié)果來看,query和wait的time都不算很長躁绸,但是我們知道這個查詢在這個節(jié)點上耗時很長裕循。所以肯定有很多的時間是耗費在等待資源上了。那么我們現(xiàn)在要做的就是找出這個占資源的查詢净刮,分析它為什么占了資源剥哑。
2.2 確認資源使用較多的查詢
還是在這個歷史節(jié)點上,我們查看這個查詢之前的一段時間庭瑰,看看是否有耗時比較大的查詢。這里需要使用子查詢抢埋,因為我們要選出查詢耗時較長的查詢弹灭,而且需要根據(jù)時間排序。因為一般都是最早的那個耗時較多的查詢導(dǎo)致后面的查詢耗時增加揪垄。
./plyql -h druid-broker-host:8082 -q "select * from (select __time,id, value_max from druid_metrics where metric in ('query/time') and __time>='2019-12-09T13:50:00+08:00' and __time<='2019-12-09T14:05:00+08:00' and host='historical01:8083' order by value_max desc limit 30) order by __time""
從結(jié)果來看每個查詢的耗時都比較大穷吮,這樣我們是沒法確認,我們需要找的是一個分界的值饥努。這里的查詢時間還沒有到達我們過濾的邊界捡鱼,所以我們增加返回的數(shù)據(jù)量。
./plyql -h druid-broker-host:8082 -q "select * from (select __time,id, value_max from druid_metrics where metric in ('query/time') and __time>='2019-12-09T13:50:00+08:00' and __time<='2019-12-09T14:05:00+08:00' and host='historical01:8083' order by value_max desc limit 100) order by __time""
到這里我們就找到了最開始耗時比較長的查詢酷愧。那么接下來就是分析一波這幾個查詢驾诈〔纾可以用之前類似的
query/wait/time
和query/segment/time
來分析。如果query/segment/time
比較大乍迄,那么基本上就是這個查詢的問題了管引。接著就可以把這個查詢的查詢語句從日志里撈出來看了。查詢語句記錄在broker的日志里闯两,怎么確定broker我們在之前的查詢中已經(jīng)提到過了褥伴。但是,有一種情況也有可能發(fā)生漾狼,就是這個最開始的幾個查詢的
query/wait/time
和query/segment/time
這兩個指標(biāo)也不大重慢,但是查詢耗時長。這時候就要進行第三步分析了逊躁。
3. 確認處理查詢的并發(fā)量
這個我們可以從兩個方面考慮似踱,一個是這段時間的查詢訪問量,一個是jetty的連接數(shù)志衣。當(dāng)節(jié)點處理的查詢并發(fā)比較大時屯援,也可能會對資源造成較大的壓力,導(dǎo)致查詢耗時增加念脯。這個可以通過zabbix查看到是節(jié)點的性能情況狞洋,這里只從Druid層面分析。
3.1 查詢訪問量
每個查詢都會對應(yīng)一個queryID绿店,我們只需要對這段時間的queryID做個簡單的count就可以算出這個節(jié)點的查詢訪問量了吉懊。
./plyql -h druid-broker-host:8082 -q "select count(id) from druid_metrics where metric in ('query/time') and __time>='2019-12-09T13:50:00+08:00' and __time<='2019-12-09T14:05:00+08:00' and host='historical01:8083'"
如果需要查看dataSource訪問量的話,只需要再做個group by:
./plyql -h druid-broker-host:8082 -q "select count(id) from druid_metrics where metric in ('query/time') and __time>='2019-12-09T13:50:00+08:00' and __time<='2019-12-09T14:05:00+08:00' and host='historical01:8083' group by dataSource"
3.1 jetty連接數(shù)
./plyql -h druid-broker-host:8082 -q "select __time, value_max from druid_metrics where metric in ('jetty/numOpenConnections') and __time>='2019-12-09T13:50:00+08:00' and __time<='2019-12-09T14:05:00+08:00' and host='historical01:8083'"
這兩個指標(biāo)假勿,可以跟歷史數(shù)據(jù)比較借嗽,就可以知道訪問量是否有波動了。
4. 小結(jié)
其實Druid查詢涉及到的內(nèi)容比較多转培,這里只討論如何分析耗時比較長的情況恶导。但是為什么會導(dǎo)致這種情況,如何去避免這種情況的產(chǎn)生浸须?這又會涉及到Druid本身的數(shù)據(jù)分配和查詢路由策略惨寿。這一部分若有機會,再做總結(jié)删窒。