Elasticsearch原理(八):嵌套结构的实现

我们在使用Elasticsearch的过程中,很多业务场景都会用到关联查询。而目前Elasticsearch支持的关联查询无非就是两种方式,一种使用嵌套(nested)和父子文档。本文主要来聊聊关于nested,Elasticsearch。

文章末尾会附上nested和父子文档的差别和使用场景。

如果大家有过一些Lucene基础的话,相信都会知道Lucene中是不支持像嵌套这种数据结构的,而Elasticsearch不过是在Lucene的基础之上,通过hack的方式做了一些修改来支持了嵌套结构,使搜索功能更佳强大,用起来更方便。那么Elasticsearch具体是如何实现的?

ElasticSearch的官方说法:
在这里插入图片描述
在这里插入图片描述

参考:https://www.elastic.co/guide/en/elasticsearch/reference/7.4/nested.html

翻译过来大概有如下几点:

  1. nested类型在索引里是作为Document单独存储的。nested类型可能是1个包含100数据的数组,那就是100个Document。每多1个nested类型,就多增加对应length的Document.
  2. 普通的query对nested字段查询无效,必须使用nested Query
  3. highlight高亮也需要使用专门的Query
  4. 对于nested类型的field个数是有限制的, 长度也是有限制的。

从官网的介绍中我们了解到,nested的存储是单独进行存储的,这其实也就解释了为什么我们明明写入了100条数据用CAT接口查看时数据条数时却显示要比100多很多的原因了。

那么为什么我们普通的检索并没有检索到nested文档的内容呢,Elasticsearch是如何把nested过滤掉的?

首先我们先看Elasticsearch是如何将nested进行分开存储的。

Elasticsearch有一个类org.elasticsearch.index.mapper.DocumentParser,是专门用来解析要索引的Document的。

源码如下:
在这里插入图片描述

我们进入nestedContext方法:
在这里插入图片描述

源码位置:
https://github.com/elastic/elasticsearch/blob/6a5bae184b80c8a0012158c217de340535e9f45c/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java

从源码中可以看出来,生成nested文档主要干了两件事:

  1. 指定_id值为父Doc的id,用来关联
  2. 指定_type值为以”__”开头的,标识特定nested 类型。

看完了nested文档的生成我们知道了nested文档是和普通文档一样被储存的,但是对外确实隐藏的,我们来看看Elasticsearch是如何做到的。

源码如下:

在这里插入图片描述在这里插入图片描述
大家看到这里应该清楚了,查询query的判断决定是否返回nested文档的依据是文档中是否包含_primary_term字段,由此可知,nested文档是不包含_primary_term字段的,而其他文档是包含这个字段的。

_primary_term:是一个整数,每当Primary Shard发生重新分配时,比如重启,Primary选举等,_primary_term会递增1。

我们下面来看_primary_term是如何添加到字段中的。
源码如下:
在这里插入图片描述

源码位置:https://github.com/elastic/elasticsearch/blob/3ac6d527a1386d19008cdd08cdbfef265da30f00/server/src/main/java/org/elasticsearch/index/mapper/SeqNoFieldMapper.java

到这里相信大家都看清楚怎么回事了,那么有的同学就会说了,前面看到的type为“__”开头这个源码里没有处理逻辑呀。那是因为上面的源码是目前最新版本(7.x)的源码,大家都知道7.x版本开始已经不再需要type了。但是大家绝大多数使用的还是7.x以前的版本,笔者特意找了一下早期版本的源码,实现方式还是有些区别的。
源码如下:

/**
 * Creates a new non-nested docs query
 * @param indexVersionCreated the index version created since newer indices can identify a parent field more efficiently
 */
public static Query newNonNestedFilter(Version indexVersionCreated) {
    if (indexVersionCreated.onOrAfter(Version.V_6_1_0)) {
        // 6.1.0版本之后。 只保留有_primary_term这个元字段的query
        return new DocValuesFieldExistsQuery(SeqNoFieldMapper.PRIMARY_TERM_NAME);
    } else {
        // 6.1.0版本之前版本
        return new BooleanQuery.Builder()
            .add(new MatchAllDocsQuery(), Occur.FILTER)
            //过滤掉nested query
            .add(newNestedFilter(), Occur.MUST_NOT)  
            .build();
    }
}

public static Query newNestedFilter() {
    // _type以“__”为前缀
    return new PrefixQuery(new Term(TypeFieldMapper.NAME, new BytesRef("__")));
}

总结来说ElasticSearch对nested隐藏实现方式:

  • 小于6.1.0版本中,过滤掉_type以“__”为前缀的document
  • 大于等于6.1.0版本中只获取有 __primary_term Field的document

同时nested也有两个缺点:

  1. nested无形中增加了索引量,如果不了解具体实现,将无法很好的进行文档划分和预估。ES限制了Field个数和nested对象的size,避免无限制的扩大。
  2. nested Query 整体性能慢,但比parent/child Query稍快。应从业务上尽可能的避免使用NestedQuery,
    对于性能要求高的场景,应该直接禁止使用。

附:nested 和 parent-child的区别以及使用场景

主要区别:

  • 由于存储结构的不同,nested和parent-child的方式有不同的应用场景。
  • nested 所有实体存储在同一个文档,parent-child模式,子type和父type存储在不同的文档里。
  • 查询效率上nested要高于parent-child。
  • 更新效率上parent-child要高于nested,更新的时候nested模式下,es会删除整个文档再创建,而parent-child只会删除你更新的文档在重新创建,不影响其他文档。

使用场景:

  • nested:在少量子文档,并且不会经常改变的情况下使用。
    比如:订单里面的产品,一个订单不可能会有成千上万个不同的产品,一般不会很多,并且一旦下单后,下单的产品是不可更新的。
  • parent-child:在大量文档,并且会经常发生改变的情况下使用。 比如:用户的浏览记录,浏览记录会很大,并且会频繁更新

更多文章关注公众号
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210325093921176.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpYW95dV9CRA==,size_16,color_FFFFFF,t_70

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值