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

为何要使用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
}
}
}
}
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/
亚马逊测评 www.yisuping.cn
深圳网站建设www.sz886.com

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值