es 父子查询_ElasticSearch 父子文档及多代父子文档(grandchild)解析之爸爸去哪儿了...
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
版权声明:本文为博主原创文章,转载请附上博文链接!