0x00 elasticsearch

elasticsearch 简称 ES,是一个当前流行的搜索服务器(全文搜索引擎),不得不说,它的搜索速度以及提供的丰富搜索选项,在面对企业级的需求时,大多数情况都可以应付自如。从 2010 年诞生至今,已经更新到了 5.6 版本,github 上获得了 25000+的 star。但有些问题在国内还是很少见到解决办法,国内整体还是要比老外落后几年。

最近在面对一个较复杂的索引结构时,使用了多代父子关系文档,但其中遇到了一些问题,这里将解决过程中的收获总结出来给后来的人提供一些借鉴和思路。

0x01 父子文档

父子文档,你可以简单的理解为关系型数据库中的一对多关系,但我们不必自己去维护映射关系,ES 维护了父子映射关系,我们要想使用这个功能,需要完成下面两件事,缺一不可:

指定某一个文档 type 是另一个文档 type 的父亲

在存储子文档时通过 parent 参数指定父文档 id

其实还有一个隐含前提条件,父子关系文档必须被索引在同一个分片上,但最简单的父子关系(只有一代,没有祖辈及以上)不必去考虑,完成上面第二点其实就已经保证了这一点,多代父子的问题下面我会提。

对于指定关系,我们只有两个时间点去设置,创建索引时或者在子 type 创建之前更新父 type 的 mapping,大家可能发现了,这两种方式几乎都是需要重新定义相关的索引结构,所以父子关系的最好在设计之初就想好。

0x02 定义父子关系

PUT /es_test

{

“mappings”: {

“parent_test”: {},

“child_test”: {

“_parent”: {

“type”: “parent_test”

}

}

}

}

1

2

3

4

5

6

7

8

9

10

11

最简单的例子,在创建 child_test 文档 type 时,指定 parent_test 的文档 type 为其父亲。

接下来存储文档时,父文档就像普通文档那样创建,而在创建子文档时,需要添加一个参数 parent 来指定该子文档的父文档 ID

PUT /es_test/child_test/2?parent=父文档 ID

{

“name”: “i am a child”

}

1

2

3

4

0x03 查询父子文档

我们看一个简单的查询语法:

GET /es_test/parent_test/_search

{

“query”: {

“has_child”: {

“type”: “child_test”,

“query”: {

“wildcard”: {

“name”: “i am a child”

}

}

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

这是最基础的通过子文档查询父文档,意思是我要搜索一个父文档,这个文档有子文档(has_child),子文档类型为 child_test,并且子文档存在值为 i am a child 的 name 字段。

通过父文档查询子文档也是类似的结构,只是换成了 has_parent 而已。

0x04 多代父子关系问题

简单的单层父子关系肯定无法满足复杂需求,所以 ES 允许多代父子关系(grandchild等)的定义,父辈和祖辈之间按照前面的方式,但此时子辈和父辈之间需要改变一些条件,将子文档的 routing 参数设置为祖辈的 ID,否则有很大的可能导致三代文档不在同一分片上,继而无法通过(has_parent or has_child)语句正确搜索到。

重点来了,为什么不设置 routing 参数,多代父子文档就无法正确被搜索?

要搞清楚这个,首先我们需要了解一下什么是分片,它是 ES 底层的工作单元,它只保存一部分数据,我们的一份文档会被随机发送到一个分片上,一个分片是一个 Lucene 的实例,它本身就是一个完整的搜索引擎,分片只知道自己分片内部发生的事,并不能去操作其它分片,至于统筹分配任务则是 ES 的事。

或许你并没有设置分片数量,但 ES 默认给你设置了 5 个分片,意味着文档将被“随机”存储到这 5 个分片中,是真的随机吗?

我们来大致了解一下当一个文档被索引(存储)的时候,发生了什么事情。

0x05 routing 参数

默认情况下,如果不手动设置 routing 参数,每个文档的 routing 参数值等于 id 的值,然后在文档索引的时候,这个 routing 参数值会通过某个 hash 函数进行整数求值,将得到的整数根据分片数取模运算(假设 routing 值为 2,有 5 个分片):

shardNum = hash(“2”) % 5

1

这样将得到一个 0 到 4 之间的数,这个数就是该文档所在的分片序号,假设得到的值为 3,那么这个文档将在 3 号分片上被索引。

之后的每个文档都将以这种方式进行分片的查找和分配,但想想我们的父子文档,还记得那个前提条件吗,为什么设置 parent 参数以后就保证了父子文档在同一个分片。这里 ES 有一个例外,当存在 parent 参数时,该参数会代替 routing 参数进行分片路由,而 parent 的值又等于父文档 id,所以根据上面的计算公式,父子文档将会肯定被划分在一个分片。

那这里其实存在一个问题,下面这个图中的子辈文档将找不到父辈文档!

难道 ES 不支持多代关系的父子文档?肯定不会的。官方说了,在这个时候,你需要手动多设置一个 routing 参数为祖辈文档 id,来取代 parent(注意只是取代分片路由功能,parent 还用来定义父子关系,不能抛弃!)。当你手动设置了 routing 参数,那么 parent 的分片路由功能也将失效,ES 计算的时候会选取 routing 的值带入 hash 函数中去计算分片序号:

0x06 总结

简单的一层父子关系,设置 parent 参数就够了,当你有多层的复杂关系时,请记得为子文档设置 routing 参数来选择合适的分片路由。

未设置 routing 参数的分片路由优先级: parent > (routing = id)

设置之后: routing > parent

作者:9ian1i

来源:CSDN

原文:https://blog.csdn.net/yanghuan313/article/details/78120900

版权声明:本文为博主原创文章,转载请附上博文链接!

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐