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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 白日梦的Elasticsearch实战笔记,32个查询案例、15个聚合案例、7个查询优化技巧(二)

四、聚合分析#


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


推荐阅读#


  1. MySQL的修仙之路,图文谈谈如何学MySQL、如何进阶!(已发布)
  2. 面前突击!33道数据库高频面试题,你值得拥有!(已发布)
  3. 大家常说的基数是什么?(已发布)
  4. 讲讲什么是慢查!如何监控?如何排查?(已发布)
  5. 对NotNull字段插入Null值有啥现象?(已发布)
  6. 能谈谈 date、datetime、time、timestamp、year的区别吗?(已发布)
  7. 了解数据库的查询缓存和BufferPool吗?谈谈看!(已发布)
  8. 你知道数据库缓冲池中的LRU-List吗?(已发布)
  9. 谈谈数据库缓冲池中的Free-List?(已发布)
  10. 谈谈数据库缓冲池中的Flush-List?(已发布)
  11. 了解脏页刷回磁盘的时机吗?(已发布)
  12. 用十一张图讲清楚,当你CRUD时BufferPool中发生了什么!以及BufferPool的优化!(已发布)
  13. 听说过表空间没?什么是表空间?什么是数据表?(已发布)
  14. 谈谈MySQL的:数据区、数据段、数据页、数据页究竟长什么样?了解数据页分裂吗?谈谈看!(已发布)
  15. 谈谈MySQL的行记录是什么?长啥样?(已发布)
  16. 了解MySQL的行溢出机制吗?(已发布)
  17. 说说fsync这个系统调用吧! (已发布)
  18. 简述undo log、truncate、以及undo log如何帮你回滚事物! (已发布)
  19. 我劝!这位年轻人不讲MVCC,耗子尾汁! (已发布)
  20. MySQL的崩溃恢复到底是怎么回事? (已发布)
  21. MySQL的binlog有啥用?谁写的?在哪里?怎么配置 (已发布)
  22. MySQL的bin log的写入机制 (已发布)
  23. 删库后!除了跑路还能干什么?(已发布)
  24. 自导自演的面试现场,趣学数据库的10种文件(已发布)
相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
15天前
|
存储 SQL 监控
|
1月前
|
存储 JSON 监控
大数据-167 ELK Elasticsearch 详细介绍 特点 分片 查询
大数据-167 ELK Elasticsearch 详细介绍 特点 分片 查询
52 4
|
1月前
|
Web App开发 JavaScript Java
elasticsearch学习五:springboot整合 rest 操作elasticsearch的 实际案例操作,编写搜索的前后端,爬取京东数据到elasticsearch中。
这篇文章是关于如何使用Spring Boot整合Elasticsearch,并通过REST客户端操作Elasticsearch,实现一个简单的搜索前后端,以及如何爬取京东数据到Elasticsearch的案例教程。
183 0
elasticsearch学习五:springboot整合 rest 操作elasticsearch的 实际案例操作,编写搜索的前后端,爬取京东数据到elasticsearch中。
|
1月前
|
自然语言处理 搜索推荐 Java
SpringBoot 搜索引擎 海量数据 Elasticsearch-7 es上手指南 毫秒级查询 包括 版本选型、操作内容、结果截图(一)
SpringBoot 搜索引擎 海量数据 Elasticsearch-7 es上手指南 毫秒级查询 包括 版本选型、操作内容、结果截图
50 0
|
1月前
|
存储 自然语言处理 搜索推荐
SpringBoot 搜索引擎 海量数据 Elasticsearch-7 es上手指南 毫秒级查询 包括 版本选型、操作内容、结果截图(二)
SpringBoot 搜索引擎 海量数据 Elasticsearch-7 es上手指南 毫秒级查询 包括 版本选型、操作内容、结果截图(二)
35 0
|
2月前
|
存储 自然语言处理 关系型数据库
ElasticSearch基础3——聚合、补全、集群。黑马旅游检索高亮+自定义分词器+自动补全+前后端消息同步
聚合、补全、RabbitMQ消息同步、集群、脑裂问题、集群分布式存储、黑马旅游实现过滤和搜索补全功能
ElasticSearch基础3——聚合、补全、集群。黑马旅游检索高亮+自定义分词器+自动补全+前后端消息同步
|
2月前
|
JSON 自然语言处理 算法
ElasticSearch基础2——DSL查询文档,黑马旅游项目查询功能
DSL查询文档、RestClient查询文档、全文检索查询、精准查询、复合查询、地理坐标查询、分页、排序、高亮、黑马旅游案例
ElasticSearch基础2——DSL查询文档,黑马旅游项目查询功能
|
12天前
|
存储 安全 数据管理
如何在 Rocky Linux 8 上安装和配置 Elasticsearch
本文详细介绍了在 Rocky Linux 8 上安装和配置 Elasticsearch 的步骤,包括添加仓库、安装 Elasticsearch、配置文件修改、设置内存和文件描述符、启动和验证 Elasticsearch,以及常见问题的解决方法。通过这些步骤,你可以快速搭建起这个强大的分布式搜索和分析引擎。
29 5
|
1月前
|
存储 JSON Java
elasticsearch学习一:了解 ES,版本之间的对应。安装elasticsearch,kibana,head插件、elasticsearch-ik分词器。
这篇文章是关于Elasticsearch的学习指南,包括了解Elasticsearch、版本对应、安装运行Elasticsearch和Kibana、安装head插件和elasticsearch-ik分词器的步骤。
126 0
elasticsearch学习一:了解 ES,版本之间的对应。安装elasticsearch,kibana,head插件、elasticsearch-ik分词器。
|
2月前
|
NoSQL 关系型数据库 Redis
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
mall在linux环境下的部署(基于Docker容器),docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongodb、minio详细教程,拉取镜像、运行容器
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
下一篇
无影云桌面