经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Elasticsearch » 查看文章
ElasticSearch(ES)使用Nested结构存储KV及聚合查询
来源:cnblogs  作者:JeffreyHu  时间:2021/3/8 11:19:43  对本文有异议

自建博客地址: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就会报错:

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

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

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

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

  1. {
  2. "labels": [{
  3. "key": "ip",
  4. "value: "127.0.0.1"
  5. }]
  6. },
  7. {
  8. "labels": [{
  9. "key": "ip",
  10. "value: "127.0.0.2"
  11. }]
  12. }

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

  1. {
  2. "query":{
  3. "bool":{
  4. "must":[
  5. {
  6. "match":{
  7. "key":"ip"
  8. }
  9. },
  10. {
  11. "match":{
  12. "value":"127.0.0.1"
  13. }
  14. }
  15. ]
  16. }
  17. }
  18. }

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

  1. {
  2. "labels.key":[
  3. "ip"
  4. ],
  5. "labels.value":[
  6. "127.0.0.1",
  7. "127.0.0.2"
  8. ]
  9. }

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

  1. {
  2. "labels.key":"ip",
  3. "labels.value":"127.0.0.1"
  4. },
  5. {
  6. "labels.key":"ip",
  7. "labels.value":"127.0.0.2"
  8. }

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

定义mappings

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

  1. "mappings": {
  2. "demoType": {
  3. "labels": {
  4. // 字段类型设置为nested
  5. "type": "nested",
  6. "properties": {
  7. "key": {
  8. "type": "keyword"
  9. },
  10. "value": {
  11. "type": "keyword"
  12. }
  13. }
  14. }
  15. }
  16. }

查询

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

  1. {
  2. "query": {
  3. "bool": {
  4. "must": [
  5. {
  6. "nested": {
  7. "path": "labels",
  8. "query": {
  9. "bool": {
  10. "must": [
  11. {
  12. "term": {
  13. "labels.key": "ip"
  14. }
  15. },
  16. {
  17. "term": {
  18. "labels.value": "127.0.0.1"
  19. }
  20. }
  21. ]
  22. }
  23. }
  24. }
  25. }
  26. ]
  27. }
  28. }
  29. }

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

  1. [{
  2. "labels": {
  3. "key": "ip",
  4. "value": "127.0.0.1"
  5. }
  6. }]

分桶聚合

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

nested聚合

假设es中存储如下数据:

  1. [{
  2. "labels": [{
  3. "key": "ip",
  4. "value": "127.0.0.1"
  5. },{
  6. "key": "os",
  7. "value": "windows"
  8. }]
  9. }, {
  10. "labels": [{
  11. "key": "ip",
  12. "value": "127.0.0.2"
  13. },{
  14. "key": "os",
  15. "value": "linux"
  16. }]
  17. }]

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

  1. {
  2. "size": 0,
  3. "aggs": {
  4. "labels_nested": {
  5. "nested": {
  6. "path": "labels"
  7. },
  8. "aggs": {
  9. "nested_value": {
  10. "terms": {
  11. "field": "labels.value"
  12. }
  13. }
  14. }
  15. }
  16. }
  17. }

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

  1. {
  2. "aggregations": {
  3. "labels_nested": {
  4. "doc_count": 2,
  5. "nested_value": {
  6. "buckets": [
  7. {
  8. "doc_count": 1,
  9. "key": "127.0.0.1"
  10. },
  11. {
  12. "doc_count": 1,
  13. "key": "127.0.0.2"
  14. },
  15. {
  16. "doc_count": 1,
  17. "key": "windows"
  18. },
  19. {
  20. "doc_count": 1,
  21. "key": "linux"
  22. }
  23. ]
  24. }
  25. }
  26. }
  27. }

过滤属性值

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

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

  1. {
  2. "size": 0,
  3. "aggs": {
  4. "labels_nested": {
  5. "nested": {
  6. "path": "labels"
  7. },
  8. "aggs": {
  9. "nested_ip": {
  10. "filter": {
  11. "term": {
  12. "labels.key": "ip"
  13. }
  14. },
  15. "aggs": {
  16. "nested_value": {
  17. "terms": {
  18. "field": "labels.value"
  19. }
  20. }
  21. }
  22. }
  23. }
  24. }
  25. }
  26. }

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

  1. {
  2. "aggregations": {
  3. "labels_nested": {
  4. "doc_count": 2,
  5. "nested_ip": {
  6. "doc_count": 2,
  7. "nested_value": {
  8. "buckets": [
  9. {
  10. "doc_count": 1,
  11. "key": "127.0.0.1"
  12. },
  13. {
  14. "doc_count": 1,
  15. "key": "127.0.0.2"
  16. }
  17. ]
  18. }
  19. }
  20. }
  21. }
  22. }

nested多重聚合

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

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

  1. {
  2. "size": 0,
  3. "aggs": {
  4. "labels_nested": {
  5. "nested": {
  6. "path": "labels"
  7. },
  8. "aggs": {
  9. "nested_ip": {
  10. "filter": {
  11. "term": {
  12. "labels.key": "ip"
  13. }
  14. },
  15. "aggs": {
  16. "nested_ip_value": {
  17. "terms": {
  18. "field": "labels.value"
  19. },
  20. "aggs": {
  21. "reverse_labels": {
  22. "reverse_nested": {}, //注意这里
  23. "aggs": {
  24. "nested_os": {
  25. "nested": {
  26. "path": "labels"
  27. },
  28. "aggs": {
  29. "labels_os": {
  30. "filter": {
  31. "term": {
  32. "labels.key": "os"
  33. }
  34. },
  35. "aggs": {
  36. "labels_os_value": {
  37. "terms": {
  38. "field": "labels.value"
  39. }
  40. }
  41. }
  42. }
  43. }
  44. }
  45. }
  46. }
  47. }
  48. }
  49. }
  50. }
  51. }
  52. }
  53. }
  54. }

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

  1. {
  2. "aggregations": {
  3. "labels_nested": {
  4. "doc_count": 2,
  5. "nested_ip": {
  6. "nested_ip_value": {
  7. "buckets": [
  8. {
  9. "doc_count": 1,
  10. "reverse_labels": {
  11. "doc_count": 1,
  12. "nested_os": {
  13. "labels_os": {
  14. "doc_count": 1,
  15. "labels_os_value": {
  16. "buckets": [
  17. {
  18. "doc_count": 1,
  19. "key": "windows"
  20. }
  21. ]
  22. }
  23. },
  24. "doc_count": 1
  25. }
  26. },
  27. "key": "127.0.0.1"
  28. },
  29. {
  30. "doc_count": 1,
  31. "reverse_labels": {
  32. "doc_count": 1,
  33. "nested_os": {
  34. "labels_os": {
  35. "doc_count": 1,
  36. "labels_os_value": {
  37. "buckets": [
  38. {
  39. "doc_count": 1,
  40. "key": "linux"
  41. }
  42. ]
  43. }
  44. },
  45. "doc_count": 1
  46. }
  47. },
  48. "key": "127.0.0.2"
  49. }
  50. ]
  51. },
  52. "doc_count": 2
  53. }
  54. }
  55. }
  56. }

结语

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

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

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

原文链接:http://www.cnblogs.com/huzhanfei/p/elasticsearches-shi-yongnested-jie-gou-cun-chukv-j.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号