自建博客地址:https://www.bytelife.net,欢送拜访! 本文为博客同步发表文章,为了更好的浏览体验,建议您移步至我的博客????

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

本文将探讨如何在ElasticSearch中应用nested构造进行数据的存储、查问和聚合,并联合K-V场景探讨ElasticSearch针对field数量限度的解决方案。

<!--more-->

为何要应用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文本,但目前还未实现。