作者:刘晓国
在之前的文章 “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" } } } }
在上面的脚本中,我们使用 TextStyle.SHORT 来得到诸如 Fri,Sat 等文字。如果我们想得到全名,比如 Friday, Saturday,我们可以使用 TestStyle.FULL。
在上面的命令中,我们可以仔细阅读这个部分:
"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。