zl程序教程

您现在的位置是:首页 >  工具

当前栏目

白日梦的Elasticsearch实战笔记,ES账号免费借用、32个查询案例、15个聚合案例、7个查询优化技巧(二)

ES案例elasticsearch笔记 优化 查询 实战 技巧
2023-09-27 14:25:58 时间
四、聚合分析#4.1、什么是聚合分析 #


聚合分析有点类似于SQL语句中的那种group by、where age 20 and age 30、这种操作。常见的聚合分析就是根据某一个字段进行分组分析 要求这个字段是不能被分词的 如果被聚合的字段被分词 按照倒排索引的方式去索引的话 就不得不去扫描整个倒排索引(才可能将被聚合的字段找全 效率很低)。


聚合分析是基于doc value的数据结果集进行操作的 这个doc value 其实就是正排索引 现在了解就好 下一篇文章统一扫盲


关于聚合分析有三个重要的概念

bucket
特别是你去使用一下java、golang中的es相关的api 就会看到这个bucket关键字 bucket就是聚合操作得到的结果集。
metric
metric就是对bucket进行分析 比如取最大值、最小值、平均值。
下钻
下钻就是在现有的分好组的bucket继续分组 比如可以先按性别分组、下钻再按年龄分组。


4.2、干货 15个聚合分析案例#


1、比如我们公司人很多 其中不泛有很多重名的人 现在我的需求是 我想知道我们公司中有多个人叫tom、多少个人叫jerry 也就是说 我想知道 重名的人分别有多少个。于是我们需要像下面这样根据名字聚合。

聚合的结果中天然存在一个metric 它就是当前bucket的count 也就是我们想要的结果


GET /your_index/your_type/_search

 # 表示只要聚合的结果 而不要参与聚合的原始数据

 “size”:0,

 # 使用聚合时 天然存在一个metric 就是当前bucket的count

 aggs : {

 group_by_name : { # 自定义的名字

 term : {

 field : name # 指定聚合的字段 意思是 group by name

GET /your_index/your_type/_search

 “size”:0,

 # 使用聚合时 天然存在一个metric 就是当前bucket的count

 aggs : {

 group_by_xxx : { # 自定义的名字

 # 除了使用term还可以使用terms

 # trems允许你指定多个字段

 terms : {

 # 指定聚合的字段 意思是 group by v1、v2、v3

 field : { value1 , value2 , value3 } 

}


2、先搜索 再对搜索结果聚合。比如我想知道在所有的男生中的重名情况


GET /your_index/your_type/_search

 # 先查询

 “query”:{

 term :{

 gender : man 

 # 再聚合

 aggs : {

 group_by_name : { 

 term : {

 field : name # 指定聚合的字段 意思是 group by name

}


3、我想把重名的人分成一组 然后我想了解每组人的平均年龄。可以像下面这样干


GET /your_index/your_type/_search

 size :0,

 # 聚合中嵌套聚合 意思是先 group by avg age 再 group by field1。

 aggs : {

 group_by_name : {

 terms : {

 field : name 

 # 在上面的name分组的结果之上再按照age聚合

 aggs : { 

 average_age : {

 # 指定聚合函数为avg

 avg : {

 field : age 

}


4、我想了解我们公司不同年龄段 20岁 25岁有多少人、25岁 30岁有多少人、30岁 35岁、35岁 40岁有多少人 以及每个年龄段有多少女生 多少男生。


GET /your_index/your_type/_search

 size :0,

 # 先按照年龄分组 在按照性别分组

 aggs : {

 group_by_age : {

 range : {

 field : age ,

 ranges : [

 from : 20,

 to : 25

 from : 25,

 to : 30

 from : 30,

 to : 35

 from : 35,

 to : 40

 aggs : {

 group_by_gender : {

 terms : {

 # gender.keyword一般是ES自动为我们创建的类型

 # keyword类型的field不会分词、默认长度256字符

 # 这里大家初步了解有这个东西 知道怎么回事就行 下一篇文章扫盲

 field : gender.keyword 

}


5、我想知道我们公司每个年龄段 每个性别的平均账户余额。


GET /your_index/your_type/_search

 size :0,

 # 先按照年龄分组 在按照性别分组 再按照平均工资聚合

 # 最终的结果就得到了每个年龄段 每个性别的平均账户余额

 aggs : {

 group_by_age : {

 range : {

 field : age ,

 ranges : [

 from : 20,

 to : 30

 aggs : {

 group_by_gender : {

 term : {

 field : gender.keyword 

 # 在上一层根据gender聚合的基础上再基于avg balance聚合

 aggs : {

 average_balance : {

 avg : {

 field : balance 

}


6、嵌套聚合 并且使用内部聚合的结果集


GET /your_index/your_type/_search

 size :0, 

 # 嵌套聚合 并且使用内部聚合的结果集

 aggs : { 

 group_by_state : {

 term : {

 field : state.keyword ,

 order : {

 # average_balance是下面内部聚合的结果集合 在此基础上做desc

 average_balance : desc 

 # 如下的agg会产出多个bucket如 

 # bucket1 {state 1 acg xxx、min xxx、max xxx、sum xxx}

 # bucket2 {state 2 acg xxx、min xxx、max xxx、sum xxx}

 aggs : {

 average_balance : {

 avg : { # avg 求平均值 metric

 field : balance 

 min_price : {

 min : { # metric 求最小值

 field : price 

 max_price : {

 max : { # metric 求最大值

 field : price 

 sum_price : {

 sum : { # metric 计算总和

 field : price 

}


8、除了前面说的按照值分组聚合 比如男、女 还可以使用histogram按区间聚合分析。


GET /your_index/your_type/_search

 size :0, 

 # histogram 类似于terms 同样会进行bucket分组操作。

 # 使用histogram需要执行一个field 比如下例中的age 表示按照age的范围进行分组聚合

 aggs : { # 聚合中嵌套聚合

 group_by_price : {

 histogram : {

 field : age ,

 # interval为10 它会划分成这样 0-10 10-20 20-30 ...

 # 那age为21的记录就会被分进20-30的区间中

 interval :10

 aggs : { # 聚合中嵌套聚合

 average_price : {

 avg : {

 field : price 

}


9、根据日期进行聚合


GET /your_index/your_type/_search

 size :0, 

 aggs : {

 agg_by_time : { 

 # 关键字

 date_histogram : {

 field : age ,

 # 间隔 一个月为一个跨度

 interval : 1M ,

 format : yyyy-MM-dd ,

 # 即使这个区间中一条数据都没有 这个区间也要返回

 min_doc_count :0 

 # 指定区间

 “extended_bounds”:{

 min : 2021-01-01 ,

 max : 2021-01-01 ,

 interval :“quarter”按照季度划分


10、filter aggregate 过滤、聚合。


# Case1

# 如下例子 我想先过滤出年龄大于20的人 然后聚合他们的平均工资

GET /your_index/your_type/_search

 size :0,

 query :{

 consitant_score :{

 # 这个filter会针对ES中全局的数据进行filter

 filter :{

 range :{ age :{ gte :20}}

 aggs :{

 avg_salary :{

 avg :{

 field : salary 

# Case2

# bucket filter

POST /sales/_search

 aggs : {

 # T恤bucket的agg

 agg_t_shirts : {

 filter : { 

 term : {

 type : t-shirt 

 aggs : {

 avg_price : { avg : { field : price } }

 # 毛衣bucket的agg

 agg_sweater : {

 filter : { 

 term : {

 type : sweater 

 aggs : {

 avg_price : { avg : { field : price } }

}


11、嵌套聚合-广度优先


说一个应用于场景: 我们检索电影的评论 但是我们先按照演员分组聚合 再按照评论的数量进行聚合。且我们假设每个演员都出演了10部电影。


分析: 如果我们选择深度优先的话 ES在构建演员电影相关信息时 会顺道计算出电影下面评论数的信息 假如说有10万个演员的filter aggregate话 10万*10 100万个电影 每个电影下又有很多影评 接着处理影评 就这样内存中可能会存在几百万条数据 但是我们最终就需要50条 这种开销是很大的。


广度优先的话 是我们先处理电影数 而不管电影的评论数的聚合情况 先从10万演员中干掉99990条数据 剩下10个演员再聚合。


 aggs :{

 target_actors :{

 terms :{

 field : actors ,

 size :10,

 collect_mode : breadth_first # 广度优先

 }


12、global aggregation


全局聚合 下面先使用query进行全文检索 然后进行聚合 下面的聚合实际上是针对两个不同的结果进行聚合。


第一个聚合添加了global关键字 意思是ES中存在的所有doc进行聚合计算得出t-shirt的平均价格
第二个聚合针对全文检索的结果进行聚合


POST /sales/_search?size 0

 query : {

 # 全文检索 type t-shirt的商品

 match : { type : t-shirt }

 aggs : {

 all_products : {

 global : {}, # 表示让 all_products 对ES中所有数据进行聚合

 aggs : {

 # 没有global关键字 表示针对全文检索的结果进行聚合

 avg_price : { avg : { field : price } }

 t_shirts : { avg : { field : price } }

}


13、Cardinality Aggregate 基数聚合


在ES中聚合时去重一般选用cardinality metric 它可以实现对每一个bucket中指定的field进行去重 最终得到去重后的count值。


虽然她会存在5%左右的错误率 但是性能特别好


POST /sales/_search?size 0

 aggs : {

 # 先按照月份聚合得到不同月的bucket

 agg_by_month : {

 date_histogram :{

 field : my_month ,

 internal : month 

 # 在上一步得到的以月份为维护划分的bucket基础上 再按照品牌求基数去重。

 # 于是最终我们就得到了每个月、每种品牌的销售量。

 aggs : {

 dis_by_brand : {

 cardinality : { 

 field : brand 

}


对Cardinality Aggregate的性能优化 添加 precision_threshold 优化准确率和内存的开销。


还是下面的例子 如果将precision_threshold的值调整到100意思是 当品牌的总数量小于100时 去重的精准度为100% 此时内存的占用情况为 100*8 800字节。

加入我们将这个值调整为1000 意思是当品台的种类在1000个以内时 去重的精准度100% 内存的占用率为1000*8 80KB。


官方给出的指标是 将precision_threshold设置为5时 错误率会被控制在5%以内。


POST /sales/_search?size 0

 aggs : {

 type_count : {

 cardinality : { # 关键字

 field : brand 

 precision_threshold :100

}


进一步优化 Cardinality底层使用的算法是 HyperLogLog 。


因为这个算法的底层会对所有的 unique value取hash值 利用这个hash值去近似的求distcint count 因此我们可以在创建mapping时 将这个hash的求法设置好 添加doc时 一并计算出这个hash值 这样 HyperLogLog 就无需再计算hash值 而是直接使用。从而达到优化速度的效果。


PUT /index/

 mappings :{

 my_type :{

 properties :{

 my_field :{

 type : text ,

 fields :{

 hash :{

 type : murmu3 

}


14、控制聚合的升降序


比如我想知道每种颜色item的平均价格 并且我希望按照价格的从小到大升序展示给我看。


于是就像下面这样 先按照颜色聚合可以将相同颜色的item聚合成1组 在聚合的结果上再根据价格进行聚合。期望在最终的结果中 通过order控制按照价格聚合的分组中升序排序 这算是个在下钻分析时的排序技巧。


GET /index/type/_search

 size :0 

 aggs :{

 group_by_color :{

 term :{

 field : color ,

 order :{ #

 avg_price : asc 

 aggs :{

 # 在上一层按color聚合的基础上 再针对price进行聚合

 avg_price :{

 avg :{

 field : price 

}


15、Percentiles Aggregation


计算百分比 常用它计算 在200ms内成功访问网站的比率、在500ms内成功访问网站的比例、在1000ms内成功访问网站的比例 或者是销售价为1000元的商品占总销售量的比例、销售价为2000元的商品占总销售量的比例等等。

示例: 针对doc中的 load_time字段 计算出在不同百分比下面的 load_time_outliner情况。


GET latency/_search

 size : 0 

 aggs : {

 load_time_outlier : {

 # 关键字

 percentiles : {

 field : load_time 

}


响应解读 在百分之50的加载请求中 平均load_time的时间是在445.0。 在99%的请求中 平均加载时间980.1。


{

 aggregations : {

 load_time_outlier : {

 values : {

 1.0 : 9.9,

 5.0 : 29.500000000000004,

 25.0 : 167.5,

 50.0 : 445.0,

 75.0 : 722.5,

 95.0 : 940.5,

 99.0 : 980.1000000000001

}


还可以自己指定百分比跨度间隔。


GET latency/_search

 size : 0 

 aggs : {

 load_time_outlier : {

 percentiles : {

 field : load_time ,

 percents : [95,99,99.9] 

}


优化: percentile底层使用的是 TDigest算法。用很多个节点执行百分比计算 近似估计 有误差 节点越多 越精准。


可以设置compression的值 默认是100 ES限制节点的最多是 compression*20 2000个node去计算 因为节点越多 性能就越差。


一个节点占用 32字节 1002032 64KB。


GET latency/_search

 size : 0 

 aggs : {

 load_time_outlier : {

 percentiles : {

 field : load_time ,

 percents : [95,99,99.9],

 compression :100 # 默认值100

}


参考 https://www.elastic.co/guide/en/elasticsearch/reference/6.2/search-aggregations.html


五、7个查询优化技巧#第一种 多字段检索 巧妙控制权重第一种 更换写法 改变占用的权重比例。第三种: 如果不希望使用相关性得分 使用下面的语法。第四种: 灵活的查询第五种: 比如我对title字段进行检索 我希望检索结果中包含 java 并且我允许检索结果中包含 ”golang“ 但是 如果检索结果中包含”golang“ 我希望这个title中包含”golang“的doc的排名能靠后一些。第六种: 重打分机制第七种: 提高召回率和精准度的技巧 混用match和match_phrase slop提高召回率。注意下面的嵌套查询层级 bool、must、should



上面的七种优化相关性得分的方式的具体实现代码 在公众号原文中可以查看到 推荐阅读原文 json的格式会好看很多 ES专题依然在连载中 欢迎关注。

点击阅读原文 查看7种优化方式的具体实现代码

点击阅读原文 查看7种优化方式的具体实现代码

点击阅读原文 查看7种优化方式的具体实现代码


参考

官方文档 https://www.elastic.co/guide/en/elasticsearch/reference/6.0

query dsl https://www.elastic.co/guide/en/elasticsearch/reference/6.2/query-dsl.html

聚合分析 https://www.elastic.co/guide/en/elasticsearch/reference/6.2/search-aggregations.html



欢迎关注#


点击阅读原文 怎么关注我你懂的


Elasticsearch聚合学习之四:结果排序 在前面的实战中,聚合的结果以桶(bucket)为单位,放在JSON数组中返回,这些数据是没有排序的,今天来学习如何给这些数据进行排序
Elasticsearch聚合学习之三:范围限定 在聚合查询时,很少用到索引中的全部文档,这就需要我们对数据的范围做限定,本文对范围限定涉及的各种操作进行了说明和实践
elasticsearch实战三部曲之三:搜索操作 本文是《elasticsearch实战三部曲》的终篇,作为elasticsearch的核心功能,搜索的重要性不言而喻,今天的实战都会围绕搜索展开