Elasticsearch系列---聚合查詢(二)

概要

近似聚合算法

上一篇咱們演練的聚合算法,在Elasticsearch分佈式場景下,實際上是有略微區別的,簡單來講咱們能夠把這些聚合算法分紅兩類,易並行算法和不易並行算法。java

易並行算法

好比max,min,就是多個node或shard能夠單獨並行計算,而且能夠隨着機器數的線性增加而橫向擴展,沒有任何協調操做,獲得的結果返回給Coordinate Node時的數據量已經很是小了,像max或min,只需返回給Coordinate Node一個Long值就行。node

Elasticsearch系列---聚合查詢(二)

不易並行算法

沒有上述的優點,每一個node或shard返回的數據都特別大,節點越多,Coordinate Node處理壓力越大,如經典的TOP N問題。算法

Elasticsearch系列---聚合查詢(二)

近似算法

針對易並行算法,處理節點分散,結果準確,但針對不易並行算法,ES會採起近似聚合的方式,採用cardinality或percentiles算法,這兩個算法近似估計後的結果,不徹底準確,偏差率約爲0.5%,可是速度會很快,通常會達到徹底精準的算法的性能的數十倍。服務器

三角選擇原則

有點相似於CAP理論,精準、實時、大數據,只能選擇其中2個微信

  1. 精準+實時: 數據量小,隨便玩
  2. 精準+大數據:hadoop,批處理,非實時,能夠處理海量數據,保證精準,可能會跑幾個小時
  3. 大數據+實時: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作機率估算從而獲得基數。app

precision_threshold參數

權衡準確率與內存開銷的參數,值的範圍爲[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
           }
        }
      }
    }
  }
}
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%的請求的耗時最長在多長時間
  1. 建立索引

    PUT /musicsite
    {
    "mappings": {
        "_doc": {
            "properties": {
                "latency": {
                    "type": "long"
                },
                "province": {
                    "type": "keyword"
                },
                "timestamp": {
                    "type": "date"
                }
            }
        }
    }
    }
  2. 灌點測試數據

    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" }
  3. 執行百分比搜索
    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百分位等級

百分比算法還有一個比較重要的度量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
            }
          }
        }
      ]
    }
  }
}

這個結果告訴咱們三點信息:

  1. 全部請求均在800ms內完成
  2. 廣東地區的訪問所有在200ms內完成,SLA達標率100%
  3. 黑龍江地區的訪問200ms內完成的佔比爲32.738%,SLA達標率32.738%

percentile_ranks度量提供了與percentiles 相同的信息,但它以不一樣方式呈現,在系統監控中,percentile_ranks比percentiles要更經常使用一些。

percentiles優化

TDigest算法特性:

  1. 1%或99%的數據要50%要準確,一頭一尾的數據更容易表現出問題所在,人們也更關注。
  2. 數值集合較小時,結果很是準確。
  3. bucket裏面數據量特別大時,開始進行估算,偏差與數據分佈和數據量相關。

用不少節點來執行百分比的計算,近似估計,有偏差,節點越多,越精準

compression參數能夠控制內存與準確度之間的比值,默認值是100,值越大,內存消耗越多,結果也更精確。

一個node使用32字節內存,默認狀況下須要消耗100 20 32 = 64KB用於TDigest計算。

這個瞭解一下就行。

小結

本篇對聚合查詢的一些原理作了簡單的介紹,近似算法的使用場景較多,系統數據監控是其中一個案例,能夠了解一下。

專一Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公衆號:Java架構社區
能夠掃左邊二維碼添加好友,邀請你加入Java架構社區微信羣共同探討技術
Java架構社區

相關文章
相關標籤/搜索