Elasticsearch:如何在聚合时选择所需要的 bucket 并进行可视化

Elasticsearch 是一个针对大数据的强大搜索引擎,它也是一个强大的数据分析引擎。采集上来的数据如果不经过分析,并生产有意义的见解,就没有任何的价值。在之前的文章中,我们已经详细讲解了有关 Elasticsearch 的聚合(aggregation)。如果你对 Elasticsearch 的聚合还是不太很清楚的话,请参阅我之前的文章:

在我们生成聚合时,我们可以通过 Bucket aggregation 来对数据进行分组,并分别计算它们的指标。但是,在实际的使用中,我们可能面对有些 bucket 并不是我们想要的 bucket,比如有些 bucket 的数据只有一个或者两个,或者是0个,那么我们并不想这些数据展示在我们的结果中。还有一种情况,比如我们只想针对每周的一个特定的天来做一个聚合。针对这些情况,我们该如何来配置我们的聚合呢?

在今天的文章中,我们将使用两种方法来对 bucket 进行选择:

 

准备数据

在今天的展示中,我们将使用 Kibana  自带的数据来做演示。打开 Kibana:

点击 Add data 按钮:

这样我们的索引 kibana_sample_data_logs 就被加载到  Elasticsearch 中了。

 

min_doc_count 使用展示

我们在 Dev Tools 中打入如下的命令:

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "NAME": {
      "terms": {
        "field": "geo.dest",
        "size": 5
      }
    }
  }
}

上面的搜索显示的结果是:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "NAME" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 7112,
      "buckets" : [
        {
          "key" : "CN",
          "doc_count" : 2621
        },
        {
          "key" : "IN",
          "doc_count" : 2323
        },
        {
          "key" : "US",
          "doc_count" : 1120
        },
        {
          "key" : "ID",
          "doc_count" : 487
        },
        {
          "key" : "BR",
          "doc_count" : 411
        }
      ]
    }
  }
}

从上面,我们可以看出来显示有排名前五的 geo.dest。这个在大多数的情况下,这种结果是非常令人满意的,但是在有些情况下,我们可能针对  doc_count 大于一个数,比如 500 的 bucket 感兴趣。那么我们该怎么做呢?我们可以添加 min_doc_count 来限制我们想要的 bucket。我们可以使用如下的查询:

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "NAME": {
      "terms": {
        "field": "geo.dest",
        "size": 5,
        "min_doc_count": 500
      }
    }
  }
}

在上面,我们通过添加 min_doc_count 来限制想要的 bucket 的文档数量。这样上面的查询结果变为:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "NAME" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 4425,
      "buckets" : [
        {
          "key" : "CN",
          "doc_count" : 2621
        },
        {
          "key" : "IN",
          "doc_count" : 2323
        },
        {
          "key" : "US",
          "doc_count" : 1120
        }
      ]
    }
  }
}

在上面,我们可以看出来:只有3个国家的文档是大于 500 的。这个 trick 也同样适用于其他的 bucket aggregation,比如:

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "NAME": {
      "date_histogram": {
        "field": "@timestamp",
        "calendar_interval": "week",
        "min_doc_count": 500
      }
    }
  }
}

通过这样的 min_doc_count 的配置,我们可以过滤掉一些不想要研究的 bucket。

很多可能会想,我们该如何把这个 min_doc_count 放到我们的可视化图中呢?我们可以通过如下的方式来做:

我们可以通过在 Advanced 下的 JSON input 来添加对 min_doc_count 的配置。

 

通过 bucket_selector 来选择想要的 bucket

父管道聚合执行脚本,该脚本确定当前存储桶是否将保留在父多存储桶聚合中。 指定的指标必须为数字,并且脚本必须返回布尔值。 如果脚本语言是表达式,则允许使用数字返回值。 在这种情况下,0.0 将被评估为false,而所有其他值将被评估为 true。

注意:像所有管道聚合一样,bucket_selector 聚合在所有其他同级聚合之后执行。 这意味着使用 bucket_selector 聚合来过滤响应中返回的存储桶不会节省运行聚合的执行时间。

我们还是使用一个具体的例子来进行展示。

我们还是使用上面的例子来进行展示。我们来使用如下的查询:

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "NAME": {
      "terms": {
        "field": "geo.dest",
        "size": 5
      },
      "aggs": {
        "my_selector": {
          "bucket_selector": {
            "buckets_path": {
              "count": "_count"
            },
            "script": "params.count > 500"
          }
        }
      }
    }
  }
}

在上面,我们使用 bucket_selector 通过 script 来对我们的 bucket 进行选择。如果这个 bucket 的文档数量大于 500,那么我们就对它进行分析。上面的代码运行的结果:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "NAME" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 7112,
      "buckets" : [
        {
          "key" : "CN",
          "doc_count" : 2621
        },
        {
          "key" : "IN",
          "doc_count" : 2323
        },
        {
          "key" : "US",
          "doc_count" : 1120
        }
      ]
    }
  }
}

我们可以看到上面的结果我们预期的结果是完全一样的。

有的人可能这么想。既然通过 min_doc_count 同样可以达到同样的效果,那么我还需要这个 bucket_selector 干吗呀?也许我们的上面的例子过于简单。bucket_selector 最强大的地方就是它的 script 编程。它可以通过编程的方式灵活地对我们的数据进行过滤。我们接下来通过一个例子来进行展示。

我们接下来针对我们的索引值显示每个周六的数据统计,我们不关心每周其它天的统计数据。我做一个如下的查询:

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "my_histogram": {
      "date_histogram": {
        "field": "@timestamp",
        "calendar_interval": "1d"
      },
      "aggs": {
        "min_time": {
          "min": {
            "field": "@timestamp"
          }
        },
        "my_selector": {
          "bucket_selector": {
            "buckets_path": {
              "first_time": "min_time"
            },
            "script": "Date date = new Date((long)params.first_time); Calendar c = Calendar.getInstance(); c.setTime(date); return (c.get(Calendar.DAY_OF_WEEK) ==  Calendar.SATURDAY)"
          }
        }
      }
    }
  }
}

在上面,我们通过 script 来对我们需要的 bucket 进行选择。在这个 script 中,它返回一个 bool 值。只有这个日期是周六才返回真,也就是被选择,否则就被丢弃。上面的查询返回的结果是:

{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "my_histogram" : {
      "buckets" : [
        {
          "key_as_string" : "2020-11-07T00:00:00.000Z",
          "key" : 1604707200000,
          "doc_count" : 229,
          "min_time" : {
            "value" : 1.604715893405E12,
            "value_as_string" : "2020-11-07T02:24:53.405Z"
          }
        },
        {
          "key_as_string" : "2020-11-14T00:00:00.000Z",
          "key" : 1605312000000,
          "doc_count" : 230,
          "min_time" : {
            "value" : 1.605313053769E12,
            "value_as_string" : "2020-11-14T00:17:33.769Z"
          }
        },
        {
          "key_as_string" : "2020-11-21T00:00:00.000Z",
          "key" : 1605916800000,
          "doc_count" : 230,
          "min_time" : {
            "value" : 1.6059168E12,
            "value_as_string" : "2020-11-21T00:00:00.000Z"
          }
        },
        {
          "key_as_string" : "2020-11-28T00:00:00.000Z",
          "key" : 1606521600000,
          "doc_count" : 230,
          "min_time" : {
            "value" : 1.606534377543E12,
            "value_as_string" : "2020-11-28T03:32:57.543Z"
          }
        },
        {
          "key_as_string" : "2020-12-05T00:00:00.000Z",
          "key" : 1607126400000,
          "doc_count" : 229,
          "min_time" : {
            "value" : 1.607135414763E12,
            "value_as_string" : "2020-12-05T02:30:14.763Z"
          }
        },
        {
          "key_as_string" : "2020-12-12T00:00:00.000Z",
          "key" : 1607731200000,
          "doc_count" : 329,
          "min_time" : {
            "value" : 1.607732193181E12,
            "value_as_string" : "2020-12-12T00:16:33.181Z"
          }
        },
        {
          "key_as_string" : "2020-12-19T00:00:00.000Z",
          "key" : 1608336000000,
          "doc_count" : 230,
          "min_time" : {
            "value" : 1.608345358948E12,
            "value_as_string" : "2020-12-19T02:35:58.948Z"
          }
        },
        {
          "key_as_string" : "2020-12-26T00:00:00.000Z",
          "key" : 1608940800000,
          "doc_count" : 230,
          "min_time" : {
            "value" : 1.608947827739E12,
            "value_as_string" : "2020-12-26T01:57:07.739Z"
          }
        }
      ]
    }
  }
}

从上面显示的结果来看,我们可以看到返回的日期是 2020-11-07,2020-11-14 等等。我们可以通过日历应用查看。这些日期都是周六:

也就是说我们通过 bucket_selector 选择了我们想要的 bucket 进行数据统计。

到这里,我们的问题好像已经解决了。可是有些人想问,我们该如何把这个 bucket_selector 的结果在可视化中进行展示呢?我们仔细研究一下这样的搜索在我们正常的可视化工具中基本上是不可以实现,但是我们可以通过 vega 来进行定制。如果你对 vega 的使用不是很熟的话,我们可以阅读我之前的文章 “Kibana:Vega 可视化入门 - 定制自己的可视化图”。

我们输入如下的代码:

{
  "$schema": "https://vega.github.io/schema/vega-lite/v2.json",
  "description": "A simple bar chart with embedded data.",
  "data": {
    "url": {
        "index": "kibana_sample_data_logs",
        "body": {
        "size": 0,
        "aggs": {
          "my_histogram": {
            "date_histogram": {
              "field": "@timestamp",
              "calendar_interval": "1d"
            },
            "aggs": {
              "min_time": {
                "min": {
                  "field": "@timestamp"
                }
              },
              "my_selector": {
                "bucket_selector": {
                  "buckets_path": {
                    "first_time": "min_time"
                  },
                  "script": "Date date = new Date((long)params.first_time); Calendar c = Calendar.getInstance(); c.setTime(date); return (c.get(Calendar.DAY_OF_WEEK) ==  Calendar.SATURDAY)"
                }
              }
            }
          }
        }
      }
    },
    "format": {
        "property": "aggregations.my_histogram.buckets"
    }
  },
  "mark": "bar",
  "encoding": {
    "x": {"field": "key_as_string", "type": "ordinal", "axis": {"labelAngle": 0}},
    "y": {"field": "doc_count", "type": "quantitative"}
  }
}

展示的图形如下:

 

从上面我们可以看出来只有每周六的数据进行了展示。