Elasticsearch聚集统计

目录

1、聚集统计简介

2、度量聚集

2.1、平均值聚集

2.2、最大值和最小值聚集

2.3、求和聚集

2.4、统计聚集

2.5、基数聚集

2.6、百分比等级聚集

2.7、头部命中聚集

2.8、矩阵统计聚集

3、桶聚集

3.1、词条聚集

3.2、范围聚集

3.3、日期范围聚集

3.4、直方图聚集

3.5、日期直方图聚集

3.6、缺失聚集

3.7、过滤器聚集

3.8、多过滤聚集

4、管道聚集

4.1、平均桶聚集

4.2、求和桶聚集

4.3、最大桶和最小桶聚集

4.4、累计求和桶聚集

4.5、差值聚集

5、使用fielddata聚集text字段

5.1、什么是doc value值

5.2、使用fielddata聚集text字段

6、全局有序编号

6.1、使用全局有序编号加快聚集速度

6.2、全局有序编号生成时机

7、聚集请求添加后过滤器

8、小结


1、聚集统计简介

     Elasticsearch不仅是一个大数据搜索引擎,也是一个大数据分析引擎。它的聚集(aggregation)统计的REST端点可用于实现与统计分析有关的功能。

        Elasticsearch提供的聚集分为三大类。

        (1) 度量聚集(Metric aggregation):度量聚集可以用于计算搜索结果在某个字段上的数量统计指标,比如平均值、最大值、最小值、总和等。

        (2) 桶聚集(Bucket aggregation):桶聚集可以在某个字段上划定一些区间,每个区间是一个“桶”,然后按照搜索结果的文档内容把文档归类到它所属的桶中,统计的结果能明确每个桶中有多少文档。桶聚集还可以嵌套其他的桶聚集或者度量聚集来进行一些复杂的指标计算。

        (3) 管道聚集(Pipeline aggregation):管道聚集就是把桶聚集统计的结果作为输入来继续做聚集统计,会在桶聚集的结果中追加一些额外的统计数据。

        测试数据准备:

PUT user
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "userid":{
        "type": "long"
      },
      "username":{
        "type": "text",
        "fields": {
          "keyword":{
            "type":"keyword",
            "ignore_above":256
          }
        }
      },
      "age":{
        "type": "integer"
      },
      "sex":{
        "type": "boolean"
      },
      "born":{
        "type": "date",
        "format": ["yyyy-MM-dd HH:mm:ss"]
      }
    }
  }
}


POST user/_bulk
{"index":{"_id":"1"}}
{"userid":"1","username":"张三","age":"18","sex":"true","born":"2000-01-26 19:00:00"}
{"index":{"_id":"2"}}
{"userid":"2","username":"李四","age":"28","sex":"false","born":"2000-02-26 19:00:00"}
{"index":{"_id":"3"}}
{"userid":"3","username":"王五","age":"48","sex":"true","born":"2000-04-26 19:00:00"}
{"index":{"_id":"4"}}
{"userid":"4","username":"马六","age":"9","sex":"false","born":"2000-05-26 19:00:00"}
{"index":{"_id":"5"}}
{"userid":"5","username":"李大头","age":"26","sex":"true","born":"2000-07-26 19:00:00"}
{"index":{"_id":"6"}}
{"userid":"6","username":"李二狗","age":"88","sex":"true","born":"2000-08-26 19:00:00"}
{"index":{"_id":"7"}}
{"userid":"7","username":"赵四","age":"18","sex":"true","born":"2000-09-26 19:00:00","address":"北京"}
{"index":{"_id":"8"}}
{"userid":"8","username":"宋小宝","age":"28","sex":"false","born":"2000-10-26 19:00:00","address":"上海"}

2、度量聚集

        度量聚集可以用于计算搜索结果在某个字段上的数量统计指标,比如平均值、最大值、最小值、总和等,有些度量聚集只返回一个结果,有的则可以一次性返回多个指标结果。

2.1、平均值聚集

         平均值聚集用来计算索引中某个数值字段的平均值,对索引user的字段age求平均值的聚集请求如下

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age_avg": {
      "avg": {
        "field": "age",
        "missing": 0
      }
    }
  }
}

        在这个请求中,aggs的参数使用了一个类型为avg的聚集,它会对age字段求平均值,请求中的missing参数表示如果遇到age字段为null的文档,则当作0计算。这一聚集被命名为age_avg,可以在响应的结果中使用这个名称得到以下统计的结果

  "aggregations" : {
    "age_avg" : {
      "value" : 32.875
    }
  }

        可以看到,聚集统计的结果在aggregations中,平均值为32.875

        聚集统计的对象是搜索结果,如果请求搜索了全部的数据,则是对全部的数据求平均值,实际中你可以按照业务逻辑的需要搜索出一部分文档做聚集统计。另外,对于不关心搜索结果的请求,可以把size参数设置为0,这样可以提高统计的响应速度。

2.2、最大值和最小值聚集

        使用最大值和最小值聚集可以快速地得到搜索结果中某个数值字段的最大值、最小值,例如,获取age字段的最大值的请求如下

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age_max": {
      "max": {
        "field": "age",
        "missing": 0
      }
    }
  }
}

         此时把聚集名称设置为age_max,聚集类型为max,得到了以下结果

  "aggregations" : {
    "age_max" : {
      "value" : 88.0
    }
  }

        同理,如果要得到age字段的最小值,把聚集类型设置为min即可

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age_min": {
      "min": {
        "field": "age",
        "missing": 0
      }
    }
  }
}

        此时把聚集名称设置为age_min,聚集类型为min,得到了以下结果

  "aggregations" : {
    "age_min" : {
      "value" : 9.0
    }
  }

2.3、求和聚集

        与平均值聚集类似,求和聚集可以让搜索结果在某个数值字段上求和

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age_sum": {
      "sum": {
        "field": "age",
        "missing": 0
      }
    }
  }
}

        统计结果依然可以将聚集名称age_sum作为key值取出。

  "aggregations" : {
    "age_sum" : {
      "value" : 263.0
    }
  }

2.4、统计聚集

        统计聚集可以一次性返回搜索结果在某个数值字段上的最大值、最小值、平均值、个数、总和

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age_stats": {
      "stats": {
        "field": "age",
        "missing": 0
      }
    }
  }
}

        使用上述代码可以得到age字段的各种统计数据

  "aggregations" : {
    "age_stats" : {
      "count" : 8,
      "min" : 9.0,
      "max" : 88.0,
      "avg" : 32.875,
      "sum" : 263.0
    }
  }

2.5、基数聚集

        基数聚集用于近似地计算搜索结果在某个字段上有多少个基数——也就是有多少种不同的数据。这个值在基数很大(例如40000以上)的时候可能不准确,精准度可以通过阈值参数precision_threshold进行控制。

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age_cardinality": {
      "cardinality": {
        "field": "age",
        "precision_threshold": 100
      }
    }
  }
}

        以上这个请求会计算age字段的基数,precision_threshold设置为100表示如果搜索结果的基数大于100,统计结果可能存在误差。调大这个阈值有利于提高统计的精准度,其默认值是3000,最大可以设置为40000。这个值配置得越大就越消耗内存,所以实际中需要在精准度和内存消耗中进行权衡,结果如下

  "aggregations" : {
    "age_cardinality" : {
      "value" : 6
    }
  }

2.6、百分比聚集

        百分比聚集用于近似地查看搜索结果中某个字段的百分比分布数据,你可以根据搜索结果清晰地看出某个值以内的数据在整体数据集中的占比。例如,对user的age字段做百分比聚集的请求如下

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age_percent": {
      "percentiles": {
        "field": "age",
        "percents": [
          1,
          5,
          25,
          50,
          75,
          95,
          99
        ]
      }
    }
  }
}

        得到的结果是一组数据

  "aggregations" : {
    "age_percent" : {
      "values" : {
        "1.0" : 9.0,
        "5.0" : 9.0,
        "25.0" : 18.0,
        "50.0" : 27.0,
        "75.0" : 38.0,
        "95.0" : 88.0,
        "99.0" : 88.0
      }
    }
  }

        这组数据的意思是,有25%的文档age值不超过18.0,有50%的文档age值不超过27.0,有95%的文档age值不超过88.0等。当然,如果你只关心25%和75%的文档的age值分布,你可以自定义区间进行传入。

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age_percent": {
      "percentiles": {
        "field": "age",
        "percents": [25,75]
      }
    }
  }
}

         这里只传入了25和75作为百分比计算的区间值,也就只得到这两个值对应的百分比分布数据。结果如下。

  "aggregations" : {
    "age_percent" : {
      "values" : {
        "25.0" : 18.0,
        "75.0" : 38.0
      }
    }
  }

        这个结果表明,有25%的文档age值大约不超过18.0;有75%的文档age值大约不超过38.0。由于这个聚集统计的结果是近似值,每次请求时查询的结果可能有细微的区别。

2.6、百分比等级聚集

        百分比等级聚集跟百分比聚集的参数恰好相反,传入一组值,就可以看到这个值以内的数据占整体数据的百分比。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age_percentile": {
      "percentile_ranks": {
        "field": "age",
        "values": [
          18,
          45
        ]
      }
    }
  }
}

        在这个聚集请求中,把聚集类型设置为percentile_ranks表示发起百分比等级聚集,values用来设置需要查看的rank值,可以得到以下结果。

  "aggregations" : {
    "age_percentile" : {
      "values" : {
        "18.0" : 25.0,
        "45.0" : 77.91666666666667
      }
    }
  }

        这个结果表明,有25.0%的文档rank值小于等于18,有77.91%的文档rank值小于等于45。

2.7、头部命中聚集

        头部命中聚集常作为桶聚集的子聚集来使用,你可以先使用桶聚集将文档分发到一系列的桶中,然后使用头部命中聚集展示每个桶中的前几条记录,这样可以得到每个分组的top-N的效果。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "groupbytime": {
      "terms": {
        "field": "born",
        "size": 10
      },
      "aggs": {
        "top-N": {
          "top_hits": {
            "size": 2,
            "_source": {
              "includes": ["username","age","born"]
            },
            "sort": ["age"]
          }
        }
      }
    }
  }
}

        上面的请求首先创建了一个terms聚集,它会按照born分组统计出每个时间点的文档数。然后嵌套了一个子聚集top_hits,取名为top-N,它会展示每个时间点排名前2个的文档,文档按照age字段排序,且每个文档只返回username、age、born三个字段。部分聚集统计的结果如下

"aggregations" : {
    "groupbytime" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : 948913200000,
          "key_as_string" : "2000-01-26 19:00:00",
          "doc_count" : 1,
          "top-N" : {
            "hits" : {
              "total" : {
                "value" : 1,
                "relation" : "eq"
              },
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "user",
                  "_type" : "_doc",
                  "_id" : "1",
                  "_score" : null,
                  "_source" : {
                    "born" : "2000-01-26 19:00:00",
                    "age" : "18",
                    "username" : "张三"
                  },
                  "sort" : [
                    18
                  ]
                }
              ]
            }
          }
        },
        {
          "key" : 951591600000,
          "key_as_string" : "2000-02-26 19:00:00",
          "doc_count" : 1,
          "top-N" : {
            "hits" : {
              "total" : {
                "value" : 1,
                "relation" : "eq"
              },
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "user",
                  "_type" : "_doc",
                  "_id" : "2",
                  "_score" : null,
                  "_source" : {
                    "born" : "2000-02-26 19:00:00",
                    "age" : "28",
                    "username" : "李四"
                  },
                  "sort" : [
                    28
                  ]
                }
              ]
            }
          }
        },
        {
          "key" : 956775600000,
          "key_as_string" : "2000-04-26 19:00:00",
          "doc_count" : 1,
          "top-N" : {
            "hits" : {
              "total" : {
                "value" : 1,
                "relation" : "eq"
              },
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "user",
                  "_type" : "_doc",
                  "_id" : "3",
                  "_score" : null,
                  "_source" : {
                    "born" : "2000-04-26 19:00:00",
                    "age" : "48",
                    "username" : "王五"
                  },
                  "sort" : [
                    48
                  ]
                }
              ]
            }
          }
        },
        {
          "key" : 959367600000,
          "key_as_string" : "2000-05-26 19:00:00",
          "doc_count" : 1,
          "top-N" : {
            "hits" : {
              "total" : {
                "value" : 1,
                "relation" : "eq"
              },
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "user",
                  "_type" : "_doc",
                  "_id" : "4",
                  "_score" : null,
                  "_source" : {
                    "born" : "2000-05-26 19:00:00",
                    "age" : "9",
                    "username" : "马六"
                  },
                  "sort" : [
                    9
                  ]
                }
              ]
            }
          }
        },
        {
          "key" : 964638000000,
          "key_as_string" : "2000-07-26 19:00:00",
          "doc_count" : 1,
          "top-N" : {
            "hits" : {
              "total" : {
                "value" : 1,
                "relation" : "eq"
              },
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "user",
                  "_type" : "_doc",
                  "_id" : "5",
                  "_score" : null,
                  "_source" : {
                    "born" : "2000-07-26 19:00:00",
                    "age" : "26",
                    "username" : "李大头"
                  },
                  "sort" : [
                    26
                  ]
                }
              ]
            }
          }
        },
        {
          "key" : 967316400000,
          "key_as_string" : "2000-08-26 19:00:00",
          "doc_count" : 1,
          "top-N" : {
            "hits" : {
              "total" : {
                "value" : 1,
                "relation" : "eq"
              },
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "user",
                  "_type" : "_doc",
                  "_id" : "6",
                  "_score" : null,
                  "_source" : {
                    "born" : "2000-08-26 19:00:00",
                    "age" : "88",
                    "username" : "李二狗"
                  },
                  "sort" : [
                    88
                  ]
                }
              ]
            }
          }
        },
        {
          "key" : 969994800000,
          "key_as_string" : "2000-09-26 19:00:00",
          "doc_count" : 1,
          "top-N" : {
            "hits" : {
              "total" : {
                "value" : 1,
                "relation" : "eq"
              },
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "user",
                  "_type" : "_doc",
                  "_id" : "7",
                  "_score" : null,
                  "_source" : {
                    "born" : "2000-09-26 19:00:00",
                    "age" : "18",
                    "username" : "赵四"
                  },
                  "sort" : [
                    18
                  ]
                }
              ]
            }
          }
        },
        {
          "key" : 972586800000,
          "key_as_string" : "2000-10-26 19:00:00",
          "doc_count" : 1,
          "top-N" : {
            "hits" : {
              "total" : {
                "value" : 1,
                "relation" : "eq"
              },
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "user",
                  "_type" : "_doc",
                  "_id" : "8",
                  "_score" : null,
                  "_source" : {
                    "born" : "2000-10-26 19:00:00",
                    "age" : "28",
                    "username" : "宋小宝"
                  },
                  "sort" : [
                    28
                  ]
                }
              ]
            }
          }
        }
      ]
    }
  }

2.8、矩阵统计聚集

        矩阵统计聚集会根据你指定的索引的一到多个数值字段计算出一组数理统计学方面的指标,包括总数、平均值、方差、偏度、峰度、协方差、相关系数等。向索引user发起一个矩阵统计聚集请求,如下所示

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "matrix": {
      "matrix_stats": {
        "fields": ["userid","age"]
      }
    }
  }
}

        该请求将聚集类型设置为matrix_stats,fields设置需要统计的字段列表为userid和age,要求字段为数值型字段。在以下结果中,每个字段的数理统计指标会被分别展示出来。

  "aggregations" : {
    "matrix" : {
      "doc_count" : 8,
      "fields" : [
        {
          "name" : "userid",
          "count" : 8,
          "mean" : 4.5,
          "variance" : 6.0,
          "skewness" : -1.1075224051434234E-16,
          "kurtosis" : 1.7619047619047616,
          "covariance" : {
            "userid" : 6.0,
            "age" : 11.214285714285712
          },
          "correlation" : {
            "userid" : 1.0,
            "age" : 0.1831311350429777
          }
        },
        {
          "name" : "age",
          "count" : 8,
          "mean" : 32.875,
          "variance" : 624.9821428571428,
          "skewness" : 1.4683671757043375,
          "kurtosis" : 4.059714322402331,
          "covariance" : {
            "userid" : 11.214285714285712,
            "age" : 624.9821428571428
          },
          "correlation" : {
            "userid" : 0.1831311350429777,
            "age" : 1.0
          }
        }
      ]
    }
  }

3、桶聚集

        桶聚集会按照某个字段划分出一些区间,把搜索结果的每个文档按照字段所在的区间划分到桶中,桶聚集会返回每个桶拥有的文档数目。桶的数目既可以用参数确定,也可以在执行过程中按照数据内容动态生成。桶的默认上限数目是65535,返回的桶数目超过这个数目会报错。另外,桶聚集可以嵌套其他的聚集来得到一些复杂的统计结果,度量聚集是不能嵌套其他子聚集的。

3.1、词条聚集

        词条聚集是常用的桶聚集,它的功能类似于SQL中的group by做分组统计的功能。它会根据指定的字段内容给每种值生成一个桶,并返回每种值对应的文档数量。下面来创建一个词条聚集请求,把聚集类型设置为terms,选择聚集的字段为age,它会返回每个age的文档数目。

POST user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "group_by_age": {
      "terms": {
        "field": "age",
        "size": 3, 
        "order": {
          "_count": "desc"
        }
      }
    }
  }
}

        在这个请求中,将size设置为3表明只需要返回前3个桶,在order参数中配置了按照文档数目(_count)降序(desc)排列。结果如下。

  "aggregations" : {
    "group_by_age" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 3,
      "buckets" : [
        {
          "key" : 18,
          "doc_count" : 2
        },
        {
          "key" : 28,
          "doc_count" : 2
        },
        {
          "key" : 9,
          "doc_count" : 1
        }
      ]
    }
  }

        统计结果返回了文档数最多的3个桶,如果不指定size的大小,则会返回6个桶。doc_count_error_upper_bound的值代表统计错误的边界,这个值大于0时,代表统计结果存在遗漏的词条没有返回,如果要做到统计准确,则需要增大size的值到足够大而不是只返回6个桶。结果参数sum_other_doc_count表示除了返回的这3个桶的数据,还有多少条数据没在桶中展示。另外,词条聚集还支持按照桶的值排序,只需要把order参数中的_count改为_key即可。

        注意:为什么size值较小时统计出的文档数量可能不准确。当把size设置为3时,该请求会到每个分片上取出每个词条前3名的统计值并返回然后汇总合并得到最后结果,由于是根据每个分片的局部结果进行合并,可能导致最后统计出的词条总数不对;也可能词条总数是对的,但是数量计算得不对。所以,要确保得到精确的结果,就应该把size设置成大于等于该字段的基数,但这样做需要更多的性能开销。如果觉得size太大时返回的桶太多,也可以在size参数后面添加一个值比较大的请求参数shard_size,它表示去每个分片取出的词条数,这样既不会影响返回的桶数目,还可以提高计算精度。
        如果想让每个桶返回被遗漏计算的最大错误数,可以在请求中添加参数show_term_doc_count_error

POST user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "group_by_age": {
      "terms": {
        "field": "age",
        "size": 3, 
        "order": {
          "_count": "desc"
        },
        "show_term_doc_count_error": true
      }
    }
  }
}

        在得到的结果中,可以从每个桶的结果中找到计算错误的边界。

  "aggregations" : {
    "group_by_age" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 3,
      "buckets" : [
        {
          "key" : 18,
          "doc_count" : 2,
          "doc_count_error_upper_bound" : 0
        },
        {
          "key" : 28,
          "doc_count" : 2,
          "doc_count_error_upper_bound" : 0
        },
        {
          "key" : 9,
          "doc_count" : 1,
          "doc_count_error_upper_bound" : 0
        }
      ]
    }
  }

        当发现doc_count_error_upper_bound的值大于0时,就有必要增大size或者shard_size参数的值来提高统计的精度。如果想在统计结果中去掉文档数少于某个值的结果,可以配置参数min_doc_count,下面的请求只返回文档数目大于等于2的桶。

POST user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "group_by_age": {
      "terms": {
        "field": "age",
        "size": 3, 
        "order": {
          "_count": "desc"
        },
        "show_term_doc_count_error": true,
        "min_doc_count": 2
      }
    }
  }
}

          词条聚集不但可以按照文档数或桶名称排序,还可以按照子聚集的统计结果排序。

POST user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "group_by_age": {
      "terms": {
        "field": "age",
        "size": 3, 
        "order": {
          "_count": "desc"
        },
        "show_term_doc_count_error": true,
        "min_doc_count": 2
      },
      "aggs": {
        "sum_age": {
          "sum": {
            "field": "age",
            "missing": 0
          }
        }
      }
    }
  }
}

        此时把排序的字段设置为子聚集sum_age的名称,表示按照age字段的总和升序排列返回的桶。

        在做聚集统计时,所选择的字段一般是data、keyword或者数值字段,对于要进行文本分析的text字段需要开启fielddata功能才能进行聚集统计。

3.2、范围聚集

        范围聚集需要你提供一组左闭右开的区间,在返回的结果中会得到搜索结果的某个字段落在每个区间的文档数目,参数from用于提供区间下界,to用于提供区间上界。统计的字段既可以是数值类型的字段也可以是日期类型的字段。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "rang_age": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "to": 20
          },
          {
           "from": 20,
           "to": 50
          },
          {
            "from": 50,
            "to": 100
          }
        ]
      }
    }
  }
}

        此时的聚集类型为range,field设置了统计的字段为age,ranges提供区间内容,得到的结果如下。

  "aggregations" : {
    "rang_age" : {
      "buckets" : [
        {
          "key" : "*-20.0",
          "to" : 20.0,
          "doc_count" : 3
        },
        {
          "key" : "20.0-50.0",
          "from" : 20.0,
          "to" : 50.0,
          "doc_count" : 4
        },
        {
          "key" : "50.0-100.0",
          "from" : 50.0,
          "to" : 100.0,
          "doc_count" : 1
        }
      ]
    }
  }

        这个结果表示,有3个文档的age值小于20,有4个文档的age值大于等于20小于50,rank值大于等于50的文档有1个。

3.3、日期范围聚集

        日期范围聚集是一种特殊的范围聚集,它要求统计的字段类型必须是日期类型,下面的代码会实现在这个born字段上做日期范围聚集。

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "range_born": {
      "date_range": {
        "field": "born",
        "ranges": [
          {
            "from": "1900-01-01 00:00:00",
            "to": "2000-01-01 00:00:00"
          },
          {
            "from": "2000-01-01 00:00:00",
            "to": "3000-01-01 00:00:00"
          }
        ]
      }
    }
  }
}

        这个请求提供了两个UTC时间的区间,这两个区间也是左闭右开的。统计时,请求会把born字段属于各个区间内的文档放入桶进行统计。该请求可以得到以下统计结果。

  "aggregations" : {
    "range_born" : {
      "buckets" : [
        {
          "key" : "1900-01-01 00:00:00-2000-01-01 00:00:00",
          "from" : -2.2089888E12,
          "from_as_string" : "1900-01-01 00:00:00",
          "to" : 9.466848E11,
          "to_as_string" : "2000-01-01 00:00:00",
          "doc_count" : 0
        },
        {
          "key" : "2000-01-01 00:00:00-3000-01-01 00:00:00",
          "from" : 9.466848E11,
          "from_as_string" : "2000-01-01 00:00:00",
          "to" : 3.250368E13,
          "to_as_string" : "3000-01-01 00:00:00",
          "doc_count" : 8
        }
      ]
    }
  }

         如果你觉得传入UTC时间不习惯,可以设置时区并传入北京时间来作为区间边界,以下请求跟前面的请求是等价的。

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "range_born": {
      "date_range": {
        "field": "born",
        "time_zone":"+08:00",
        "ranges": [
          {
            "from": "1900-01-01 08:00:00",
            "to": "2000-01-01 08:00:00"
          },
          {
            "from": "2000-01-01 08:00:00",
            "to": "3000-01-01 08:00:00"
          }
        ]
      }
    }
  }
}

        如果建索引时born字段写入的数据已经是北京时间,那么ranges的时间区间可以直接传入北京时间而不需要设置时区,这其实也是一种简化的时区处理方法。但是你需要理解,即使你把一个不带时区的北京时间字符串写进born字段,Elasticsearch在后台还是会把它作为UTC时间的时间戳来保存,在born字段上进行一次排序就能看到born字段存储的时间戳。

3.4、直方图聚集

        直方图聚集经常用于做一些柱状图和折线图的展示,它可以选择一个数值或日期字段,然后根据字段的最小值和区间步长生成一组区间,统计出每个区间的文档数目。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "histogram_age": {
      "histogram": {
        "field": "age",
        "interval": 20
      }
    }
  }
}

        这个请求设置了聚集类型为histogram,会统计age字段的直方图分布数据,还设置了区间步长为10,返回的结果如下

  "aggregations" : {
    "histogram_age" : {
      "buckets" : [
        {
          "key" : 0.0,
          "doc_count" : 3
        },
        {
          "key" : 20.0,
          "doc_count" : 3
        },
        {
          "key" : 40.0,
          "doc_count" : 1
        },
        {
          "key" : 60.0,
          "doc_count" : 0
        },
        {
          "key" : 80.0,
          "doc_count" : 1
        }
      ]
    }
  }

        这个结果返回了5个左闭右开的区间,如果你想把区间的上界扩展到10,可以使用extended_bounds参数扩大区间的边界来强制返回空桶,代码如下

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "histogram_age": {
      "histogram": {
        "field": "age",
        "interval": 20,
        "extended_bounds": {
          "min": 0,
          "max": 200
        }
      }
    }
  }
}

        上面的请求使用extended_bounds中的max设置区间的上限,使用min设置区间的下限,可以得到扩展边界中的空桶,因此在结果中比前面多返回了5个桶

  "aggregations" : {
    "histogram_age" : {
      "buckets" : [
        {
          "key" : 0.0,
          "doc_count" : 3
        },
        {
          "key" : 20.0,
          "doc_count" : 3
        },
        {
          "key" : 40.0,
          "doc_count" : 1
        },
        {
          "key" : 60.0,
          "doc_count" : 0
        },
        {
          "key" : 80.0,
          "doc_count" : 1
        },
        {
          "key" : 100.0,
          "doc_count" : 0
        },
        {
          "key" : 120.0,
          "doc_count" : 0
        },
        {
          "key" : 140.0,
          "doc_count" : 0
        },
        {
          "key" : 160.0,
          "doc_count" : 0
        },
        {
          "key" : 180.0,
          "doc_count" : 0
        },
        {
          "key" : 200.0,
          "doc_count" : 0
        }
      ]
    }
  }

3.5、日期直方图聚集

        日期直方图聚集的功能和直方图聚集的基本一样,但它只能在日期字段上做聚集,常用于做时间维度的统计分析。使用它可以非常方便地设置时间间隔,既可以是固定长度的时间间隔,也可以是日历时间间隔,例如你想了解每个月文档的数量分布,由于大小月的关系,应该把时间间隔设置为month,而不是固定为30天。

        参数calendar_interval用于设置日历时间间隔,可选的配置表格所示。

日历时间间隔的配置
配置说明
minute每个时间区间长度为1分钟,每个区间从秒开始到下一分钟的0秒结束
hour每个时间区间长度为1小时,每个区间从0分0秒开始到下一小时的0分0秒结束
day每个时间区间长度为1天,每个区间从0点开始到下一天的0点结束
week每个区间间隔是1周,每个区间从所在周那一天的0点开始到下一周的同一天的0点结束
month每个区间间隔是1个月,每个区间从1号的0点开始到下个月1号的0点结束
quarter每个区间间隔是1个季度,每个区间从所在月的1号0点开始到3个月后的1号0点结束
year每个区间间隔是1年,每个区间从所在年份的1月1日0点开始到下一年的1月1日0点结束

        下面来实现日期直方图聚集,只需要传入时间间隔就可以切分区间,不用像范围聚集那样需传入每个区间的边界。

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "histogram_born": {
      "date_histogram": {
        "field": "born",
        "interval": "month", 
        "time_zone": "+08:00"
      }
    }
  }
}

        在这个请求中,由于born字段保存了UTC时间,为了得到北京时间的统计结果,参数time_zone设置了时区为+08:00,时间间隔为一月,得到的结果如下

  "aggregations" : {
    "histogram_born" : {
      "buckets" : [
        {
          "key_as_string" : "2000-01-01 00:00:00",
          "key" : 946656000000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2000-02-01 00:00:00",
          "key" : 949334400000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2000-03-01 00:00:00",
          "key" : 951840000000,
          "doc_count" : 0
        },
        {
          "key_as_string" : "2000-04-01 00:00:00",
          "key" : 954518400000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2000-05-01 00:00:00",
          "key" : 957110400000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2000-06-01 00:00:00",
          "key" : 959788800000,
          "doc_count" : 0
        },
        {
          "key_as_string" : "2000-07-01 00:00:00",
          "key" : 962380800000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2000-08-01 00:00:00",
          "key" : 965059200000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2000-09-01 00:00:00",
          "key" : 967737600000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2000-10-01 00:00:00",
          "key" : 970329600000,
          "doc_count" : 1
        }
      ]
    }
  }

        如果想得到2月到8月的结果,可以配置范围边界强制返回空桶,这个功能在实际开发中很有用。

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "histogram_born": {
      "date_histogram": {
        "field": "born",
        "interval": "month",
        "time_zone": "+08:00",
        "extended_bounds": {
          "min": "2000-02-01 00:00:00",
          "max": "2000-08-01 00:00:00"
        }
      }
    }
  }
}

        对于固定长度的时间间隔,可以用fixed_interval来配置,你可以配置2d、3d这样的时间间隔而不是像日历间隔那样只能固定看一天、一周、一月、一季、一年的间隔。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "histogram_born": {
      "date_histogram": {
        "field": "born",
        "interval": "120d",
        "time_zone": "+08:00"
      }
    }
  }
}

        固定长度的时间间隔最大的时间单位是天(d),还可以使用小时(h)、分钟(m)、秒(s)、毫秒(ms),不支持以周(week)、月(month)、季度(quarter)、年(year)作为固定长度的时间间隔单位。

3.6、缺失聚集

        使用缺失聚集可以很方便地统计出索引中某个字段缺失或者为空的文档数量。

        聚集请求直接返回address字段为空的文档数量

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "miss_address": {
      "missing": {
        "field": "address.keyword"
      }
    }
  }
}

        该聚集请求直接返回address字段为空的文档数量

  "aggregations" : {
    "miss_address" : {
      "doc_count" : 6
    }
  }

3.7、过滤器聚集

        过滤器聚集往往作为其他聚集的父聚集使用,它可以在其他的聚集开始之前去掉一些文档使其不纳入统计,但是过滤器聚集的过滤条件对搜索结果不起作用。也就是说,它只过滤聚集结果,不过滤搜索结果。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "filteraggs": {
      "filter": {
        "term": {
          "age": "18"
        }
      },
      "aggs": {
        "names": {
          "terms": {
            "field": "username.keyword",
            "size": 10
          }
        }
      }
    }
  }
}

        在这个嵌套聚集请求中,先使用过滤器聚集只保留age为“18”的文档,然后嵌套了词条聚集,聚集的结果只出现一个桶,但是搜索结果还是会返回所有的文档。这里设置了size为0没有展示搜索详情,但是如下所示total已经返回了全部文档总数。

  "aggregations" : {
    "filteraggs" : {
      "doc_count" : 2,
      "names" : {
        "doc_count_error_upper_bound" : 0,
        "sum_other_doc_count" : 0,
        "buckets" : [
          {
            "key" : "张三",
            "doc_count" : 1
          },
          {
            "key" : "赵四",
            "doc_count" : 1
          }
        ]
      }
    }
  }

3.8、多过滤聚集

        跟过滤器聚集相比,多过滤器聚集允许添加多个过滤条件,每个条件生成一个桶,聚集结果会返回每个桶匹配的文档数。你还可以把不属于任何过滤器的文档全部放入一个名为“_other_”的桶。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "filtersaggs": {
      "filters": {
        "other_bucket": true, 
        "filters": {
          "zhang": {
            "match":{
              "username":"张"
            }
          },
          "zhao":{
            "match":{
              "username":"赵"
            }
          }
        }
      }
    }
  }
}

        这个请求定义了两个过滤条件,分别用match搜索姓名包含“张”和“赵”的文档,搜索的结果数会各自返回到桶中,other_bucket参数设置为true表示显示不属于任何过滤器的文档数到名为“_other_”的桶中,代码如下。

  "aggregations" : {
    "filtersaggs" : {
      "buckets" : {
        "zhang" : {
          "doc_count" : 1
        },
        "zhao" : {
          "doc_count" : 1
        },
        "_other_" : {
          "doc_count" : 6
        }
      }
    }
  }

4、管道聚集

        度量聚集和桶聚集都是对索引的文档数据进行统计,但是管道聚集统计的对象不是索引中的文档数据,它是对桶聚集产生的结果做进一步聚集从而得到一些新的统计结果。管道聚集需要你提供一个桶聚集的相对路径来确定统计的桶对象。根据管道聚集出现的位置,管道聚集可以分为父管道聚集和兄弟管道聚集。

4.1、平均桶聚集

        假如你通过日期直方图聚集或直方图聚集产生了3个桶,现在你想对这3个桶的统计值取平均值并做展示,这时候就可以使用平均桶聚集,例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "histogram_born": {
      "date_histogram": {
        "field": "born",
        "interval": "120d"
      },
      "aggs": {
        "age_avg": {
          "avg": {
            "field": "age"
          }
        }
      }
    },
    "age_sum":{
      "avg_bucket": {
        "buckets_path": "histogram_born>age_avg"
      }
    }
  }
}

        在这个聚集请求中,先使用了一个日期直方图聚集把数据切分成了3个桶,然后在每个桶内部嵌套一个平均值聚集,它计算出每个桶中age字段的平均值,在请求的末尾追加了一个平均桶聚集avg_bucket,histogram_born>age_avg这个相对路径表示对平均值聚集age_avg的桶求平均值。平均桶聚集age_sum出现的位置与日期直方图聚集born的位置并列,这样的管道聚集称为兄弟管道聚集。

4.2、求和桶聚集

        除了可以对桶聚集的值求平均值,还可以使用sum_bucket对多个桶的值求和。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "histogram_born": {
      "date_histogram": {
        "field": "born",
        "interval": "120d",
        "extended_bounds": {
          "min": "2000-01-01 00:00:00",
          "max": "2000-12-31 23:59:59"
        }
      },
      "aggs": {
        "age_sum": {
          "sum": {
            "field": "age"
          }
        }
      }
    },
    "age_sum":{
      "sum_bucket": {
        "buckets_path": "histogram_born>age_sum"
      }
    }
  }
}

        在这个请求中,把平均值聚集改为了求和聚集,把平均桶聚集改为了求和桶聚集,可以求出3个桶的age_sum值之和,结果如下

  "aggregations" : {
    "histogram_born" : {
      "buckets" : [
        {
          "key_as_string" : "1999-11-25 00:00:00",
          "key" : 943488000000,
          "doc_count" : 2,
          "age_sum" : {
            "value" : 46.0
          }
        },
        {
          "key_as_string" : "2000-03-24 00:00:00",
          "key" : 953856000000,
          "doc_count" : 2,
          "age_sum" : {
            "value" : 57.0
          }
        },
        {
          "key_as_string" : "2000-07-22 00:00:00",
          "key" : 964224000000,
          "doc_count" : 4,
          "age_sum" : {
            "value" : 160.0
          }
        },
        {
          "key_as_string" : "2000-11-19 00:00:00",
          "key" : 974592000000,
          "doc_count" : 0,
          "age_sum" : {
            "value" : 0.0
          }
        }
      ]
    },
    "age_sum" : {
      "value" : 263.0
    }
  }

4.3、最大桶和最小桶聚集

        最大桶和最小桶聚集分别用于求多个桶的最大值和最小值,使用方法与求和桶聚集类似。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "histogram_born": {
      "date_histogram": {
        "field": "born",
        "interval": "120d"
      },
      "aggs": {
        "age_data": {
          "stats": {
            "field": "age"
          }
        }
      }
    },
    "max_age":{
      "max_bucket": {
        "buckets_path": "histogram_born>age_data.max"
      }
    }
  }
}

        这个请求在日期直方图聚集里面嵌套了一个统计聚集,统计聚集包含多个值,因此在指定桶的相对路径时使用了histogram_born>age_data.max指定对统计聚集的max字段求最大值,得到的结果是3个桶中的最大值。由于最大值桶的key值可能有多个,所以max_age返回的keys是一个数组,结果如下。

  "aggregations" : {
    "histogram_born" : {
      "buckets" : [
        {
          "key_as_string" : "1999-11-25 00:00:00",
          "key" : 943488000000,
          "doc_count" : 2,
          "age_data" : {
            "count" : 2,
            "min" : 18.0,
            "max" : 28.0,
            "avg" : 23.0,
            "sum" : 46.0
          }
        },
        {
          "key_as_string" : "2000-03-24 00:00:00",
          "key" : 953856000000,
          "doc_count" : 2,
          "age_data" : {
            "count" : 2,
            "min" : 9.0,
            "max" : 48.0,
            "avg" : 28.5,
            "sum" : 57.0
          }
        },
        {
          "key_as_string" : "2000-07-22 00:00:00",
          "key" : 964224000000,
          "doc_count" : 4,
          "age_data" : {
            "count" : 4,
            "min" : 18.0,
            "max" : 88.0,
            "avg" : 40.0,
            "sum" : 160.0
          }
        }
      ]
    },
    "max_age" : {
      "value" : 88.0,
      "keys" : [
        "2000-07-22 00:00:00"
      ]
    }
  }

4.4、累计求和桶聚集

        累计求和桶聚集用于对直方图聚集或日期直方图聚集的桶中的某些数据做累计求和,每次求和的结果会分别放入直方图聚集的各个桶。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "histogram_born": {
      "date_histogram": {
        "field": "born",
        "interval": "120d"
      },
      "aggs": {
        "sum_age": {
          "sum": {
            "field": "age"
          }
        },
        "acc_sum":{
          "cumulative_sum": {
            "buckets_path": "sum_age"
          }
        }
      }
    }
  }
}

        在这个请求中,由于管道聚集cumulative_sum出现在日期直方图聚集的子聚集中,所以称cumulative_sum是一个父管道聚集。由于buckets_path指定的路径是相对路径,所以这里直接把路径设置为求和聚集的名称“sum_age”就可以了。结果如下。

  "aggregations" : {
    "histogram_born" : {
      "buckets" : [
        {
          "key_as_string" : "1999-11-25 00:00:00",
          "key" : 943488000000,
          "doc_count" : 2,
          "sum_age" : {
            "value" : 46.0
          },
          "acc_sum" : {
            "value" : 46.0
          }
        },
        {
          "key_as_string" : "2000-03-24 00:00:00",
          "key" : 953856000000,
          "doc_count" : 2,
          "sum_age" : {
            "value" : 57.0
          },
          "acc_sum" : {
            "value" : 103.0
          }
        },
        {
          "key_as_string" : "2000-07-22 00:00:00",
          "key" : 964224000000,
          "doc_count" : 4,
          "sum_age" : {
            "value" : 160.0
          },
          "acc_sum" : {
            "value" : 263.0
          }
        }
      ]
    }
  }

4.5、差值聚集

        差值聚集用于计算直方图聚集的相邻桶数据的增量值,它也是父管道聚集。例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "histogram_born": {
      "date_histogram": {
        "field": "born",
        "interval": "120d"
      },
      "aggs": {
        "age_avg": {
          "avg": {
            "field": "age"
          }
        },
        "hb":{
          "derivative": {
            "buckets_path": "age_avg"
          }
        }
      }
    }
  }
}

        值聚集可以用于计算相邻桶数据的环比增长值,因此从第二个桶开始才有统计结果,结果如下。

  "aggregations" : {
    "histogram_born" : {
      "buckets" : [
        {
          "key_as_string" : "1999-11-25 00:00:00",
          "key" : 943488000000,
          "doc_count" : 2,
          "age_avg" : {
            "value" : 23.0
          }
        },
        {
          "key_as_string" : "2000-03-24 00:00:00",
          "key" : 953856000000,
          "doc_count" : 2,
          "age_avg" : {
            "value" : 28.5
          },
          "hb" : {
            "value" : 5.5
          }
        },
        {
          "key_as_string" : "2000-07-22 00:00:00",
          "key" : 964224000000,
          "doc_count" : 4,
          "age_avg" : {
            "value" : 40.0
          },
          "hb" : {
            "value" : 11.5
          }
        }
      ]
    }
  }

5、使用fielddata聚集text字段

        Elasticsearch支持各种常用的聚集方式,但是这些聚集请求的聚集字段均未使用text字段,原因是实现聚集统计时需要使用字段的doc value值,而text字段不支持doc value。为了让text字段也能做聚集统计,Elasticsearch给text字段提供了fielddata字段数据功能,使用此功能可以在内存中临时生成字段的doc value值,从而实现对text字段做聚集统计。字段数据是一种缓存机制,它会取出字段的值放入内存,可用于排序和聚集统计。

5.1、什么是doc value值

        doc value值是字段本身的数据内容,它与_source的值是一样的。在构建索引时,索引字段的doc value值会生成并存放在磁盘上,这个值主要用于排序和聚集统计,text类型字段不支持doc value值。

        当我们查看username字段的doc value值时,只能使用username.keyword,否则会返回异常,因为username是一个text类型的字段。如下所示

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "docvalue_fields": ["username"]
}

         上述请求中直接使用了username字段,会返回如下异常信息

{
  "error" : {
    "root_cause" : [
      {
        "type" : "illegal_argument_exception",
        "reason" : "Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [username] in order to load field data by uninverting the inverted index. Note that this can use significant memory."
      }
    ],
    "type" : "search_phase_execution_exception",
    "reason" : "all shards failed",
    "phase" : "fetch",
    "grouped" : true,
    "failed_shards" : [
      {
        "shard" : 0,
        "index" : "user",
        "node" : "CkokWaSmT3eAiswEEgCB5w",
        "reason" : {
          "type" : "illegal_argument_exception",
          "reason" : "Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [username] in order to load field data by uninverting the inverted index. Note that this can use significant memory."
        }
      }
    ],
    "caused_by" : {
      "type" : "illegal_argument_exception",
      "reason" : "Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [username] in order to load field data by uninverting the inverted index. Note that this can use significant memory.",
      "caused_by" : {
        "type" : "illegal_argument_exception",
        "reason" : "Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [username] in order to load field data by uninverting the inverted index. Note that this can use significant memory."
      }
    }
  },
  "status" : 400
}

        此时将username调整为username.keyword,这是一个keyword类型,返回结果正常。请求如下

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "docvalue_fields": ["username.keyword"]
}

        返回结果正常,如下

      {
        "_index" : "user",
        "_type" : "_doc",
        "_id" : "8",
        "_score" : 1.0,
        "_source" : {
          "userid" : "8",
          "username" : "宋小宝",
          "age" : "28",
          "sex" : "false",
          "born" : "2000-10-26 19:00:00",
          "address" : "上海"
        },
        "fields" : {
          "username.keyword" : [
            "宋小宝"
          ]
        }
      }

5.2、使用fielddata聚集text字段

        在user索引中新增一个text类型的字段,在映射中开启字段数据功能

PUT user/_mapping
{
  "properties": {
    "desc":{
      "type": "text",
      "fielddata": true
    }
  }
}

        新增一条数据

POST user/_bulk
{"index":{"_id":"9"}}
{"userid":"","username":"后羿","age":"18","sex":"false","born":"2000-10-26 19:00:00","address":"杭州","desc":"我很能射!"}

         在这个desc字段上做词条聚集

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "termdata": {
      "terms": {
        "field": "desc",
        "size": 10
      }
    }
  }
}

        这时候每个中文都会被放到桶中成为最终结果,如果文档很多而且文本较长,则这个过程需要消耗大量内存,结果如下。

  "aggregations" : {
    "termdata" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "射",
          "doc_count" : 1
        },
        {
          "key" : "很",
          "doc_count" : 1
        },
        {
          "key" : "我",
          "doc_count" : 1
        },
        {
          "key" : "能",
          "doc_count" : 1
        }
      ]
    }
  }

        要显示内存中的desc字段数据所占用的大小,可以使用以下代码。

GET _cat/fielddata?v&fields=desc

       只要传入需要查看的字段名就能查出,代码如下。

id                     host       ip         node         field size
CkokWaSmT3eAiswEEgCB5w 172.17.0.2 172.17.0.2 8976e2932988 desc  832b

        字段数据可能会消耗大量内存,为了防止内存被字段数据过度消耗,可以在elasticsearch.yml中使用indices.fielddata.cache.size配置字段数据消耗内存的上限,可以给定一个百分比值,也可以给定具体大小值,该参数配置完后需要重启集群才能生效。

        除了内存上限的配置,还可以使用字段数据的断路器,它会评估一个请求使用字段数据消耗的内存量,如果消耗的内存量超过indices.breaker.request.limit中配置的数值,它就会终止该请求继续消耗内存,这种配置可以使用REST端点动态修改,代码如下。

PUT /_cluster/settings
{
  "persistent" : {
    "indices.breaker.request.limit" : "50%"
  }
}

6、全局有序编号

        当你在某个keyword字段(使用doc value)或text字段(使用fielddata)上做词条聚集时,Elasticsearch在每个分片中会按照字典顺序给每个词条分配一个全局有序的编号,然后统计每个分片各个编号出现的次数,最后将各个分片的统计结果汇总,返回最终结果时会把每个词条的编号换转回词条。全局有序编号到各个词条的映射关系会形成一个字典,这个字典会成为字段数据缓存的一部分。

6.1、使用全局有序编号加快聚集速度

        Elasticsearch不直接对原始文本做词条聚集,而是改为对全局有序编号做聚集,是为了减少聚集统计过程中的内存消耗并加快统计速度。

        使用字段原始的文本做聚集统计,如下。

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age-stats": {
      "terms": {
        "field": "age",
        "execution_hint": "map"
      }
    }
  }
}

        参数execution_hint设置为map,表示会使用词条的文本做聚集,这会导致性能降低,在实际开发中不推荐这种设置,通常直接保持默认值global_ordinals即可。

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age-stats": {
      "terms": {
        "field": "age",
        "execution_hint": "global_ordinals"
      }
    }
  }
}

6.2、全局有序编号生成时机

   虽然使用全局有序编号能够减少内存消耗并加快聚集统计的速度,但是当聚集的字段基数太大时(也就是桶的个数太多时),全局有序编号到词条的映射所形成的字典会变得很大导致构建的过程比较耗时,反而会使聚集的速度变得很慢,甚至会因为消耗内存过多而触发字段数据的断路器导致聚集请求失败。因此,当聚集统计的字段基数过大时,为了减少生成全局有序编号的开销,可以在映射中将字段的eager_global_ordinals设置为true,这样在数据写入索引后,一旦分片被刷新,每个词条的全局有序编号会自动生成,聚集统计时就能直接使用它们。在建索引时为一个keyword字段生成全局有序编号的方法如下。

PUT person
{
  "mappings": {
    "properties": {
      "username": {
        "type": "keyword",
        "eager_global_ordinals": true
      }
    }
  }
}

        在索引person中,字段username开启了eager_global_ordinals功能,表示将全局有序编号的生成时机从聚集时转移到索引分片刷新时,这样在username字段上做词条聚集时可以直接使用已经生成的全局有序编号,从而加快聚集统计的速度。这个过程虽然能加快词条聚集的速度,但是会降低索引构建的速度,只有在聚集字段的基数过大时才有必要使用该功能。

7、聚集请求添加后过滤器

        前面已经介绍过两种过滤器,一种是在布尔查询的过滤上下文中添加搜索条件,另一种是过滤器聚集。本节介绍的后过滤器与它们都有区别,具体情况如下表所示。

3种过滤器的说明
类型说明
布尔查询的过滤条件对搜索和聚集统计效果都有效
过滤器聚集只影响聚集统计效果,对搜索结果无效
后过滤器只影响搜索结果,对聚集统计效果无效

        由于后过滤器是在生成聚集统计结果之后对搜索结果进行过滤,所以它对聚集统计的结果没有任何影响,你可以在聚集请求的后面添加一个后过滤器,例如:

GET user/_search
{
  "query": {
    "match_all": {}
  },
  "size": 10,
  "aggs": {
    "name": {
      "terms": {
        "field": "username.keyword",
        "size": 10
      }
    }
  },
  "post_filter": {
    "term": {
      "username.keyword": "后羿"
    }
  }
}

        这个请求把后过滤器的过滤条件放到了聚集请求的后面,表示搜索结果只显示名字为“后羿”的数据,聚集统计的结果是索引的全部文档

{
  "took" : 51,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "user",
        "_type" : "_doc",
        "_id" : "9",
        "_score" : 1.0,
        "_source" : {
          "userid" : "",
          "username" : "后羿",
          "age" : "18",
          "sex" : "false",
          "born" : "2000-10-26 19:00:00",
          "address" : "杭州",
          "desc" : "我很能射!"
        }
      }
    ]
  },
  "aggregations" : {
    "name" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "后羿",
          "doc_count" : 1
        },
        {
          "key" : "宋小宝",
          "doc_count" : 1
        },
        {
          "key" : "张三",
          "doc_count" : 1
        },
        {
          "key" : "李二狗",
          "doc_count" : 1
        },
        {
          "key" : "李四",
          "doc_count" : 1
        },
        {
          "key" : "李大头",
          "doc_count" : 1
        },
        {
          "key" : "王五",
          "doc_count" : 1
        },
        {
          "key" : "赵四",
          "doc_count" : 1
        },
        {
          "key" : "马六",
          "doc_count" : 1
        }
      ]
    }
  }
}

8、小结

  • 度量聚集可以将搜索结果在某个字段上做数量统计,例如求和、求平均值、求最大值、最小值、计算基数、计算百分比分布数据等。
  • 桶聚集可以在某个字段上划分一些区间,计算出每个区间的文档数目并进行统计。桶聚集可以嵌套其他的桶聚集或度量聚集来完成复杂的统计计算。
  • 管道聚集需要指定聚集的相对路径,它可以对桶聚集的计算结果进行再次统计,例如求和、求平均值、求差值等。
  • 由于text字段不支持doc value,它无法直接用来做聚集统计,为了解决这一问题,可以在映射中开启text字段的fielddata功能。在这个text字段做聚集统计时,该功能会实现在内存中产生一个正排索引起到类似于doc value的作用从而完成text字段的聚集统计。这个过程比较耗内存,使用前可以配置字段数据的断路器或者设置fielddata内存消耗的上限,防止出现内存不足导致集群故障。
  • 当进行词条聚集时,由于Elasticsearch需要为每个词条分配全局有序编号并维持编号到词条的映射关系,如果字段的基数太大,则这个过程存在很大的性能开销,会导致聚集的速度变慢。此时可以将聚集字段的eager_global_ordinals设置为true,在索引数据写入时自动完成全局有序编号的构建,从而加快词条聚集的速度。
  • 在聚集请求中使用后过滤器,可以对搜索结果做进一步筛选,但是对聚集统计的结果无影响,它的效果与过滤器聚集的恰好相反。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值