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

概要

近似聚合算法

上一篇我們演練的聚合算法,在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個

  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做概率估算從而得到基數。

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%的請求的耗時最長在多長時間

  1. 創建索引

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

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" }
  1. 執行百分比搜索

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
            }
          }
        }
      ]
    }
  }
}

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

  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架構社區

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章