三、父子关系数据建模
nested object的建模,有个不好的地方,就是采取的是类似冗余数据的方式,将多个数据都放在一起了,维护成本就比较高
每次更新,需要重新索引整个对象(包括跟对象和嵌套对象)
ES 提供了类似关系型数据库中 Join 的实现。使用 Join 数据类型实现,可以通过
Parent / Child 的关系,从而分离两个对象
父文档和子文档是两个独立的文档
更新父文档无需重新索引整个子文档。子文档被新增,更改和删除也不会影响到父文档和其他子文档。要点:父子关系元数据映射,用于确保查询时候的高性能,但是有一个限制,就是父子数据必须存在于一个shard中
父子关系数据存在一个shard中,而且还有映射其关联关系的元数据,那么搜索父子关系数据的时候,不用跨分片,一个分片本地自己就搞定了,性能当然高
父子关系
定义父子关系的几个步骤
- 设置索引的 Mapping
- 索引父文档
- 索引子文档
- 按需查询文档
设置 Mapping
DELETE my_blogs # 设定 Parent/Child Mapping PUT my_blogs { "mappings": { "properties": { "blog_comments_relation": { "type": "join", "relations": { "blog": "comment" } }, "content": { "type": "text" }, "title": { "type": "keyword" } } } }
索引父文档
PUT my_blogs/_doc/blog1 { "title":"Learning Elasticsearch", "content":"learning ELK is happy", "blog_comments_relation":{ "name":"blog" } } PUT my_blogs/_doc/blog2 { "title":"Learning Hadoop", "content":"learning Hadoop", "blog_comments_relation":{ "name":"blog" } }
索引子文档
**父文档和子文档必须存在相同的分片上 **
确保查询 join 的性能
当指定文档时候,必须指定它的父文档 ID
**使用 route 参数来保证,分配到相同的分片 **
#索引子文档
PUT my_blogs/_doc/comment1?routing=blog1 { "comment":"I am learning ELK", "username":"Jack", "blog_comments_relation":{ "name":"comment", "parent":"blog1" } } PUT my_blogs/_doc/comment2?routing=blog2 { "comment":"I like Hadoop!!!!!", "username":"Jack", "blog_comments_relation":{ "name":"comment", "parent":"blog2" } } PUT my_blogs/_doc/comment3?routing=blog2 { "comment":"Hello Hadoop", "username":"Bob", "blog_comments_relation":{ "name":"comment", "parent":"blog2" } }
Parent / Child 所支持的查询
- 查询所有文档
- Parent Id 查询
- Has Child 查询
- Has Parent 查询
1 # 查询所有文档
POST my_blogs/_search {} #根据父文档ID查看 GET my_blogs/_doc/blog2 # Parent Id 查询 POST my_blogs/_search { "query": { "parent_id": { "type": "comment", "id": "blog2" } } } # Has Child 查询,返回父文档 POST my_blogs/_search { "query": { "has_child": { "type": "comment", "query" : { "match": { "username" : "Jack" } } } } } # Has Parent 查询,返回相关的子文档 POST my_blogs/_search { "query": { "has_parent": { "parent_type": "blog", "query" : { "match": { "title" : "Learning Hadoop" } } } } }
使用 has_child 查询
返回父文档
通过对子文档进行查询
返回具体相关子文档的父文档
父子文档在相同的分片上,因此 Join 效率高
使用 has_parent 查询
返回相关性的子文档
通过对父文档进行查询
返回相关的子文档
使用 parent_id 查询
返回所有相关子文档
通过对付文档 Id 进行查询
返回所有相关的子文档
访问子文档
需指定父文档 routing 参数
#通过ID ,访问子文档 GET my_blogs/_doc/comment2 #通过ID和routing ,访问子文档 GET my_blogs/_doc/comment3?routing=blog2
更新子文档
更新子文档不会影响到父文档
#更新子文档
PUT my_blogs/_doc/comment3?routing=blog2 { "comment": "Hello Hadoop??", "blog_comments_relation": { "name": "comment", "parent": "blog2" } }
嵌套对象 v.s 父子文档
Nested Object Parent / Child
**优点:文档存储在一起,读取性能高、父子文档可以独立更新 **
**缺点:更新嵌套的子文档时,需要更新整个文档、需要额外的内存去维护关系。读取性能 **
**相对差 **
适用场景子文档偶尔更新,以查询为主、子文档更新频繁
四、文件系统数据建模
思考一下,github中可以使用代码片段来实现数据搜索。这是如何实现的?
在github中也使用了ES来实现数据的全文搜索。其ES中有一个记录代码内容的索引,大致数据内容如下:
{ "fileName" : "HelloWorld.java", "authName" : "hxl", "authID" : 110, "productName" : "first‐java", "path" : "/com/hxl/first", "content" : "package com.hxl.first; public class HelloWorld { //code... }" }
我们可以在github中通过代码的片段来实现数据的搜索。也可以使用其他条件实现数据搜索。但是,如果需要使用文件路径搜索内容应该如何实现?这个时候需要为其中的字段path定义一个特殊的分词器。具体如下:
创建 mapping
PUT /codes { "settings": { "analysis": { "analyzer": { "path_analyzer" : { "tokenizer" : "path_hierarchy" } } } }, "mappings": { "properties": { "fileName" : { "type" : "keyword" }, "authName" : { "type" : "text", "analyzer": "standard", "fields": { "keyword" : { "type" : "keyword" } } }, "authID" : { "type" : "long" }, "productName" : { "type" : "text", "analyzer": "standard", "fields": { "keyword" : { "type" : "keyword" } } }, "path" : { "type" : "text", "analyzer": "path_analyzer", "fields": { "keyword" : { "type" : "keyword" } } }, "content" : { "type" : "text", "analyzer": "standard" } } } } PUT /codes/_doc/1 { "fileName" : "HelloWorld.java", "authName" : "hxl", "authID" : 110, "productName" : "first‐java", "path" : "/com/hxl/first", "content" : "package com.hxl.first; public class HelloWorld { // some code... }" } GET /codes/_search { "query": { "match": { "path": "/com" } } } GET /codes/_analyze { "text": "/a/b/c/d", "field": "path" }
数据操作
PUT /codes { "settings": { "analysis": { "analyzer": { "path_analyzer" : { "tokenizer" : "path_hierarchy" } } } }, "mappings": { "properties": { "fileName" : { "type" : "keyword" }, "authName" : { "type" : "text", "analyzer": "standard", "fields": { "keyword" : { "type" : "keyword" } } }, "authID" : { "type" : "long" }, "productName" : { "type" : "text", "analyzer": "standard", "fields": { "keyword" : { "type" : "keyword" } } }, "path" : { "type" : "text", "analyzer": "path_analyzer", "fields": { "keyword" : { "type" : "text", "analyzer": "standard" } } }, "content" : { "type" : "text", "analyzer": "standard" } } } } GET /codes/_search { "query": { "match": { "path.keyword": "/com" } } } GET /codes/_search { "query": { "bool": { "should": [ { "match": { "path": "/com" } }, { "match": { "path.keyword": "/com/hxl" } } ] } } }
参考文档:www.elastic.co/guide/en/el…
pathhierarchy-tokenizer.html