上一篇咱們演練的聚合算法,在Elasticsearch分佈式場景下,實際上是有略微區別的,簡單來講咱們能夠把這些聚合算法分紅兩類,易並行算法和不易並行算法。java
好比max,min,就是多個node或shard能夠單獨並行計算,而且能夠隨着機器數的線性增加而橫向擴展,沒有任何協調操做,獲得的結果返回給Coordinate Node時的數據量已經很是小了,像max或min,只需返回給Coordinate Node一個Long值就行。node
沒有上述的優點,每一個node或shard返回的數據都特別大,節點越多,Coordinate Node處理壓力越大,如經典的TOP N問題。算法
針對易並行算法,處理節點分散,結果準確,但針對不易並行算法,ES會採起近似聚合的方式,採用cardinality或percentiles算法,這兩個算法近似估計後的結果,不徹底準確,偏差率約爲0.5%,可是速度會很快,通常會達到徹底精準的算法的性能的數十倍。服務器
有點相似於CAP理論,精準、實時、大數據,只能選擇其中2個微信
沒有什麼方案是100%完美的,完美主義在這裏很差使。架構
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算法基於HyperLogLog++(HLL)算法的。HLL會先對咱們的輸入做哈希運算,而後根據哈希運算的結果中的bits作機率估算從而獲得基數。app
權衡準確率與內存開銷的參數,值的範圍爲[0,40000],超過40000的數看成40000來處理。elasticsearch
假設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 } } } } } }
默認狀況下,發送一個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加速的方法可能就沒有多大效果。
Elasticsearch提供的另外一個近似算法,用來找出異常數據,咱們知道平均數和中位數的統計結果,會掩蓋掉不少真實狀況,沒有多大實際意義,好比某某地區平均薪酬是xxxx元,你們都對這種報告笑而不語。
可是正態分佈的方差和標準差,能夠反饋出一些數據的異常,percentilies百分比算法適用於統計這樣的數據。
咱們另外舉一個案例,好比某某音樂網站訪問時延統計數據。通常有以下幾個統計點:
建立索引
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" } } } }
響應結果以下(有刪節): ```java { "aggregations": { "latency_avg": { "value": 150.91666666666666 }, "latency_percentiles": { "values": { "50.0": 106, "95.0": 353.69999999999993, "99.0": 358 } } } }
咱們能夠看到TP50、TP9五、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,與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 } } } ] } } }
這個結果告訴咱們三點信息:
percentile_ranks度量提供了與percentiles 相同的信息,但它以不一樣方式呈現,在系統監控中,percentile_ranks比percentiles要更經常使用一些。
TDigest算法特性:
用不少節點來執行百分比的計算,近似估計,有偏差,節點越多,越精準
compression參數能夠控制內存與準確度之間的比值,默認值是100,值越大,內存消耗越多,結果也更精確。
一個node使用32字節內存,默認狀況下須要消耗100 20 32 = 64KB用於TDigest計算。
這個瞭解一下就行。
本篇對聚合查詢的一些原理作了簡單的介紹,近似算法的使用場景較多,系統數據監控是其中一個案例,能夠了解一下。
專一Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公衆號:Java架構社區
能夠掃左邊二維碼添加好友,邀請你加入Java架構社區微信羣共同探討技術