• ElasticSearch 聚合分析


    公号:码农充电站pro
    主页:https://codeshellme.github.io

    ES 中的聚合分析(Aggregations)是对数据的统计分析功能,它的优点是实时性较高,相比于 Hadoop 速度更快。

    1,聚合的分类

    ES 中的聚合分析主要有以下 3 大类,每一类都提供了多种统计方法:

    一般使用聚合分析时,通常将 size 设置为 0,表示不需要返回查询结果,只需要返回聚合结果。

    一个示例:

    # 多个 Metric 聚合,找到最低最高和平均工资
    POST index_name/_search
    {
      "size": 0,              # size 为 0
      "aggs": {
        "max_salary": {       # 自定义聚合名称
          "max": {            # 聚合类型
            "field": "salary" # 聚合字段
          }
        },
        "min_salary": {       # 自定义聚合名称
          "min": {            # 聚合类型
            "field": "salary" # 聚合字段
          }
        },
        "avg_salary": {       # 自定义聚合名称
          "avg": {            # 聚合类型
            "field": "salary" # 聚合字段
          }
        }
      }
    }
    

    2,Metrics 聚合

    Metrics 聚合可以分为单值分析和多值分析:

    • 单值分析:分析结果是单个值
      • max
      • min
      • avg
      • sum
      • cardinality:类似 distinct count
        • 注意 cardinality 对 keyword 类型数据和 text 类型数据的区别
        • keyword 类型不会进行分词处理,而 text 类型会进行分词处理
    • 多值分析:分析结果是多个值

    2.1,示例

    示例,一个员工表定义:

    DELETE /employees
    PUT /employees/
    {
      "mappings" : {
          "properties" : {
            "age" : {
              "type" : "integer"
            },
            "gender" : {
              "type" : "keyword"
            },
            "job" : {
              "type" : "text",
              "fields" : {
                "keyword" : {  # 子字段名称
                  "type" : "keyword", # 子字段类型
                  "ignore_above" : 50
                }
              }
            },
            "name" : {
              "type" : "keyword"
            },
            "salary" : {
              "type" : "integer"
            }
          }
        }
    }
    

    插入一些测试数据:

    PUT /employees/_bulk
    { "index" : {  "_id" : "1" } }
    { "name" : "Emma","age":32,"job":"Product Manager","gender":"female","salary":35000 }
    { "index" : {  "_id" : "2" } }
    { "name" : "Underwood","age":41,"job":"Dev Manager","gender":"male","salary": 50000}
    { "index" : {  "_id" : "3" } }
    { "name" : "Tran","age":25,"job":"Web Designer","gender":"male","salary":18000 }
    { "index" : {  "_id" : "4" } }
    { "name" : "Rivera","age":26,"job":"Web Designer","gender":"female","salary": 22000}
    { "index" : {  "_id" : "5" } }
    { "name" : "Rose","age":25,"job":"QA","gender":"female","salary":18000 }
    { "index" : {  "_id" : "6" } }
    { "name" : "Lucy","age":31,"job":"QA","gender":"female","salary": 25000}
    { "index" : {  "_id" : "7" } }
    { "name" : "Byrd","age":27,"job":"QA","gender":"male","salary":20000 }
    { "index" : {  "_id" : "8" } }
    { "name" : "Foster","age":27,"job":"Java Programmer","gender":"male","salary": 20000}
    { "index" : {  "_id" : "9" } }
    { "name" : "Gregory","age":32,"job":"Java Programmer","gender":"male","salary":22000 }
    { "index" : {  "_id" : "10" } }
    { "name" : "Bryant","age":20,"job":"Java Programmer","gender":"male","salary": 9000}
    { "index" : {  "_id" : "11" } }
    { "name" : "Jenny","age":36,"job":"Java Programmer","gender":"female","salary":38000 }
    { "index" : {  "_id" : "12" } }
    { "name" : "Mcdonald","age":31,"job":"Java Programmer","gender":"male","salary": 32000}
    { "index" : {  "_id" : "13" } }
    { "name" : "Jonthna","age":30,"job":"Java Programmer","gender":"female","salary":30000 }
    { "index" : {  "_id" : "14" } }
    { "name" : "Marshall","age":32,"job":"Javascript Programmer","gender":"male","salary": 25000}
    { "index" : {  "_id" : "15" } }
    { "name" : "King","age":33,"job":"Java Programmer","gender":"male","salary":28000 }
    { "index" : {  "_id" : "16" } }
    { "name" : "Mccarthy","age":21,"job":"Javascript Programmer","gender":"male","salary": 16000}
    { "index" : {  "_id" : "17" } }
    { "name" : "Goodwin","age":25,"job":"Javascript Programmer","gender":"male","salary": 16000}
    { "index" : {  "_id" : "18" } }
    { "name" : "Catherine","age":29,"job":"Javascript Programmer","gender":"female","salary": 20000}
    { "index" : {  "_id" : "19" } }
    { "name" : "Boone","age":30,"job":"DBA","gender":"male","salary": 30000}
    { "index" : {  "_id" : "20" } }
    { "name" : "Kathy","age":29,"job":"DBA","gender":"female","salary": 20000}
    

    min 聚合分析:

    # Metric 聚合,找到最低的工资
    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "min_salary": {
          "min": {    # 聚合类型,求最小值
            "field":"salary"
          }
        }
      }
    }
    
    # 返回结果
    "hits": {
      "total": {
        "value": 20,      # 一共统计了多少条数据
        "relation": "eq"
      },
      "max_score": null,
      "hits": [...]       # 因为 size 为 0
    },
    "aggregations": {
      "min_salary": {     # 自定义的聚合名称                       
        "value": 9000,
      }
    }
    

    stats 聚合分析:

    # 输出多值
    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "stats_salary": {
          "stats": {      # stats 聚合
            "field":"salary"
          }
        }
      }
    }
    
    # 返回多值结果
    "aggregations": {
      "stats_salary": {  # 自定义的聚合名称                       
         "count": 20,
         "min": 9000,
         "max": 50000,
         "avg": 24700,
         "sum": 494000
      }
    }
    

    2.2,top_hits 示例

    # 指定 size,不同岗位中,年纪最大的3个员工的信息
    POST employees/_search
    {
        "size": 0,
    	"aggs":{  
           "old_employee":{  # 聚合名称
              "top_hits":{    # top_hits 分桶
                "size":3,
                "sort":[      # 根据 age 倒序排序,选前 3 个
                  {"age":{"order":"desc"}}
                ]
              }
            }
        }
    }
    

    3,Bucket 聚合

    Bucket 聚合按照一定的规则,将文档分配到不同的中,达到分类的目的。

    Bucket 聚合支持嵌套,也就是在桶里再次分桶。

    Bucket 聚合算法:

    • Terms:根据关键字(字符串)分桶。text 类型的字段需要打开 fielddata 配置。
      • 注意 keyword 类型不会做分词处理,text 类型会做分词处理。
      • 另外 size 参数可以控制桶的数量
    • Range:按照范围进行分桶,主要针对数字类型的数据
    • Date range
    • Histogram:直方图分桶,指定一个间隔值,来进行分桶。
    • Date histogram

    3.1,Terms 示例

    示例:

    # 对 keword 进行聚合
    POST employees/_search
    {
      "size": 0,      # size 为 0
      "aggs": {
        "jobs": {     # 自定义聚合名称
          "terms": {  # terms 聚合
            "field":"job.keyword" # job 字段的 keyword 子字段
          }
        }
      }
    }
    
    # 返回值结构示例
    "aggregations": {
      "genres": {
        "doc_count_error_upper_bound": 0,   
        "sum_other_doc_count": 0,           
        "buckets": [     # 很多桶,这是一个数组                    
          {
            "key": "electronic",
            "doc_count": 6
          },
          {
            "key": "rock",
            "doc_count": 3
          },
          {
            "key": "jazz",
            "doc_count": 2
          }
        ]
      }
    }
    

    对 Text 字段进行 terms 聚合查询会出错,示例:

    # 对 Text 字段进行 terms 聚合查询
    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "jobs": {
          "terms": {
            "field":"job"  # job 是 text 类型
          }
        }
      }
    }
    
    # 对 Text 字段打开 fielddata,以支持 terms aggregation
    PUT employees/_mapping
    {
      "properties" : {
        "job":{
           "type":     "text",
           "fielddata": true  # 打开 fielddata
        }
      }
    }
    

    3.2,Terms 性能优化

    当某个字段的写入和 Terms 聚合比较频繁的时候,可用通过打开 eager_global_ordinals 配置来对 Terms 操作进行优化。

    示例:

    PUT index_name
    {
      "mappings": {
        "properties": {
          "foo": {  # 字段名称
            "type": "keyword",
            "eager_global_ordinals": true # 打开
          }
        }
      }
    }
    

    3.3,嵌套聚合示例

    Bucket 聚合支持添加子聚合来进一步分析,子聚合可以是一个 Metrics 或者 Bucket

    示例 1:

    # 指定 size,不同岗位中,年纪最大的3个员工的信息
    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "jobs": {
          "terms": {  # 先做了一个 terms 分桶
            "field":"job.keyword"
          },
          "aggs":{   # 嵌套一个聚合,称为子聚合,
            "old_employee":{  # 聚合名称
              "top_hits":{    # top_hits 分桶
                "size":3,
                "sort":[      # 根据 age 倒序排序,选前 3 个
                  {"age":{"order":"desc"}}
                ]
              }
            }
          }
        }
      }
    }
    

    示例 2 :

    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "Job_salary_stats": {
          "terms": {     # 先做了一个 terms 分桶
            "field": "job.keyword"
          },
          "aggs": {
            "salary": {
              "stats": { # 子聚合是一个 stats 
                "field": "salary"
              }
            }
          }
        }
      }
    }
    
    # 多次嵌套
    POST employees/_search
    {
      "size": 0,
      "aggs": {          # 第 1 层
        "Job_gender_stats": {
          "terms": {
            "field": "job.keyword"  # 先根据岗位分桶
          },
          "aggs": {     # 第 2 层
            "gender_stats": {
              "terms": {
                "field": "gender"   # 再根据性别分桶
              },
              "aggs": { # 第 3 层
                "salary_stats": {
                  "stats": {        # 最后根据工资统计 stats
                    "field": "salary"
                  }
                }
              }
            }
          }
        }
      }
    }
    

    3.4,Range 示例

    对员工的工资进行区间聚合:

    # Salary Ranges 分桶,可以自己定义 key
    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "salary_range": {     # 自定义聚合名称
          "range": {          # range 聚合
            "field":"salary", # 聚合的字段
            "ranges":[        # range 聚合规则/条件
              {
                "to":10000    # salary < 10000
              },
              {
                "from":10000, # 10000 < salary < 20000
                "to":20000
              },
              {               # 如果没有定义 key,ES 会自动生成
                "key":"可以使用 key 自定义名称", 
                "from":20000  # salary > 20000
              }
            ]
          }
        }
      }
    }
    

    3.5,Histogram 示例

    示例,工资0到10万,以 5000一个区间进行分桶:

    # Salary Histogram
    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "salary_histrogram": {   # 自定义聚合名称
          "histogram": {         # histogram 聚合
            "field":"salary",    # 聚合的字段
            "interval":5000,     # 区间值
            "extended_bounds":{  # 范围
              "min":0,
              "max":100000
            }
          }
        }
      }
    }
    

    4,Pipeline 聚合

    Pipeline 聚合用于对其它聚合的结果进行再聚合。

    根据 Pipeline 聚合原聚合的位置区别,分为两类:

    • Pipeline 聚合原聚合同级,称为 Sibling 聚合
      • Max_bucketMin_bucketAvg_bucketSum_bucket
      • Stats_bucketExtended-Status_bucket
      • Percentiles_bucket
    • Pipeline 聚合内嵌在原聚合之内,称为 Parent 聚合
      • Derivative:求导
      • Cumulative-sum:累计求和
      • Moving-function:滑动窗口

    4.1,Sibling 聚合示例

    示例:

    # 平均工资最低的工作类型
    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "jobs": {             # 自定义聚合名称
          "terms": {
            "field": "job.keyword",  # 先对岗位类型进行分桶
            "size": 10
          },
          "aggs": {
            "avg_salary": {
              "avg": {
                "field": "salary"   # 再计算每种工资岗位的平价值
              }
            }
          }
        },
        "min_salary_by_job":{ # 自定义聚合名称
          "min_bucket": {     # pipeline 聚合
            "buckets_path": "jobs>avg_salary"
          }                   # 含义是:对 jobs 中的 avg_salary 进行一个 min_bucket 聚合
        }
      }
    }
    

    4.2,Parent 聚合示例

    示例:

    # 示例 1
    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "age": {   # 自定义聚合名称
          "histogram": {
            "field": "age",
            "min_doc_count": 1,
            "interval": 1
          },
          "aggs": {
            "avg_salary": { # 自定义聚合名称
              "avg": {
                "field": "salary"
              }
            },              # 自定义聚合名称
            "derivative_avg_salary":{ # 注意 derivative 聚合的位置,与 avg_salary 同级
              "derivative": {         # 而不是与 age 同级
                "buckets_path": "avg_salary" # 注意这里不再有箭头 > 
              }
            }
          }
        }
      }
    }
    
    # 示例 2
    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "age": {
          "histogram": {
            "field": "age",
            "min_doc_count": 1,
            "interval": 1
          },
          "aggs": {
            "avg_salary": {
              "avg": {
                "field": "salary"
              }
            },
            "cumulative_salary":{
              "cumulative_sum": { # 累计求和
                "buckets_path": "avg_salary"
              }
            }
          }
        }
      }
    }
    

    5,聚合的作用范围

    ES 聚合的默认作用范围是 Query 的查询结果,如果没有写 Query,那默认就是在索引的所有数据上做聚合。

    比如:

    POST employees/_search
    {
      "size": 0,
      "query": {    # 在 query 的结果之上做聚合
        "range": {
          "age": {"gte": 20}
        }
      },
      "aggs": {
        "jobs": {
          "terms": {"field":"job.keyword"}
        }
      }
    }
    

    ES 支持通过以下方式来改变聚合的作用范围:

    • Query:ES 聚合的默认作用范围。
      • 一般设置 size 为 0
      • 如果没有写 Query,那默认就是在索引的所有数据上做聚合。
    • Filter:写在某个聚合的内部,只控制某个聚合的作用范围。
      • 一般设置 size 为 0
    • Post Filter:对聚合没有影响,只是对聚合的结果进行再过滤。
      • 不再设置 size 为 0
      • 使用场景:获取聚合信息,并获取符合条件的文档。
    • Global:会覆盖掉 Query 的影响。

    5.1,Filter 示例

    示例:

    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "older_person": {  # 自定义聚合名称
          "filter":{       # 通过 filter 改变聚合的作用范围
            "range":{
              "age":{"from":35}
            }
          }, # end older_person
          "aggs":{         # 在 filter 的结果之上做聚合
             "jobs":{      # 自定义聚合名称
               "terms": {"field":"job.keyword"}
             }
           }
        }, # end older_person
        "all_jobs": {     # 又一个聚合,没有 filter
          "terms": {"field":"job.keyword"}
        }
      }
    }
    

    5.2,Post Filter 示例

    示例:

    POST employees/_search
    {
      "aggs": {
        "jobs": {       # 自定义聚合名称
          "terms": {"field": "job.keyword"}
        }
      }, # end aggs
      "post_filter": {  # 一个 post_filter,对聚合的结果进行过滤
        "match": {
          "job.keyword": "Dev Manager"
        }
      }
    }
    

    5.3,Global 示例

    POST employees/_search
    {
      "size": 0,
      "query": {    # 一个 query
        "range": {
          "age": {"gte": 40}
        }
      },
      "aggs": {
        "jobs": {   # 一个聚合
          "terms": {"field":"job.keyword"}
        },
        "all":{            # 又一个聚合,名称为 all
          "global":{},     # 这里的 global 会覆盖掉上面的 query,使得聚合 all 的作用范围不受 query 的影响
          "aggs":{         # 子聚合
            "salary_avg":{ # 自定义聚合名称
              "avg":{"field":"salary"}
            }
          }
        }
      }
    }
    

    6,聚合中的排序

    6.1,基于 count 的排序

    聚合中的排序使用 order 字段,默认按照 _count_key 进行排序。

    • _count:表示按照文档数排序,如果不指定 _count,默认按照降序进行排序。
    • _key:表示关键字(字符串值),如果文档数相同,再按照 key 进行排序。

    示例 1:

    # 使用 count 和 key
    POST employees/_search
    {
      "size": 0,
      "query": {
        "range": {
          "age": {"gte": 20}
        }
      },
      "aggs": {
        "jobs": {               # 自定义聚合名称
          "terms": {            # terms 聚合
            "field":"job.keyword",
            "order":[           # order 排序
              {"_count":"asc"}, # 先安装文档数排序
              {"_key":"desc"}   # 如果文档数相同,再按照 key 排序
            ]
          }
        }
      }
    }
    

    6.2,基于子聚合的排序

    也可以基于子聚合排序。

    示例 2:

    # 先对工作种类进行分桶
    # 再以工作种类的平均工资进行排序
    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "jobs": {      # 自定义聚合名称
          "terms": {
            "field":"job.keyword",
            "order":[  # 基于子聚合的排序
                 {"avg_salary":"desc"}
               ]                
           }, # end terms
        "aggs": {         # 子聚合
          "avg_salary": { # 子聚合名称
            "avg": {"field":"salary"}
           }
          }
        } # end jobs
      }
    }
    

    如果子聚合是多值输出,也可以基于 子聚合名.属性 来进行排序,如下:

    POST employees/_search
    {
      "size": 0,
      "aggs": {
        "jobs": {
          "terms": {
            "field":"job.keyword",
            "order":[  # 基于子聚合的属性排序
                {"stats_salary.min":"desc"}
             ]
         }, # end terms
        "aggs": {
          "stats_salary": { # 子聚合是多值输出
            "stats": {"field":"salary"}
            }
          }
        } # end jobs
      }
    }
    

    7,聚合分析的原理及精准度

    下面介绍聚合分析的原理及精准度的问题。

    7.1,分布式系统的三个概念

    分布式系统中有三个概念:

    • 数据量
    • 精准度
    • 实时性

    对于分布式系统(数据分布在不同的分片上),这三个指标不能同时具备,同时只能满足其中的 2 个条件

    • Hadoop 离线计算:可以同时满足大数据量和精准度
    • 近似计算:可以同时满足大数据量和实时性
    • 有限数据计算:可以同时满足精准度和实时性

    在这里插入图片描述

    ES 属于近似计算,具备了数据量实时性的特点,失去了精准度

    7.2,聚合分析的原理

    ES 是一个分布式系统,数据分布在不同的分片上。

    因此,ES 在进行聚合分析时,会先在每个主分片上做聚合,然后再将每个主分片上的聚合结果进行汇总,从而得到最终的聚合结果。

    在这里插入图片描述

    7.3,聚合分析的精准度

    分布式聚合的原理,会天生带来精准度的问题,但并不是所有的聚合分析都有精准度问题:

    • 比如 Min 聚合 就不会有精准度问题。
      • 因为求总的最小值,与先在所有主分片求最小值,再汇总每个主分片的最小值,它们最终的结果是一样的。
    • 比如 Terms 聚合 就有精准度问题。

    下面来看下 Terms 聚合存在的问题,下图中的:

    • A(6) 表示 A 类的文档数有 6 个。
    • B(4) 表示 B 类的文档数有 4 个。
    • C(4) 表示 C 类的文档数有 4 个。
    • D(3) 表示 D 类的文档数有 3 个。

    下图是 Terms 聚合流程:

    在这里插入图片描述

    上图中,在进行 Terms 聚合时(最终结果只要按照数量排序的前 3 个),需要分别在分片 P0P1上做聚合,然后再将它们的聚合结果进行汇总。

    正确的聚合结果应该是 A(12),B(6),D(6),但是由于分片的原因,ES 计算出来的结果是 A(12),B(6),C(4)。这就是 Terms 聚合存在的精准度问题。

    7.4,show_term_doc_count_error 参数

    打开 show_term_doc_count_error 配置可以使得 terms 聚合的返回结果中有一个 doc_count_error_upper_bound 值(最小为0),通过该值可以了解精准程度;该值越小,说明 Terms 的精准度越高

    POST index_name/_search
    {
      "size": 0,
      "aggs": {
        "weather": {  # 自定义聚合名称
          "terms": {  # terms 聚合
            "field":"OriginWeather",
            "show_term_doc_count_error":true # 打开
          }
        }
      }
    }
    

    7.5,如何提高 terms 精准度

    提高 terms 聚合的精准度有两种方式:

    • 将主分片数设置为 1。
      • 因为 terms 的不准确是由于分片导致的,如果将主分片数设置为 1,就不存在不准确的问题。
      • 这种方式在数据量不是很大的时候,可以是使用。
    • shard_size 的值尽量调大(意味着从分片上额外获取更多的数据,从而提升准确度)。
      • shard_size 值变大后,会使得计算量变大,进而使得ES 的整体性能变低,精准度变高
      • 所以需要权衡 shard_size 值与精准度的平衡。
      • shard_size 值的默认值是 【size * 1.5 + 10】。

    设置 shard_size 的语法:

    POST my_flights/_search
    {
      "size": 0,
      "aggs": {
        "weather": {
          "terms": {
            "field":"OriginWeather",
            "size":1,
            "shard_size":1,
            "show_term_doc_count_error":true
          }
        }
      }
    }
    

    (本节完。)


    推荐阅读:

    ElasticSearch 查询

    ElasticSearch URI 查询

    ElasticSearch DSL 查询

    ElasticSearch 文档及操作

    ElasticSearch 搜索模板与建议


    欢迎关注作者公众号,获取更多技术干货。

    码农充电站pro

  • 相关阅读:
    UVA 12545 Bits Equalizer
    UVA 1610 Party Games
    UVA 1149 Bin Packing
    UVA 1607 Gates
    UVA 12627 Erratic Expansion
    UVA10562-Undraw the Trees(递归)
    UVA10129-Play on Words(欧拉路径)
    UVA816-Abbott's Revenge(搜索进阶)
    UVA1103-Ancient Messages(脑洞+dfs)
    UVA839-Not so Mobile
  • 原文地址:https://www.cnblogs.com/codeshell/p/14439579.html
Copyright © 2020-2023  润新知