Elasticsearch:创建 Runtime field 并在 Kibana 中使用它 - 7.11 发布

在之前的文章 “Elasticsearch:使用 Runtime fields 对索引字段进行阴影处理以修复错误 - 7.11 发布”,我展示了如何使用 runtime field 来 shadow 一个已有的在 mapping 中的字段,比如 duration。在今天的练习中,我将展示如何创建一个崭新的字段并进行数据的统计。在这里请注意的是:新增加的 runtime field 并不在 source 中添加,而只是在查询时生成的,也即 schema on read。

在接下来的练习中,它包含创建 runtime field 的演示,其中从包含日期的时间戳字段中计算星期几。 然后使用索引字段和新创建的 runtime field 在 Kibana Lens 中创建可视化文件。 Runtime field 是在 Elasticsearch 中读取时为 schema 的实现提供的名称。

 

展示

我们先来创建一个 index mapping:

#Create the index mapping
PUT date_to_day
{
  "mappings": {
    "properties": {
      "timestamp": {
        "type": "date",
        "format": "yyyy-MM-dd"
      },
      "response_code": {
        "type": "integer"
      }
    }
  }
}

在上面,我们展示了两个字段: timestamp 以及  response_code。由于有一个时间戳,我们可以从这个时间戳中导出时间所在的 day of week,也就是星期几。这个对于我们想对一周的每一天统计非常有用。

我们通过如下的 bulk API  来导入数据:

#Load a few documents to work with
POST date_to_day/_bulk
{"index":{}}
{"response_code": 200, "timestamp": "2021-01-01"}
{"index":{}}
{"response_code": 300, "timestamp": "2021-01-03"}
{"index":{}}
{"response_code": 200, "timestamp": "2021-01-04"}
{"index":{}}
{"response_code": 400, "timestamp": "2021-01-01"}
{"index":{}}
{"response_code": 300, "timestamp": "2021-01-05"}
{"index":{}}
{"response_code": 200, "timestamp": "2020-12-21"}
{"index":{}}
{"response_code": 200, "timestamp": "2021-01-02"}
{"index":{}}
{"response_code": 200, "timestamp": "2021-01-08"}
{"index":{}}
{"response_code": 300, "timestamp": "2021-01-09"}
{"index":{}}
{"response_code": 400, "timestamp": "2021-01-09"}

由于我们想对一周内的每一天来进行统计。一种办法是重新建立一个新的 mapping。在这个新的 mapping 里包含这个 day of week 的定义。并在数据导入之前我们对数据进行处理。在实际的使用中,面对大量的已有数据,这样的处理可能非常费力。我们可以使用 runtime field 来完成想要的功能。

 

在搜索请求时使用

由于 runtime field 是动态生成的,它需要计算机来进行处理。在很多的时候,我们并不想修改 mapping 来完成。我们只想针对一些搜索来进行生成这个 runtime field,或者只是作为在修改 mapping 前的一个练习来验证 runtime field  的正确性。我们使用如下的命令来生产这类的 runtime field:

#Create an ephemeral runtime field for day of week and aggregate on it
GET date_to_day/_search
{
  "runtime_mappings": {
    "day_of_week": {
      "type": "keyword",
      "script": {
        "source": """emit(doc['timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.SHORT, Locale.ROOT))"""
      }
    }
  },
  "size": 0,
  "aggs": {
    "terms": {
      "terms": {
        "field": "day_of_week"
      }
    }
  }
}

在上面的命令中,我们可以仔细阅读这个部分:

  "runtime_mappings": {
    "day_of_week": {
      "type": "keyword",
      "script": {
        "source": """emit(doc['timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.SHORT, Locale.ROOT))"""
      }
    }
  },

这个不是是使用 script 来生成一个叫做 day_of_week 的 runtime 字段。而这个字段只存在于这个搜索中。在执行完这个搜索后,这字段将自动消失。这个 day_of_week 字段是根据 timestamp 导引出来的,是之前的 mapping 中完全没有的字段。

上面命令的执行结果为:

{
  "took" : 16,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "terms" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "Fri",
          "doc_count" : 3
        },
        {
          "key" : "Sat",
          "doc_count" : 3
        },
        {
          "key" : "Mon",
          "doc_count" : 2
        },
        {
          "key" : "Sun",
          "doc_count" : 1
        },
        {
          "key" : "Tue",
          "doc_count" : 1
        }
      ]
    }
  }
}

在上面显示,我们对一周内的每一天进行了统计。

 

在 index mapping 中使用

当然在很多的情况下,我们希望这个字段一直存在于索引的 mapping 中。这样做的好处是,我们可以在 Kibana 中的可视化中直接使用被定义的 runtime fields。通过在 mapping 定义下添加 runtime 部分并定义 Painless script,可以映射 runtime fields。 该脚本可以访问文档的整个上下文,包括原始 _source 和任何映射的字段及其值。 在查询时,脚本将运行并为查询所需的每个脚本字段生成值。

在定义要与运行时字段一起使用的 Painless script 时,必须包含 emit 以发射计算值。 例如,以下请求中的脚本从 @timestamp 字段中提取星期几,该字段定义为日期类型。 该脚本根据 timestamp 的值计算星期几,并使用 emit 返回所计算的值。

我们可以通过如下的方法来定义:

#Add the runtime field to the index mapping
PUT date_to_day/_mapping
{
  "runtime": {
    "day_of_week": {
      "type": "keyword",
      "script": {
        "source": """emit(doc['timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.SHORT, Locale.ROOT))"""
      }
    }
  }
}

runtime 部分可以是以下任何数据类型:

  • boolean
  • date
  • double
  • geo_point
  • ip
  • keyword
  • long

我们可以通过如下的命令来查看 date_to_day 索引的 mapping:

{
  "date_to_day" : {
    "mappings" : {
      "runtime" : {
        "day_of_week" : {
          "type" : "keyword",
          "script" : {
            "source" : "emit(doc['timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.SHORT, Locale.ROOT))",
            "lang" : "painless"
          }
        }
      },
      "properties" : {
        "response_code" : {
          "type" : "integer"
        },
        "timestamp" : {
          "type" : "date",
          "format" : "yyyy-MM-dd"
        }
      }
    }
  }
}

我们到 Kibana 的 index pattern 中去创建一个索引模式并查看它的字段定义:

 

从上面我们可以看出来一个新增加的 day_of_week 的字段。

我们可以在 Kibana 中直接使用这个字段并进行可视化:

当然,也许你怀疑是不是索引的 source 是否已经包含新生成的 day_of_week 字段,我们可以通过如下的命令来查看:

GET  date_to_day/_search

上面的命令显示:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "date_to_day",
        "_type" : "_doc",
        "_id" : "_iyDlXcBjSpwk8PH7vNz",
        "_score" : 1.0,
        "_source" : {
          "response_code" : 200,
          "timestamp" : "2021-01-01"
        }
      },
      {
        "_index" : "date_to_day",
        "_type" : "_doc",
        "_id" : "_yyDlXcBjSpwk8PH7vNz",
        "_score" : 1.0,
        "_source" : {
          "response_code" : 300,
          "timestamp" : "2021-01-03"
        }
      },
      {
        "_index" : "date_to_day",
        "_type" : "_doc",
        "_id" : "ACyDlXcBjSpwk8PH7vRz",
        "_score" : 1.0,
        "_source" : {
          "response_code" : 200,
          "timestamp" : "2021-01-04"
        }
      },

  ...

显然,我们的 source 并没有任何的改变。 day_of_week 只是 schema on read。

已标记关键词 清除标记