共计 4572 个字符,预计需要花费 12 分钟才能阅读完成。
自建博客地址: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 数量并不是咱们能管制的,例如在监控零碎中的业务数据所携带的业务标签,其中可能蕴含了监控零碎不能预知的业务字段。
对于这种情景,可能想到的解决方案两个:
- 调整 ElasticSearch 的配置,减少 field 的限度数量:这种计划仅仅实用于能够预测出 field 数量极限的状况,治标不治本,一旦 field 数量再次到达限度,又会面临同样的问题。
- 就是应用 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.key
为ip
和 os
的数据均被统计到了其中,这通常不合乎咱们理论场景中的需要。
当初假如要对所有 labels.key
为ip
的 labels.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.key
为ip
聚合后,再对 labels.key
为os
进行聚合:
{
"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 文本,但目前还未实现。