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

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 白日梦的Elasticsearch实战笔记,ES账号免费借用、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

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
14小时前
|
存储 缓存 运维
Elasticsearch 8.X 检索实战调优锦囊 001
Elasticsearch 8.X 检索实战调优锦囊 001
5 0
|
8天前
|
SQL JSON DataWorks
DataWorks产品使用合集之DataWorks 数据集成任务中,将数据同步到 Elasticsearch(ES)中,并指定 NESTED 字段中的 properties 类型如何解决
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
18 0
|
12天前
|
自然语言处理 Java 索引
SpringBoot 实现 elasticsearch 查询操作(RestHighLevelClient 的案例实战)
SpringBoot 实现 elasticsearch 查询操作(RestHighLevelClient 的案例实战)
16 1
|
16天前
Elasticsearch【问题记录 02】【不能以root运行es + max virtual memory areas vm.max_map_count [65530] is too low处理】
【4月更文挑战第12天】Elasticsearch【问题记录 02】【不能以root运行es + max virtual memory areas vm.max_map_count [65530] is too low处理】
20 3
|
24天前
|
监控 搜索推荐 安全
面经:Elasticsearch全文搜索引擎原理与实战
【4月更文挑战第10天】本文是关于Elasticsearch面试准备的博客,重点讨论了四个核心主题:Elasticsearch的分布式架构和数据模型、CRUD操作与查询DSL、集群管理与性能优化,以及安全与插件扩展。文中通过代码示例介绍了如何进行文档操作、查询以及集群管理,并强调理解Elasticsearch的底层原理和优化策略对面试和实际工作的重要性。
31 6
|
2月前
|
消息中间件 Kubernetes Docker
KubeSphere 核心实战之三【在kubesphere平台上部署ElasticSearch、应用商店部署RabbitMQ和应用市场部署Zookeeper】(实操篇 3/4)
KubeSphere 核心实战之三【在kubesphere平台上部署ElasticSearch、应用商店部署RabbitMQ和应用市场部署Zookeeper】(实操篇 3/4)
47 0
|
2月前
|
消息中间件 Java 关系型数据库
【二十】springboot整合ElasticSearch实战(万字篇)
【二十】springboot整合ElasticSearch实战(万字篇)
226 47
|
9天前
|
存储 安全 数据管理
【专栏】如何在 Rocky Linux 8 上安装和配置 Elasticsearch
【4月更文挑战第28天】本文指导在Rocky Linux 8上安装配置Elasticsearch,包括添加仓库,运行`yum install elasticsearch`进行安装,修改配置文件如`cluster.name`和`network.host`,启动服务并验证其正常运行。同时,文章提及了内存、文件描述符设置及安全配置,并列出常见问题及解决方法,帮助用户成功搭建Elasticsearch。
|
12天前
|
Java Maven 开发工具
【ElasticSearch 】IK 分词器安装
【ElasticSearch 】IK 分词器安装
20 1
|
12天前
|
存储 数据可视化 数据挖掘
【ElasticSearch】ElasticSearch安装
【ElasticSearch】ElasticSearch安装
25 2

热门文章

最新文章