概要
近似聚合算法
上一篇我們演練的聚合算法,在Elasticsearch分佈式場景下,其實是有略微區別的,簡單來說我們可以把這些聚合算法分成兩類,易並行算法和不易並行算法。
易並行算法
比如max,min,就是多個node或shard可以單獨並行計算,並且可以隨着機器數的線性增長而橫向擴展,沒有任何協調操作,得到的結果返回給Coordinate Node時的數據量已經非常小了,像max或min,只需返回給Coordinate Node一個Long值就行。
易並行算法
不易並行算法
沒有上述的優勢,每個node或shard返回的數據都特別大,節點越多,Coordinate Node處理壓力越大,如經典的TOP N問題。
不易並行算法
近似算法
針對易並行算法,處理節點分散,結果準確,但針對不易並行算法,ES會採取近似聚合的方式,採用cardinality或percentiles算法,這兩個算法近似估計後的結果,不完全準確,誤差率約爲0.5%,但是速度會很快,一般會達到完全精準的算法的性能的數十倍。
三角選擇原則
有點類似於CAP理論,精準、實時、大數據,只能選擇其中2個
-
精準+實時: 數據量小,隨便玩
-
精準+大數據:hadoop,批處理,非實時,可以處理海量數據,保證精準,可能會跑幾個小時
-
大數據+實時:es,不精準,近似估計,可能會有百分之幾的錯誤率
沒有什麼方案是100%完美的,完美主義在這裏不好使。
cartinality去重算法
cartinality可以對每個bucket中指定的field進行去重,取去重後的count,類似於count(distcint),例如,我們統計一下每個月新發布歌單中有多少位歌手
GET /music/children/_search
{
"size" : 0,
"aggs" : {
"months" : {
"date_histogram": {
"field": "releaseDate",
"interval": "month"
},
"aggs": {
"distinct_author_cnt" : {
"cardinality" : {
"field" : "author.keyword"
}
}
}
}
}
}
cartinality優化
cartinality算法基於HyperLogLog++(HLL)算法的。HLL會先對我們的輸入作哈希運算,然後根據哈希運算的結果中的bits做概率估算從而得到基數。
precision_threshold參數
權衡準確率與內存開銷的參數,值的範圍爲[0,40000],超過40000的數當作40000來處理。
假設precision_threshold配置爲100,如果字段唯一值(歌手數量)在100以內,準確率基本上是100%,歌手數量大於100時,開始節省內存而犧牲準確度,同時也會對度量結果帶入誤差。
內存消耗precision_threshold * 8 byte,precision_threshold值越大,內存佔用越大。
在實際應用中,100的閾值可以在唯一值爲百萬的情況下仍然將誤差維持5%以內。
例如:
GET /music/children/_search
{
"size" : 0,
"aggs" : {
"months" : {
"date_histogram": {
"field": "releaseDate",
"interval": "month"
},
"aggs": {
"distinct_author_cnt" : {
"cardinality" : {
"field" : "author.keyword",
"precision_threshold": 100
}
}
}
}
}
}
HLL Hash加速
默認情況下,發送一個cardinality請求的時候,會動態地對所有的field value,取hash值,HLL只需要字段內容的哈希值,我們可以在索引時就預先計算好。就能在查詢時跳過哈希計算然後將哈希值從fielddata直接加載出來,即查詢時變索引時的優化思路。
我們創建music索引時,把author字段預先執行hash計算, ES 6.3.1需要先安裝murmur3插件:
elasticsearch-plugin install mapper-murmur3
,
安裝成功有如下日誌:
[root@localhost bin]# ./elasticsearch-plugin install mapper-murmur3
-> Downloading mapper-murmur3 from elastic
[=================================================] 100%
-> Installed mapper-murmur3
請求示例:
PUT /music/
{
"mappings": {
"children": {
"properties": {
"author": {
"type": "text",
"fields": {
"hash": {
"type": "murmur3"
}
}
}
}
}
}
}
使用時,我們改用author.hash,如下示例:
GET /music/children/_search
{
"size" : 0,
"aggs" : {
"months" : {
"date_histogram": {
"field": "releaseDate",
"interval": "month"
},
"aggs": {
"distinct_author_cnt" : {
"cardinality" : {
"field" : "author.hash",
"precision_threshold": 100
}
}
}
}
}
}
這個對聚合查詢可能有一點性能上的提升,但同時也在加大存儲的壓力,如果是針對特別大的字段,比如Content這種文本,可能有提升的價值,如果是keyword的小文本,一兩個單詞的那種,求hash值已經是非常快的操作,使用HLL加速的方法可能就沒有多大效果。
percentilies百分比算法
Elasticsearch提供的另一個近似算法,用來找出異常數據,我們知道平均數和中位數的統計結果,會掩蓋掉很多真實情況,沒有多大實際意義,比如某某地區平均薪酬是xxxx元,大家都對這種報告笑而不語。
但是正態分佈的方差和標準差,可以反饋出一些數據的異常,percentilies百分比算法適用於統計這樣的數據。
我們另外舉一個案例,比如某某音樂網站訪問時延統計數據。一般有如下幾個統計點:
-
tp50:50%的請求的耗時最長在多長時間
-
tp90:90%的請求的耗時最長在多長時間
-
tp99:99%的請求的耗時最長在多長時間
-
創建索引
PUT /musicsite
{
"mappings": {
"_doc": {
"properties": {
"latency": {
"type": "long"
},
"province": {
"type": "keyword"
},
"timestamp": {
"type": "date"
}
}
}
}
}
-
灌點測試數據
POST /musicsite/_doc/_bulk
{ "index": {}}
{ "latency" : 56, "province" : "廣東", "timestamp" : "2019-12-28" }
{ "index": {}}
{ "latency" : 35, "province" : "廣東", "timestamp" : "2019-12-29" }
{ "index": {}}
{ "latency" : 45, "province" : "廣東", "timestamp" : "2019-12-29" }
{ "index": {}}
{ "latency" : 69, "province" : "廣東", "timestamp" : "2019-12-28" }
{ "index": {}}
{ "latency" : 89, "province" : "廣東", "timestamp" : "2019-12-28" }
{ "index": {}}
{ "latency" : 47, "province" : "廣東", "timestamp" : "2019-12-29" }
{ "index": {}}
{ "latency" : 123, "province" : "黑龍江", "timestamp" : "2019-12-28" }
{ "index": {}}
{ "latency" : 263, "province" : "黑龍江", "timestamp" : "2019-12-29" }
{ "index": {}}
{ "latency" : 142, "province" : "黑龍江", "timestamp" : "2019-12-29" }
{ "index": {}}
{ "latency" : 269, "province" : "黑龍江", "timestamp" : "2019-12-28" }
{ "index": {}}
{ "latency" : 358, "province" : "黑龍江", "timestamp" : "2019-12-28" }
{ "index": {}}
{ "latency" : 315, "province" : "黑龍江", "timestamp" : "2019-12-29" }
-
執行百分比搜索
GET /musicsite/_doc/_search
{
"size": 0,
"aggs": {
"latency_percentiles": {
"percentiles": {
"field": "latency",
"percents": [
50,95,99
]
}
},
"latency_avg": {
"avg": {
"field": "latency"
}
}
}
}
響應結果如下(有刪節):
{
"aggregations": {
"latency_avg": {
"value": 150.91666666666666
},
"latency_percentiles": {
"values": {
"50.0": 106,
"95.0": 353.69999999999993,
"99.0": 358
}
}
}
}
我們可以看到TP50、TP95、TP99的統計值,並且與平均值的對比,可以發現,平均值掩蓋了很多實際的問題,如果只有均值統計,那麼很多問題將難以引起警覺。
如果我們把省份條件加上,再做一次聚合統計:
GET /musicsite/_doc/_search
{
"size" : 0,
"aggs" : {
"province" : {
"terms" : {
"field" : "province"
},
"aggs" : {
"load_times" : {
"percentiles" : {
"field" : "latency",
"percents" : [50, 95, 99]
}
},
"load_avg" : {
"avg" : {
"field" : "latency"
}
}
}
}
}
}
響應結果如下(有刪節):
{
"aggregations": {
"group_by_province": {
"buckets": [
{
"key": "廣東",
"doc_count": 6,
"load_times": {
"values": {
"50.0": 51.5,
"95.0": 89,
"99.0": 89
}
},
"load_avg": {
"value": 56.833333333333336
}
},
{
"key": "黑龍江",
"doc_count": 6,
"load_times": {
"values": {
"50.0": 266,
"95.0": 358,
"99.0": 358
}
},
"load_avg": {
"value": 245
}
}
]
}
}
}
結果就很明顯:黑龍江的訪問時延明顯比廣東地區高了很多。那麼基於這個數據分析,如果需要對網站進行提速,可以考慮在東北地區部署服務器或CDN。
percentile_ranks百分位等級
百分比算法還有一個比較重要的度量percentile_ranks,與percentiles含義是互爲雙向的。例如TP 50爲106ms,表示50%的請求的耗時最長爲106ms,而用percentile_ranks的來表示的含義:耗時106ms的請求所佔的比例爲50%。
例如我們的音樂網站對SLA(服務等級協議)的要求:確保所有的請求100%,延時都必須在200ms以內。
所以我們在日常的監控中,必須瞭解有多少請求延時是在200ms以內的,多少請求是在800ms以內的。
GET /musicsite/_doc/_search
{
"size" : 0,
"aggs" : {
"group_by_province" : {
"terms" : {
"field" : "province"
},
"aggs" : {
"load_times" : {
"percentile_ranks" : {
"field" : "latency",
"values" : [200, 800]
}
}
}
}
}
}
響應(有刪節):
{
"aggregations": {
"group_by_province": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "廣東",
"doc_count": 6,
"load_times": {
"values": {
"200.0": 100,
"800.0": 100
}
}
},
{
"key": "黑龍江",
"doc_count": 6,
"load_times": {
"values": {
"200.0": 32.73809523809524,
"800.0": 100
}
}
}
]
}
}
}
這個結果告訴我們三點信息:
-
所有請求均在800ms內完成
-
廣東地區的訪問全部在200ms內完成,SLA達標率100%
-
黑龍江地區的訪問200ms內完成的佔比爲32.738%,SLA達標率32.738%
percentile_ranks度量提供了與percentiles 相同的信息,但它以不同方式呈現,在系統監控中,percentile_ranks比percentiles要更常用一些。
percentiles優化
TDigest算法特性:
-
1%或99%的數據要50%要準確,一頭一尾的數據更容易表現出問題所在,人們也更關注。
-
數值集合較小時,結果非常準確。
-
bucket裏面數據量特別大時,開始進行估算,誤差與數據分佈和數據量相關。
用很多節點來執行百分比的計算,近似估計,有誤差,節點越多,越精準
compression參數可以控制內存與準確度之間的比值,默認值是100,值越大,內存消耗越多,結果也更精確。
一個node使用32字節內存,默認情況下需要消耗100 * 20 * 32 = 64KB用於TDigest計算。
這個瞭解一下就行。
小結
本篇對聚合查詢的一些原理做了簡單的介紹,近似算法的使用場景較多,系統數據監控是其中一個案例,可以瞭解一下。
專注Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公衆號:Java架構社區
可以掃左邊二維碼添加好友,邀請你加入Java架構社區微信羣共同探討技術