2021年3月7日星期日

ElasticSearch(ES)使用Nested结构存储KV及聚合查询

自建博客地址:https://www.bytelife.net,欢迎访问! 本文为博客同步发表文章,为了更好的阅读体验,建议您移步至我的博客👇

本文作者: Jeffrey
本文链接: https://www.bytelife.net/articles/51440.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文将讨论如何在ElasticSearch中使用nested结构进行数据的存储、查询和聚合,并结合K-V场景讨论ElasticSearch针对field数量限制的解决方案。

为何要使用Nested结构存储KV(键值对)?

ElasticSearch对于field的数量有限制,默认情况下field的数量如果超过1000个,写入时再创建新的fields就会报错:

java.lang.IllegalArgumentException: Limit of total fields [1000] in index [(index_name)] has been exceeded at org.elasticsearch.index.mapper.MapperService.checkTotalFieldsLimit(MapperService.java:630)

但有些场景的field数量并不是我们能控制的,例如在监控系统中的业务数据所携带的业务标签,其中可能包含了监控系统不能预知的业务字段。
对于这种情景,可能想到的解决方案两个:

  1. 调整ElasticSearch的配置,增加field的限制数量:这种方案仅仅适用于可以预测出field数量极限的情况,治标不治本,一旦field数量再次抵达限制,又会面临同样的问题。
  2. 就是使用Pair结构来存储

假设第2种方案的数据结构为:

{ "labels": [{  "key": "ip",  "value: "127.0.0.1"  }]},{ "labels": [{  "key": "ip",  "value: "127.0.0.2"  }]}

那么es查询就会存在一个问题,例如下面的查询:

{ "query":{  "bool":{   "must":[    {     "match":{      "key":"ip"     }    },    {     "match":{      "value":"127.0.0.1"     }    }   ]  } }}

这个查询会把例子中的的数据全部查询出来,并不符合我们的预期。这是因为es在存储索引时,对于普通object类型的field实际上是打平来存储的,比如这样:

{ "labels.key":[  "ip" ], "labels.value":[  "127.0.0.1",  "127.0.0.2" ]}

可以看见,索引打平后,对象的关联关系丢失了。对于这种情况,ElasticSearch提供的nested结构可以帮助我们解决类似的问题。Nested结构保留了子文档数据中的关联性,如果labels的数据格式被定义为nested,那么每一个nested object将会作为一个隐藏的单独文本建立索引。如下:

{  "labels.key":"ip",  "labels.value":"127.0.0.1"},{  "labels.key":"ip",  "labels.value":"127.0.0.2"}

通过分开给每个nested object建索引,object内部的字段间的关系就能保持。当执行查询时,只会匹配'match'同时出现在相同的nested object的结果。

定义mappings

使用nested结构非常简单,指定字段的type为nested即可。下面的例子中定义了一个名为labels的nested结构,其中包含两个字段,分别是key和value。

"mappings": { "demoType": {  "labels": {   // 字段类型设置为nested   "type": "nested",   "properties": {    "key": {     "type": "keyword"    },    "value": {     "type": "keyword"    }   }  } }}

查询

nested结构的数据查询和普通object略有不同,nested object作为一个独立隐藏文档单独建索引,因此,不能直接查询到它们。取而代之,我们必须使用nested查询或者nested filter。例如:

{ "query": { "bool": {  "must": [  {   "nested": {   "path": "labels",   "query": {    "bool": {    "must": [     {     "term": {      "labels.key": "ip"     }     },     {     "term": {      "labels.value": "127.0.0.1"     }     }    ]    }   }   }  }  ] } }}

这个查询可以返回我们预期的正确结果:

[{ "labels": {  "key": "ip",  "value": "127.0.0.1" }}]

分桶聚合

查询的问题解决了,聚合时问题又来了,前面我们说到,nested结构存储在一个隐藏的单独文本索引中,那么普通的聚合查询自然便无法访问到它们。因此,nested结构在聚合时,需要使用特定的nested聚合。

nested聚合

假设es中存储如下数据:

[{ "labels": [{  "key": "ip",  "value": "127.0.0.1" },{  "key": "os",  "value": "windows" }]}, { "labels": [{  "key": "ip",  "value": "127.0.0.2" },{  "key": "os",  "value": "linux" }]}]

我们要聚合所有对labels.value进行聚合,可以使用下面的方式:

{ "size": 0, "aggs": { "labels_nested": {  "nested": {  "path": "labels"  },  "aggs": {  "nested_value": {   "terms": {   "field": "labels.value"   }  }  } } }}

这个查询将会得到下面类似的结果:

{ "aggregations": { "labels_nested": {  "doc_count": 2,  "nested_value": {  "buckets": [   {   "doc_count": 1,   "key": "127.0.0.1"   },   {   "doc_count": 1,   "key": "127.0.0.2"   },   {   "doc_count": 1,   "key": "windows"   },   {   "doc_count": 1,   "key": "linux"   }  ]  } } }}

过滤属性值

上面的例子可以看到,其只是单纯的将所有的value进行了聚合,并没有针对k-v中的key进行过滤,因此导致labels.keyipos的数据均被统计到了其中,这通常不符合我们实际场景中的需求。

现在假设要对所有labels.keyiplabels.value进行聚合,那么可以使用如下的方式:

{ "size": 0, "aggs": { "labels_nested": {  "nested": {  "path": "labels"  },  "aggs": {  "nested_ip": {   "filter": {   "term": {    "labels.key": "ip"   }   },   "aggs": {   "nested_value": {    "terms": {    "field": "labels.value"    }   }   }  }  } } }}

通过这样的方式就可以把labels.key不是ip的文档过滤掉,经过这个查询将得到类似如下的结果:

{ "aggregations": { "labels_nested": {  "doc_count": 2,  "nested_ip": {  "doc_count": 2,  "nested_value": {   "buckets": [   {    "doc_count": 1,    "key": "127.0.0.1"   },   {    "doc_count": 1,    "key": "127.0.0.2"   }   ]  }  } } }}

nested多重聚合

如果想在nested聚合下嵌套聚合其它字段,直接嵌套是不行的,这里需要使用到reverse_nested跳出当前nested聚合后,再进行嵌套聚合。
注意:无论是嵌套其它nested字段还是普通字段,都需要使用reverse_nested跳出当前nested聚合。

例如想对labels.keyip聚合后,再对labels.keyos进行聚合:

{ "size": 0, "aggs": { "labels_nested": {  "nested": {  "path": "labels"  },  "aggs": {  "nested_ip": {   "filter": {   "term": {    "labels.key": "ip"   }   },   "aggs": {   "nested_ip_value": {    "terms": {    "field": "labels.value"    },    "aggs": {    "reverse_labels": {     "reverse_nested": {}, //注意这里     "aggs": {     "nested_os": {      "nested": {      "path": "labels"      },      "aggs": {      "labels_os": {       "filter": {       "term": {        "labels.key": "os"       }       },       "aggs": {       "labels_os_value": {        "terms": {        "field": "labels.value"        }       }       }      }      }     }     }    }    }   }   }  }  } } }}

如此,将得到类似下面的结果:

{ "aggregations": { "labels_nested": {  "doc_count": 2,  "nested_ip": {  "nested_ip_value": {   "buckets": [   {    "doc_count": 1,    "reverse_labels": {    "doc_count": 1,    "nested_os": {     "labels_os": {     "doc_count": 1,     "labels_os_value": {      "buckets": [      {       "doc_count": 1,       "key": "windows"      }      ]     }     },     "doc_count": 1    }    },    "key": "127.0.0.1"   },   {    "doc_count": 1,    "reverse_labels": {    "doc_count": 1,    "nested_os": {     "labels_os": {     "doc_count": 1,     "labels_os_value": {      "buckets": [      {       "doc_count": 1,       "key": "linux"      }      ]     }     },     "doc_count": 1    }    },    "key": "127.0.0.2"   }   ]  },  "doc_count": 2  } } }}

结语

至此,关于nested结构存储K-V的用法就介绍完啦!使用nested结构可以帮助我们保持object内部的关联性,借此解决elasticsearch对field数量的限制。nested结构不仅可以应用在K-V结构的场景,还可以应用于其它任何需要保持object内部关联性的场景。

注意:使用nested结构也会存在一些问题:

  • 增加,改变或者删除一个nested文本,整个文本必须重新建索引。nested文本越多,代价越大。
  • 检索请求会返回整个文本,而不仅是匹配的nested文本。尽管有计划正在执行以能够支持返回根文本的同时返回最匹配的nested文本,但目前还未实现。








原文转载:http://www.shaoqun.com/a/608598.html

跨境电商:https://www.ikjzd.com/

logo免费制作:https://www.ikjzd.com/w/1998

upc:https://www.ikjzd.com/w/111


自建博客地址:https://www.bytelife.net,欢迎访问!本文为博客同步发表文章,为了更好的阅读体验,建议您移步至我的博客👇本文作者:Jeffrey本文链接:https://www.bytelife.net/articles/51440.html版权声明:本博客所有文章除特别声明外,均采用BY-NC-SA许可协议。转载请注明出处!本文将讨论如何在ElasticSearch中使用n
ola:https://www.ikjzd.com/w/2103
blackbird:https://www.ikjzd.com/w/950
电霸:https://www.ikjzd.com/w/2597
卖家该如何安全运营Facebook账号?:https://www.ikjzd.com/tl/103356
外媒:京东将裁员逾1.2万人,关键职能团队或被砍一半:https://www.ikjzd.com/home/23029
跨境出口电商卖家成长论坛之蜕变:https://www.kjyunke.com/courses/150

没有评论:

发表评论