上一篇咱们演练的聚合算法,在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架构社区微信群共同探讨技术