聚集查询

·  阅读 176

聚集查询

聚集查询(Aggregation)提供了针对多条文档的统计运算功能,它不是针对文 档本身内容的检索,而是要将它们聚合到一起运算某些方面的特征值。

聚集查询与 SQL 语言中的聚集函数非常像,聚集函数在 Elasticsearch 中相当 于是聚集查询的一种聚集类型。 比如在 SQL 中的 avg 函数用于求字段平均值, 而在 Elasticsearch 中要实现相同的功能可以使用 avg 聚集类型。

聚集查询也是通过_search 接口执行,只是在执行聚集查询时使用的参数是 aggregations 或 aggs。所以_search 接口可以执行两种类型的查询,1 种是通过 query 参数执行 DSL,另一种则是通过 aggregations 执行聚集查询。

这两种查询方式还可以放在一起使用, 执行逻辑是先通过 DSL 检索满足查 询条件的文档,然后再使用聚集查询对 DSL 检索结果做聚集运算这一规则适用 于本章列举的所有聚集查询。聚集查询有着比较规整的请求结构,具体格式如下:

"aggregations/aggs" : {
    "<聚集名称>":{
        "<聚集类型>":{
                <聚集体>
        }
        ………
        [,"aggregations/aggs" : ( [ <子聚集>]+ ) ]
    }
    [,"<聚集名称>”: (...) ]*
}
复制代码

aggregations 和 aggs 都是_search 的参数,其中 aggs 是 agregations 的简写。 每一个聚集查询都需要定义一个聚集名称,并归属于种聚集类型。聚集名称是用 户自定义的, 而聚集类型则是由 Elasticsearch 预先定义好。

自定义的, 而聚集类型则是由 Elasticsearch 预先定义好。 聚集名称会在返回结果中标识聚集结果,而聚集类型则决定了聚集将如何运 算。比如前面提到的 avg 就是一种聚集类型。 这里要特别强调的是,聚集中可 以再包含子聚集。子聚集位于父聚集的名称中,与聚集类型同级,所以子聚集的 运算都是在父聚集的环境中运算。Eaticsarch 对子聚集的深度没有限制,所以理 论上说可以包含无限深度的子聚集。

聚集类型总体上被分为四种大类型,即指标聚集(Metrics Aggregation)、 桶 型( Bucket Aggregation)、管道聚集( Pipeline Aggregation) 和矩阵聚集( Matrix Aggregation)。

指标聚集是根据文档字段所包含的值运算某些统计特征值,如平均值、总和 等,它们的结果一般都包含一个或多个数值,前面提到的 avg 聚集就是指标聚集 的 1 种。桶型聚集根据一定的分组标准将文档归到不同的组中,这些分组在 Elasticsearch 中被称为桶( Bucken),桶型聚集与 SQL 中 group by 的作用类似,一 般会与指标聚集嵌套使用。管道聚集可以理解为聚集结果的再聚集,它一般以另 一个聚集结果作为输人,然后在此基础上再做聚集。矩阵聚集是 Elasticsearch 中 的新功能,由于是针对多字段做多种运算,所以形成的结果类似于矩阵。

指标聚集

指标聚集是根据文档中某一字段做聚集运算,比如计算所有产品销量总和、 平均值等等。指标聚集的结果可能是单个值,这种指标聚集称为单值指标聚集; 也可能是多个值,称为多值指标聚集。

平均值聚集

平均值聚集是根据文档中数值类型字段计算平均值的聚集查询,包括 avg 聚 集和 weighted_avg 聚集两种类型。avg 聚集直接取字段值后计算平均值,而 weighted _avg 聚集则会在计算平均值时添加不同的权重。

avg 聚集

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "delay_avg":{
            "avg":{
                "field":"FlightDelayMin"
            }
        }
    }
}
复制代码

在示例 7-2 中使用了请求参数 fiter _path 将返回结果的其他字段过滤掉了, 否则查询的结果中将包含 kibana_sample_data_flights 索引中的文档。加了 filter_path 之后返果为

{
  "aggregations" : {
    "delay_avg" : {
      "value" : 47.443267776096825
    }
  }
}
复制代码

在返回结果中,aggregations 是关键字,代表这是聚集查询的结果。其中的 delay_avg 则是在聚集查询中定义的聚集名称,value 是聚集运算的结果。

在示例中运算航班延误时间时会将所有文档都包含进来做计算,如果只想其 中一部分文档参与运算则可以使用 query 参数以 DSL 的形式定义查询条件。

例如在示例中就是只计算了飞往中国的航班平均延误时间

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
  "query":{
      "match":{
          "DestCountry":"CN"
      }
  },
  "aggs":{
      "delay_avg":{
          "avg":{
              "field":"FlightDelayMin"
          }
      }
  }
}
复制代码

在示例中请求_search 接口时,同时使用了 query 与 aggs 参数。在执行检索 时会先通过 query 条件过滤文档,然后再在符合条件的文档中运算平均值聚集 weighted_avg 无非就是根据某些条件对进行聚集的数据进行加权运算,和 avg 聚集没有本质差别。

计数聚集与极值聚集

计数聚集用于统计字段值的数量,而极值聚集则是查找字段的极大值和极小 值

计数聚集

value_count 聚集和 cardinality 聚集可以归入计数聚集中,前者用于统计从 字段中取值的总数,而后者则用于统计不重复数值的总数。例如:

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "country_code":{
            "cardinality":{
                "field":"DestCountry"
            }
        },
        "total_country":{
            "value_count":{
                "field":"DestCountry"
            }
        }
    }
}
复制代码

在示例中,cardinality 聚集统计了 DestCountry 字段非重复值的数量,类似 于 SQL 中的 distinct。value_count 聚集则统计了 DestCountry 字段所有返回值的 数量,类似于 SQL 中的 count。

需要注意的是,cardinality 聚集的算法使用极小内存实现统计结果的基本准 确。所以 cardinality 在数据量极大的情况下是不能保证完全准确的。

极值聚集

极值聚集是在文档中提取某一字段最大值或最小值的聚集,包括 max 聚集 和 min 聚集

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "max_price":{
            "max":{
                "field":"AvgTicketPrice"
            }
        },
        "min_price":{
            "min":{
                "field":"AvgTicketPrice"
            }
        }
    }
}
复制代码

上面例子聚集有 max_price 和 min_price 两个,它们分别计算了机票价格的 最大的最小值。

统计聚集

统计聚集是一个多值指标聚集,也就是返回结果中会包含多个值,都是一些 与统计相生的数据。统计聚集包含 stats 聚集和 extended_stats 聚集两种,前者 返回的统计数据是一些比较基本的数值,而后者则包含一些比较专业的统计数值。

stats 聚集

stats 聚集返回结果中包括字段的最小值(min)、 最大值(max)、总和(sum)、 数业(coun) 及平均值(avg) 五项内容。例如,在示例 7-9 中对机票价格做统计:

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "price_stats":{
            "stats":{
                "field":"AvgTicketPrice"
            }
        }
    }
}
复制代码

在示例中,stats 聚集使用 field 参数指定参与统计运算的字段为 AvgTicketPrice

extended_stats 聚集

extended_stats 聚集增加了几项统计数据,这包括平方和、方差、标准方差 和标准方差偏移量。从使用的角度来看,extended_stats 聚集与 stats 聚集完全 相同,只是聚集类型不同。

百分位聚集

百分位聚集根据文档字段值统计字段值按百分比的分布情况,包括 pecrentiles 聚集和 percentile_ranks 两种。前者统计的是百分比与值的对应关系, 而后者正好相反统计值与百分比的对应关系。

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "price_percentile":{
            "percentiles":{
                "field":"AvgTicketPrice",
                "percents":[
                    25,
                    50,
                    75,
                    100
                ]
            }
        },
        "price_percentile_rank":{
            "percentile_ranks":{
                "field":"AvgTicketPrice",
                "values":[
                    600,
                    1200
                ]
            }
        }
    }
}
复制代码

pecrentiles 聚集通过 pecrents 参数设置组百分比,然后按值由小到大的顺序 划分不同区间,每个区间对应一个百分比。percentile_ranks 聚集则通过 value 参 数设置组值, 然后根据这些值分别计算落在不同值区间的百分比。

以示例返回的结果为例:

image.png

在 pecrentiles 返回结果 price_percentile 中,“"25. 0" : 410. 0127977258341" 代表的含义是 25%的机票价格都小于 410. 0127977258341,其他以此类推。在 percentile_ranks 返回结果 price_percentile_rank 中, 600.0": 45. 39892372745635"代表的含义是 600.0 以下的机票占总机票价格的百分比为 45.39892372745635%

范围分桶

如果使用 SQL 语言类比,桶型聚集与 SQL 语句中的 group by 子句极为相似。 桶型聚集(Bucket Aggregation)是 Elasticsearch 官方对这种聚集的叫法,它起的作用是根据条件对文档进行分组。

可以将这里的桶理解为分组的容器,每个桶都与一个分组标准相关联,满足 这个分组标准的文档会落桶中。所以在默认情况下,桶型聚集会根据分组标准返 回所有分组,同时还会通过 doc_count 字段返回每一桶中的文档数量。

由于单纯使用桶型聚集只返回桶内文档数量,意义并不大,所以多数情况下 都是将桶型聚集与指标聚集以父子关系的形式组合在起使用。桶型聚集作为父聚 集起到分组的作用。而指标聚集则以子聚集的形式出现在桶型聚集中, 起到分 组统计的作用。比如将用户按性别分组,然后统计他们的平均年龄。

按返回桶的数量来看,桶型聚集可以分为单桶聚集和多桶聚集。在多桶聚集 中,有些桶的数量的固定的。而有些桶的数量则是在运算时动态决定。由于桶聚 集基本都是将所有桶一次返回,返回了过多的通会影响性能,所以单个请求允许 返间的最大桶数受 search.max_bucket 参数限制。

这个参数在 7.0 之前的版本中默认值为-1,代表无上限。但在 Elasticsearch 版本 7 中,这个参数的默认值已经更改为 10000 所以在做桶型聚集时要先做好数 据验证,防止桶数量过多影响性能

桶型聚集的种类非常多,我们分别来讲解

数值范围

range、date_range 与 ip_range 这三种类型的聚集都用于根据字段的值范围 内对文档分桶,字段值在同一范围内的文档归入同一桶中。每个值范围都可通过 from 和 to 参数指定,范围包含 from 值但不包含 to 值,用数学方法表示就是[from, to)。 在设置范围时,可以设置一个也可以设置多个,范围之间并非一定要连续, 可以有间隔也可以有重叠。

range 聚集

range 聚集使用 ranges 参数设置多个数值范围,使用 field 参数指定 1 个数值 类型的字段。range 聚集在执行时会将该字段在不同范围内的文档数量统计出来, 并在返回结果的 doc_count 字段中展示出来。例如统计航班不同范围内的票价数 量,可以按示例的方式发送请求:

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "price_ranges":{
            "range":{
                "field":"AvgTicketPrice",
                "ranges":[
                    {
                        "to":300
                    },
                    {
                        "from":300,
                        "to":600
                    },
                    {
                        "from":600,
                        "to":900
                    },
                    {
                        "to":900
                    }
                ]
            }
        }
    }
}
复制代码

结果

{
  "aggregations" : {
    "price_ranges" : {
      "buckets" : [
        {
          "key" : "*-300.0", 
          "to" : 300.0,  0到300
          "doc_count" : 5250 #落在桶的数量
        },
        {
          "key" : "*-900.0",
          "to" : 900.0,  0到900
          "doc_count" : 30937
        },
        {
          "key" : "300.0-600.0",
          "from" : 300.0, 300-900
          "to" : 600.0,
          "doc_count" : 11934
        },
        {
          "key" : "600.0-900.0",
          "from" : 600.0, 600-900
          "to" : 900.0,
          "doc_count" : 13753
        }
      ]
    }
  }
}
复制代码

在返回结果中,每个范围都会包含一个 key 字段,代表了这个范围的标识, 它的基本格式是“- "。如果觉得返回的这种 key 格式不容易理解,可 以通过在 range 聚集的请求中添加 keyed 和 key 参数定制返回结果的 key 字段值。 其中 keyed 是 range 的参数,用于标识是否使用 key 标识范围,所以为布尔类型, key 参数则是与 from、to 参数同级的参数,用于定义返回结果中的 key 字段值。

date_range 聚集

date_range 聚集与 range 聚集类似,只见范围和字段的类型为日期而非数值

date_range 聚集的范围指定也是通过 ranges 参数设置,具体的范围也是使 用- 两个参数,并且可以使用 keyed 和 key 定义返回结果的标识。 date_range 聚集多了个指定日期格式的参数 format, 可以用于指定 from 和 to 的 目期格式。例如,

POST /kibana_sample_data_flights/_search?filter_path=aggregations
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "mar_flights":{
            "date_range":{
                "field":"timestamp",
                "ranges":[
                    {
                        "from":"2019-03-01",
                        "to":"2019-03-30"
                    }
                ],
                "format":"yyyy-MM-dd"
            }
        }
    }
}
复制代码

ip_range 聚集

ip_ range 聚集根据 ip 类型的字段统计落在指定 IP 范围的文档数量,使用的 聚集类型名称为 ip_ range。例如,统计了两个 IP
复制代码
 POST /kibana_sample_data_logs/_search?filter_path=aggregations
 {
 "aggs":{
     "local":{
         "ip_range":{
             "field":"clientip",
             "ranges":[
                 {
                     "from":"157.4.77.0",
                     "to":"157.4.77.255"
                 },
                 {
                     "from":"105.32.127.0",
                     "to":"105.32.127.255"
                 }
             ]
         }
     }
 }
}
复制代码

间隔范围

histogram、date _ histogram 与 auto_date_histogram 这三种聚集与上一节中 使用数值定义范围的聚集很像,也是统计落在某一范围内的文档数量。 但与数值范围聚集不同的是,这三类座集统计范围由固定的间隔定义,也就是范围的结 束值和起始值的差值是固定的

histogram 聚集

histogram 聚集以数值为间隔定义数值范围,字段值具有相同范围的文档将 落入同桶中。例如示例以 100 为间隔做分桶,可以通过返回结果的 doc_count 字 段获取票价在每个区间的文档数量:

    
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "price_histo":{
            "histogram":{
                "field":"AvgTicketPrice",
                "interval":100,
                "offset":50,
                "keyed":false,
                "order":{
                    "_count":"asc"
                }
            }
        }
    }
}
复制代码

其中,interval 参数用于指定数值问隔必须为正值,而 offset 参数则代表起 始数值的偏移量,必须位于[0, interval) 范围内。order 参数用于指定排序字段和 顺序,可选字段为_key 和_count。当 keyed 参数设置为 true 时,返回结果中每 个桶会有一个标识,标识的计算公式为

        bucket_key = Math. floor( ( value- offset)/interval) * interval + offset复制代码

date_histogram 聚集

date_histogra 聚集以时间为间隔定义日期范围,字段值具有相同日期范围的 文档将落入同一桶中。同样,返回结果中也会包含每个间隔范围内的文档数量 doc_count。 例如统计每月航班数量:

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "month flights":{
            "date_histogram":{
                "field":"timestamp",
                "interval":"month"
            }
        }
    }
}
复制代码

在示例使用参数 interval 指定时间间隔为 month,即按月划分范围。时间可以 是还有:

毫秒:1ms 10ms
秒:second/1s 10s
分钟:minute/1m 10m
小时:hout/1h 2h
天:day 2d 不支持
星期:week/1w 不支持
月:month/1M 不支持
季度:quarter/1q 不支持
年:year/1y 不支持
复制代码

不过这个参数将要过期,替代的是 fixed_interval 和 calendar_interval,可以 参考这个页面,有详细说明: www.elastic.co/guide/en/el… ns-bucket-datehistogram-aggregation.html

简单来说,calendar_interval 支持 1 年、1 季度、1 月、1 周、1 天、1 小时、 1 分钟,不支持时间为复数,fixed_interval 支持的时间单位为天、小时、分、秒、 毫秒,允许复数,例如"fixed_interval" : "30d",表示为 30 天。

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "month flights":{
            "date_histogram":{
                "field":"timestamp",
                "fixed_interval":"30d"
            }
        }
    }
}
复制代码

auto_date_histogram 聚集

前述两种聚集都是指定间隔的具体值是多少,然后再根据间隔值返回每一 桶中满足条件的文档数。最终会有多少桶取决于两个条件,即间隔值和字段值在 所有文档中的实际跨度。反过来,如果预先指定需要返回多少个桶,那么间隔值 也可以通过桶的数量以及字段值跨度共同确定。auto_date_histogram 聚集就是这 样一种聚集,它不是指定时间间隔值,而是指定需要返回桶的数量。例如在示例 中定义需要返回 10 个时间段的桶:

POST /kibana_sample_data_flights/_search?size=0
{
    "aggs":{
        "age_group":{
            "auto_date_histogram":{
                "field":"timestamp",
                "buckets":10
            }
        }
    }
}
复制代码

参数 field 设置通过哪一个字段做时间分隔,而参数 buckets 则指明了需要返 回多少个桶。 默认情况下, buckets 的数量为 10。需要注意的是,buckets 只 是设置了期望返回桶的数量,但实际返回桶的数量可能等于也可能小于 buckets 设置的值。例如示例的请求中期望 10 个桶,但实际可能只返回 6 个桶。

auto_date_histogram 聚集在返回结果中还提供了一个 interval 字段,用于说 明实际采用的间隔时间。从实现的角度来说,不精确匹配 buckets 数量也有利于 提升检索的性能

子聚集 (聚集嵌套)

前面介绍的桶型聚集,大部分都只是返回满足聚集条件的文档数量。在实际 应用中,如果需要桶型聚集与 SQL 中的 group by 具有相同的意义,用于将文档 分桶后计算各桶特定指标值,比如根据用户性别分组,然后分别求他们的平均年 龄。Elasticsearch 这样的功能通过子聚集 (聚集嵌套)来实现。例如,示例中的请 求就是先按月对从中国起飞的航班做了分桶,然后又通过聚集嵌套计算每月平均 延误时间:

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "query":{
        "term":{
            "OriginCountry":"CN"
        }
    },
    "aggs":{
        "date_price_histogram":{
            "date_histogram":{
                "field":"timestamp",
                "interval":"month"
            },
            "aggs":{
                "avg_price":{
                    "avg":{
                        "field":"FlightDelayMin"
                    }
                }
            }
        }
    }
}
复制代码

在示例中,_search 接口共使用了两个参数,query 参数以 term 查询条件将 所有 OriginCountry 字段是 CN 的文档筛选出来参与聚集运算。

aggs 参数则定义了一个名称为 data_price_histogram 的桶型聚集,这个聚集 内部又嵌套了个名称为 avg_ price 的聚集。由于 price 这个聚集位于 data_price histogram 中,所以它会使用这个聚集的分桶结果做运算而不会针对所有文档。 所以,最终的效果就是将按月计算从中国出发航班的平均延误时间,

使用嵌套聚集时要注意,嵌套聚集应该位于父聚集名称下而与聚集类型同级, 并且需要通过参数再次声明。如果与父聚集一样位于 aggs 参数下,那么这两个 聚集就是平级而非嵌套聚集

词项分桶

使用字段值范围分桶主要针对结构化数据,比如年龄、IP 地址等等。但对 于字符串类型的字段来说,使用值范围来分桶显然是不合适的。由于字符串类型 字段在编入索引时会通过分析器生成词项,所以字符申类型字段的分桶一般通过 词项实现。使用词项实现分桶的聚集,包括 terms、significant_terms 和 significant_text 聚集。由于使用词项分桶需要加载所有词项数据,所以它们在执 行速度上都会比较慢。为了提升性能,Elsticesearch 提供了 sampler 和 diversifed_sampler 聚集,可通过缩小样本数量减少运算量。

terms 聚集

terms 聚集根据文档字段中的词项做分桶,所有包含同一词项的文档将被归 人同一桶中,聚集结果中包含字段中的词项及其词频,在默认情况下还会根据词 频排序,所以 terms 聚集也可用于热词展示,由于 terms 聚集在统计词项的词频数据时需要打开它的 fielddata 机制。fielddata 机制对内存消耗较大且有导致内存 溢出的可能, 所以 terms 聚集一般针对 keyword 非 text 类型。

Fielddata:其实根据倒排索引反向出来的一个正排索引,即 document 到 term 的映射。

只要我们针对需要分词的字段设置了 fielddata,就可以使用该字段进行聚合, 排序等。我们设置为 true 之后,在索引期间,就会以列式存储在内存中。为什么存在于内存呢,因为按照 term 聚合,需要执行更加复杂的算法和操作,如果 基于磁盘或者 OS 缓存,性能会比较差。

fielddata 堆内存要求很高,如果数据量太大,对于 JVM 来及回收来说存在 一定的挑战,也就是对 ES 带来巨大的压力。所以 doc_value 的出现我们可以使用 磁盘存储,他同样是和 fielddata 一样的数据结构,在倒排索引基础上反向出来 的正排索引,并且是预先构建,即在建倒排索引的时候,就会创建 doc values。, 这会消耗额外的存储空间,但是对于 JVM 的内存需求就会减少。总体来看, DocValues 只是比 fielddata 慢一点,大概 10-25%,则带来了更多的稳定性。

cardlinality 聚集可以统计字段中不重复词项的数量,而 terms 聚集则可以将 这些词项全部展示出来。与 cardlinality 聚集一样, terms 聚集统计出来的词频也 不能保证完全精确。例如:

到达目的地的国家分组,去重后的国家数量。

POST /kibana_sample_data_flights/_search?filter_path=aggregations 
{
    "aggs":{
        "country_terms":{
            "terms":{#
                "field":"DestCountry",
                "size":10
            }
        },
        "country_terms_count":{
            "cardinality":{
                "field":"DestCountry"
            }
        }
    }
}
复制代码

在示例中定义了两个聚集,由于它们都是定义在 aggs 下,所以不是嵌套聚 集。terms 聚集的 field 参数定义了提取词项的字段为 DestCountry, 它的词项在 返回结果中会按词频由高到低依次展示,词频会在返回结果的 doc_count 字段中 展示。另一个参数 size 则指定了只返回 10 个词项,这相当把 DestCountry 字段 中词频前 10 名检索出来。

用于热词展示。

significant_terms 聚集

terms 聚集统计在字段中的词项及其词频,聚集结果会按各词项总的词频排 序,并讲现次数最多的词项排在最前面,这非常适合做推荐及热词类的应用。但 按词频总数不一定可能是总是正确的选择,在一些检索条件已知的情况下,一些 词频总数比较低的词项反而是更合适的推荐热词

举例来说,假设在 10000 篇技术类文章的内容中提到 Elasticsearch 有 200 篇, 占比为 2%;但在文章标题含有 NoSQL 的 1000 篇文章中,文章内容提到 Elasticsearch 的为 180 篇,占比为 18%。 这种占比显著的提升,说明在文章标题 含有 NoSQL 的条件下,Elasticsearch 变得更为重要。换句话说,如果一个词项在 某个文档子集中与在文档全集中相比发生了非常显著的变化,就说明这个词项在 这个文档子集中是更为重要的词项。

significant_terms 聚集就是针对上述情况的一种聚集查询,它将文档和词项 分为前景集 Foreground Set 和背景集(Background Set)。前景集对应一个文档子集, 面背景集则对应文档全集。significant_terms 聚集根据 query 指定前景集,运算 field 参数指定字段中的词项在前景集和背景集中的词频总数,并在结果的 doc_coumt 和 bg_coumt 中保存它们。例如:

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "query":{
        "term":{
            "OriginCountry":{
                "value":"IE"
            }
        }
    },
    "aggs":{
        "dest":{
            "significant_terms":{
                "field":"DestCountry"
            }
        }
    }
}
复制代码

在示例中,query 参数使用 DSL 指定了前景集为出发国家为 IE (即爱尔兰)的 航班,而聚集查询中则使用 significant_ terms 统计到达国家的前景集词频和背景 集词频。来看下返回结果:

image.png

在返回结果中,前景集文档数量为 119,背景集文档数量为 13059。

在 buckets 返回的所有词项中,国家编码为 GB 的航班排在第一位。它在前 景集中的词频为 12,占比约为 10% (12/119); 而在背景集中的词频为 449,占比约 为 3. 4% (445/13059)。

词项 GB 在前景集中的占比是背景集中的 3 倍左右,发生了显著变化,所以 在这个前景集中 GB 可以被视为热词而排在第一位。GB 代表的国家是英国,从爱 尔兰出发去英国的航班比较多想来也是合情合理的。

除了按示例方式使用 quey 参数指定前景集以外,还可以将 terms 聚集与 significant_terms 聚集结合起来使用,这样可以一次性列出一个字段的所有前景 集的热词。例如:

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "orgin_dest":{
            "terms":{
                "field":"OriginCountry"
            },
            "aggs":{
                "dest":{
                    "significant_terms":{
                        "field":"DestCountry"
                    }
                }
            }
        }
    }
}
复制代码

在示例中,使用 terms 聚集将 OriginCountry 字段的词项全部查询出来做前景 集,然后再与 significant_terms 聚集起查询它们的热词。

significant_text 聚集

如果参与 significant_terms 聚集的字段为 text 类型,那么需要将字段的 fielddata 机制开启,否则在执行时会返回异常信息。significant_text 聚集与 significant_terms 聚集的作用类型,但不需要开启字段的 fielddata 机制,所以可 以把它当成是种专门为 text 类型字段设计的 significant_terms 聚集。例如在 kibana_sample_data_logs 中,message 字段即为 text 类型,如果想在这个字段 上做词项分析就需要使用 significant_terms 聚集:

POST /kibana_sample_data_logs/_search?filter_path=aggregations 
{
    "query":{
        "term":{
            "response":{
                "value":"200"
            }
        }
    },
    "aggs":{
        "agent_term":{
            "significant_text":{
                "field":"message"
            }
        }
    }
}
复制代码

在示例中,前景集为响应状态码 response 为 200 的日志,significant_text 聚 集则查看在这个前景集下 message 字段中出现异常热度的词项。返回结果片段:

image.png

通过展示的返回结果可以看出,排在第一位的词项 200 在前景集和背景集中 的数量是一样的, 这说明 message 中完整地记录了 200 状态码;而排在第二位 的词项 beats 前景集和背景集分别为 3462 和 3732。这说明请求“/beats" 地址的 成功率要远高于其他地址。

significant_text 聚集之所以不需要开启fielddata机制是因为它会在检索时对 text 字段重新做分析,所以 significant_text 聚集在执行时速度比其他聚集要慢很 多。如果希望提升执行效率,则可以使用 sampler 聚集通过减少前景集的样本数 量降低运算量。

样本聚集

sampler 聚集的作用是限定其内部嵌套聚集在运算时采用的样本数量。 sampler 提取样本时会按文档检索的相似度排序,按相似度分值由高到低的顺序 提取。例如:

POST /kibana_sample_data_flights/_search?filter_path=aggregations 
{
    "query":{
        "term":{
            "OriginCountry":{
                "value":"IE"
            }
        }
    },
    "aggs":{
        "sample_data":{
            "sampler":{
                "shard_size":100
            },
            "aggs":{
                "dest_country":{
                    "significant_terms":{
                        "field":"DestCountry"
                    }
                }
            }
        }
    }
}
复制代码

在示例中共定义了 sample_ data 和 dest_country 两个聚集,其中 dest_country 是 sample_data 聚集的子聚集或嵌套聚集,因此 dest_country 在运 算时就只从分片上取一部分样本做运算。sampler 聚集的 shard_size 就是定义了 每个分片上提取样本的数量,这些样本会根据 DSL 查询结果的相似度得分由高到 低的顺序提取

执行后会发现,这次目的地最热的目的地国家由 GB 变成了 KR,这就是样本 范围缩小导致的数据失真。为了降低样本减少对结果准确性的影响,需要将些重 复的数据从样本中剔除。换句话说就是样本更加分散,加大样本数据的多样性。 Elasticsearch 提供的 diversified_sampler 聚集提供了样本多样性的能力,它提供了 field 或 script 两个参数用于去除样本中可能重复的数据。由于相同航班的票价可 能是相同

 POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "query":{
        "term":{
            "OriginCountry":{
                "value":"IE"
            }
        }
    },
    "aggs":{
        "sample_data":{
            "diversified_sampler":{
                "shard_size":100,
                "field":"AvgTicketPrice"
            },
            "aggs":{
                "dest_country":{
                    "significant_terms":{
                        "field":"DestCountry"
                    }
                }
            }
        }
    }
}

复制代码

diversified_sampler 通过 field 参数设置了 AvgTicketPrice 字段,这样在返回结 果中 GB 就又重新回到了第一位

单桶聚集

前面介绍的桶型聚集都是多桶型聚集,本节主要介绍单桶聚集 单桶聚集在返回结果中只会形成一个桶,它们都有比较特定的应用场最。在 Elasticsearch 中,单桶聚集主要包括 filter,global, missing 等几种类型。另外 还有一种 filters 聚集,它虽然属于多桶聚集, 但与 filter 聚集很接近,所以放到 一起说明

过滤器聚集

过滤器聚集通过定义一个或多个过滤器来区分桶,满足过速器条件的文档将 落入这个过滤器形成的桶中。过滤器聚集分为单桶和多桶两种,对应的聚集类型 自然就是 filter 和 filters。

本来看 filter 桶型聚集,它属于单桶型聚集。一般会同时嵌套一个指标聚集, 用于在过滤后的文档范围内计算指标,例如:

POST /kibana_sample_data_flights/_search?size=0&filter_path=aggregations 
{
    "aggs":{
        "origin_cn":{
            "filter":{
                "term":{
                    "OriginCountry":"CN"
                }
            },
            "aggs":{
                "cn_ticket_price":{
                    "avg":{
                        "field":"AvgTicketPrice"
                    }
                }
            }
        },
        "avg_price":{
            "avg":{
                "field":"AvgTicketPrice"
            }
        }
    }
}
复制代码

在示例中一共定义了 3 个聚集,最外层是两个聚集,最后聚集为嵌套聚集, origin_cn 聚集为单过滤器的桶型聚集,它将所有 OriginCountry 为 CN 的文档归入一桶

origin_cn 桶型聚集嵌套了 cn_ticket_price 指标聚集, 它的作用是计算当前 桶内文档 AvgTicketPrice 字段的平均值。另一个外层聚集 avg_price 虽然也是计 算 AghckePie 字段的平均值,但它计算的是所有文档的平均值。实际上,使用 query 与 agg 结合起来也能实现类似的功能,区别在于过滤器不会做相似度计算,所以 效率更高一些也更灵活一些。

多过滤器与单过滤器的作用类似,只是包含有多个过滤器,所以会形成多个 桶。多过滤博型聚集使用 filters 参数接收过滤条件的数组,一般也是与指标聚集 一同使用。例如使用两个过滤器计算从中国、美国出发的航班平均机票价格:

POST /kibana_sample_data_flights/_search?size=0&filter_path=aggregations
{
    "aggs":{
        "origin_cn_us":{
            "filters":{
                "filters":[
                    {
                        "term":{
                            "OriginCountry":"CN"
                        }
                    },
                    {
                        "term":{
                            "OriginCountry":"US "
                        }
                    }
                ]
            },
            "aggs":{
                "avg_ price":{
                    "avg":{
                        "field":"AvgTicketPrice"
                    }
                }
            }
        }
    }
}
复制代码

global 聚集

global 桶型聚集也是一种单桶型聚集, 它的作用是把索引中所有文档归入 一个桶中。这种桶型聚集看似没有什么价值,但当 global 桶型聚集与 query 结 合起来使用时,它不会受 query 定义的查询条件影响,最终形成的桶中仍然包含 所有文档。global 聚集在使用上非常简单,没有任何参数,例如:

POST /kibana_sample_data_flights/_search?size=0&filter_path=aggregations 
{
    "query":{
        "term":{
            "Carrier":{
                "value":"Kibana Airlines"
            }
        }
    },
    "aggs":{
        "kibana_avg_delay":{
            "avg":{
                "field":"FlightDelayMin"
            }
        },
        "all flights":{
            "global":{

            },
            "aggs":{
                "all_avg_delay":{
                    "avg":{
                        "field":"FlightDelayMin"
                    }
                }
            }
        }
    }
}
复制代码

在示例中 query 使用 term 查询将航空公司为“Kibana Airline"的文档都检索 出来,而 kibana _avg delay 定义的平均值聚集会将它们延误时间的平均值计算出 来。但另个 all_fights 聚集由于使用了 global 聚集所以在嵌套的 all_avg_delay 聚 集中计算出来的是所有航班廷误时间z的平均值。

missing 聚集

missing 聚集同样也是一种单桶型聚集,它的作用是将某一字段缺失的文档 归入一桶。 missing 聚集使用 field 参数定义要检查缺失的字段名称,例如:

POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs":{
        "no_price":{
            "missing":{
                "field":"AvgTicketPrice"
            }
        }
    }
} 
复制代码

示例将 kibana_sample_data_flights 中缺失 AvgTicketPrice 字段的文档归入一 桶,通过返回结果的 doc_count 查询数量也可以与指标聚集做嵌套,计算这些文 档的某一指标值。

聚集组合

有两种比较特殊的多桶型聚集,它们是 composite 聚集和 adjacency_matrix 聚集。这两种聚集是以组合不同条件的形式形成新桶,只是在组合的方法和组件 的条件上存在着明显差异。

composite 聚集可以将不同类型的聚集组合到一一起,它会从不同的聚集中 提取数据,并以笛卡尔乘积的形式组合它们,而每一个组合就会形成一个新桶。 例如想查看平均票价与机场天气的对应关系,可以这样:

POST /kibana_sample_data_flights/_search?filter_path=aggregations 
{
    "aggs":{
        "price_weather":{
            "composite":{
                "sources":[
                    {
                        "avg_price":{
                            "histogram":{
                                "field":"AvgTicketPrice",
                                "interval":500
                            }
                        }
                    },
                    {
                        "weather":{
                            "terms":{
                                "field":"OriginWeather"
                            }
                        }
                    }
                ]
            }
        }
    }
}

复制代码

在示例中,composite 聚集中通过 soures 参数定义了两个需要组合的子聚集。 第一个聚集 avg_price 是一个针对 AvgTicketPrice 以 500 为间隔的 histogam 聚集, 第二个则聚集 weather 则一个针对 OriginWeather 的 terms 聚集。sources 参数 中还可以定义更多的聚集,它们会以笛卡儿乘积的形式组合起来。

它包含自前聚集结果中最后一个结果的 key。所以请求下一页聚集结果就可 以通过 after 和 size 参数值定,例如:

image.png adjacency_matrix 又叫邻接矩阵,是图论中的概念,描述顶点之间的相邻关 系,adjacency_matrix 聚集因为牵涉到这些概念,略过,感兴趣的同学可以自行 研究。

POST /kibana_sample_data_flights/_search?filter_path=aggregations 
{
    "aggs":{
        "price_weather":{
            "composite":{
                "after":{
                    "avg_price":500,
                    "weather":"Cloudy"
                },
                "sources":[
                    {
                        "avg_price":{
                            "histogram":{
                                "field":"AvgTicketPrice",
                                "interval":500
                            }
                        }
                    },
                    {
                        "weather":{
                            "terms":{
                                "field":"OriginWeather"
                            }
                        }
                    }
                ]
            }
        }
    }
}
复制代码

管道聚集

管道聚集不是直接从索引中读取文档,而是在其他聚集的基础上再进行聚集 运算。所以管道聚集可以理解为是在聚集结果上再次做聚集运算,比如求聚集结 果中多个桶中某一指标的平均值、最大值等。要实现这样的目的,管道聚集都会 包含一个名为 buckets_path 的参数,用于指定访问其他桶中指标值的路径。 buckets_ path 参数的值由三部分组成,即聚集名称、指标名称和分隔符。

聚集名称与聚集名称之间的分隔符是“>”,而聚集名称与指标名称之间的 分隔符使用“.”。

按管道聚集运算来源分类,管道聚集可以分为基于父聚集结果和基于兄弟聚 集结果两类。前者使用父聚集的结果并将运算结果添加到父聚集结果中,后者则 使用兄弟聚集的结果并且结果会展示在自己的聚集结果中

基于兄弟聚集 其于兄弟聚集的管道聚集包括 avg_bucket、max_ bucket、min_bucket、sum bucket、 stats_bucket、extended stats_ bucket、 percentiles_bucket 七种。如 果将它们名称中的 bucket 去除,它们就与本章前面介绍的部分指标聚集同名了。 事实上,它们不仅在名称上接近,而且在功能上也类似,只是聚集运算的范围由 整个文档变成了另一个聚集结果。以 avg_bucket 为例, 它的作用是计算兄弟聚 集结果中某一指标的平均值:

POST /kibana_sample_data_flights/_search?filter_path=aggregations 
{
  "aggs": {
    "carriers": {
      "terms": {
        "field": "Carrier",
        "size": 10
      },
      "aggs": {
        "carrier_stat": {
          "stats": {
            "field": "AvgTicketPrice"
          }
        }
      }
    },
    "all_stat": {
      "avg_bucket": {
        "buckets_path": "carriers>carrier_stat.avg"
      }
    }
  }
}    
复制代码

例中,最外层包含有两个名称分别为 carriers 和 all_stat 的聚集,这两个聚 集就是兄弟关系。 carries 聚集是一个针对 Carrier 字段的 terms 聚集,Carrier 字段保存的是航 班承运航空公司,所以这个聚集的作用是按航空公司将航班分桶。在这个聚集中 嵌套了一个名为 carrier_stat 的聚集,它是一个针对 AvgTicketPrice 字段的 stats 聚集,会按桶计算票价的最小值、平均值等统计数据。

all_ stat 聚集则是一个 avg_bucket 管道聚集,在它的 Path 参数中指定了运 算平均值的路径" carriers>carrier_stat.avg ",即从兄弟聚集中查找 carrier_stat 指 标聚集, 然后再用其中的 avg 字段参与平均值计算。 所以 all_ stat 这计算出来 的是四个航空公司平均票价的平均值,实际上就是所有航班的平均票价。

尽管示例针对 avg_bucket 管道聚集的检索,但使用其余六种基于兄弟的管 道聚集的关键字值替换后,它们就变成了另一种合法的管道聚集了。

基于父聚集

基于父聚集的管道聚集包括 moving_avg、moving_fn、bucket_script、 bucket_selector、bucket_sort、derivative、cumulative_sum、serial_diff 八种。

滑动窗口

moving_avg 和 moving_fn 这两种管道聚集的运算机制相同,都是基于滑动 窗口( Siding Window)算法对父聚集的结果做新的聚集运算。滑动窗口算法使用一 个具有固定宽度的窗口滑过一组数据, 在滑动的过程中对落在窗口内的数据做 运算。moving_avg 管道聚集是对落在窗口内的父聚集结果做平均值运算,而 moving_fn 管道聚集则可以对落在窗口内的父聚集结果做各种自定义的运算。由 于 moving_avg 管道可以使用 moving_fn 管道聚集实现,所以 moving_avg 在 Elaticearch 版本 6.4.0 中已经被废止。

由于使用滑动窗口运算时每次移动 1 个位置, 这就要求 moving_avg 和 moving_fn 所在聚集桶与桶间隔必须固定,所以这两种管道聚集只能在 histogam 和 date_histogam 聚集中使用:

POST /kibana_sample_data_flights/_search?filter_path=aggregations 
{
  "aggs": {
    "day_price": {
      "date_histogram": {
        "field": "timestamp",
        "interval": "day"
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "AvgTicketPrice"
          }
        },
        "smooth_price": {
          "moving_fn": {
            "buckets_path": "avg_price",
            "window": 10,
            "script": "MovingFunctions.unweightedAvg(values)"
          }
        }
      }
    }
  }
}
复制代码

在示例中,最外层的父聚集 day_price 是 1 个 date_histogam 桶型聚集,它 根据文档的 timestamp 字段按天将文档分桶。day_price 聚集包含 avg_price 和 smooth_price 两个子聚集,其中 avg_price 聚集是一个求 AvgTicketPrice 字段在 1 个桶内平均值的 avg 聚集,而 smooth_price 则是一个使用滑动窗口做平均值平 滑的管道聚集,窗口宽度由参数 window 设置为 10,默认值为 5。

通过返回结果比较 avg_price 与 smooth_price 就会发现,后者由于经过了滑 动窗口运算,数据变化要平滑得多。

moving _fn 聚集包含一个用于指定运算脚本的 script 参数,在脚本中可以通 过 values 访问 buckets_path 参数指定的指标值。moving _fn 还内置了一个 MovingFunctions 类,包括多个运算函数:

max()
min( )
sum( )
stdDev( ) 标准偏差
unweightedAvg( ) 无加权平均值
linearWeightedAvg( ) 线性加权移动平均值
ewma( ) 指数加权移动平均值
holt( ) 二次指数加权移动平均值
holtWinters( ) 三次指数加权移动平均值
复制代码
分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改