理解elasticsearch的parent-child关系

前面文章介绍了,在es里面的几种数据组织关系,包括array[object],nested,以及今天要说的Parent-Child。

Parent-Child与Nested非常类似,都可以用来处理一对多的关系,如果多对多的关系,那就拆分成一对多在处理。前面提到nested的缺点是对数据的更新需要reindex整个nested结构下的所有数据,所以注定了它的使用场景一定是查询多更新少的场景,如果是更新多的场景,那么nested的性能未必会很好,而Parent-Child就非常适合在更新多的场景,因为Parent-Child的数据存储都是独立的,只要求父子文档都分布在同一个shard里面即可而nested模式下,不仅要求在同一个shard下还必须是同一个sengment里面的同一个block下,这种模式注定了nested查询的性能要比Parent-Child好,但是更新性能就大大不如Parent-Child了,对比nested模式,Parent-Child主要有下面的几个特点:

(1) 父文档可以被更新,而无须重建所有的子文档

(2)子文档的添加,修改,或者删除不影响它的父文档和其他的子文档,这尤其是在子文档数量巨大而且需要被添加和更新频繁的场景下Parent-Child能获取更好的性能

(3)子文档可以被返回在搜索结果里面

ElasticSearch在内存里面维护了一个父子关系的映射表,以便于能够加速查询,这种映射使用的是doc-value,如果数据量巨大内存放不下,会自动的保存到磁盘中,当然此时性能也会下降。

下面来看一个例子,首先我们要定义mapping:

{
  "order": 0,
  "template": "pc_test*",
  "settings": {
    "index": {
      "number_of_replicas": "0",
      "number_of_shards": "3"
    }
  },
  "mappings": {
    "employee": {
      "_parent": {
        "type": "branch"
      }
    },
    "branch": {}
  },
  "aliases": {}
}

branch:代表一个分公司

employee:代表员工

关系: 一个公司可以包含多个员工

下面开始插入数据,首先我们先插入公司数据:

POST /company/branch/_bulk
{ "index": { "_id": "london" }}
{ "name": "London Westminster", "city": "London", "country": "UK" }
{ "index": { "_id": "liverpool" }}
{ "name": "Liverpool Central", "city": "Liverpool", "country": "UK" }
{ "index": { "_id": "paris" }}
{ "name": "Champs Élysées", "city": "Paris", "country": "France" }

注意插入公司数据的type是branch,数据的id用的是city字段,

添加员工数据的时候,要指定的父文档是属于哪个,这样才能把父子数据给关联到同一台机器上。

PUT /company/employee/1?parent=london 
{
  "name":  "Alice Smith",
  "dob":   "1970-10-24",
  "hobby": "hiking"
}

parent id字段有两个用途:

(1)它创建了连接父子文档的关系并且确保了子文档一定和父文档存在一个shard里面

(2)默认情况下es用的是文档的id字段进行hash取模分片的,如果父文档的id字段被指定,那么路由字段就是id,而在子文档中我们指定parent的值也是父文档的id字段,所以就一定确保了父子文档都在一个shard里面,在父子文档的关系中,index,update,add,delete包括search在使用的时候都必须设置路由字段,否则查询结果会出错。

继续插入子文档:

POST /company/employee/_bulk
{ "index": { "_id": 2, "parent": "london" }}
{ "name": "Mark Thomas", "dob": "1982-05-16", "hobby": "diving" }
{ "index": { "_id": 3, "parent": "liverpool" }}
{ "name": "Barry Smith", "dob": "1979-04-01", "hobby": "hiking" }
{ "index": { "_id": 4, "parent": "paris" }}
{ "name": "Adrien Grand", "dob": "1987-05-11", "hobby": "horses" }

注意:如果parent的值改变了,必须删除这个parent下面的所有子文档然后删除本身,最后添加新的父文档,再添加新的子文档,否则parent值改变后,父文档的parent改变了,子的没改变会出现父子不在同一个shard里面,从而导致查询出错。

下面来看下,如何查询父子关系的数据,这里面主要有两个查询方法:

(1)has_child

使用子文档的字段当成查询条件,查询出符合条件的父文档的数据

一个查询例子如下:

GET /company/branch/_search
{
  "query": {
    "has_child": {
      "type": "employee",
      "query": {
        "range": {
          "dob": {
            "gte": "1980-01-01"
          }
        }
      }
    }
  }
}

这里面关于父文档的score,是由所有子文档的评分通过一个计算方法得来的,这里可以设置,有5种策略:

none:忽略评分 avg:所有子文档的平均分 min:所有子文档的最小分 max:所有子文档的最大分 sum:所有子文档的得分和

通过下面的查询,可以看出评分对排序的影响:

GET /company/branch/_search
{
  "query": {
    "has_child": {
      "type":       "employee",
      "score_mode": "max",
      "query": {
        "match": {
          "name": "Alice Smith"
        }
      }
    }
  }
}

得分设置为none拥有更快的查询性能,因为少了额外的计算

此外has_child查询还可以接受两个限制参数min_children和max_children,在查询的时候根据子文档的个数做过滤,看下面的一个例子:

GET /company/branch/_search
{
  "query": {
    "has_child": {
      "type":         "employee",
      "min_children": 2, 
      "query": {
        "match_all": {}
      }
    }
  }
}

上面的查询仅仅查询最子文档个数符合过滤条件的父文档,has_child也可以使用filter查询。

(2)has_parent

has_parent查询和has_child相反,通过查询父文档的字段,从而得到子文档的数据。

一个例子如下:

GET /company/employee/_search
{
  "query": {
    "has_parent": {
      "type": "branch", 
      "query": {
        "match": {
          "country": "UK"
        }
      }
    }
  }
}

has_parent也支持score_mode,有两种设置一个none,一个score因为每个child只有一个parent,所以不需要做聚合的评分。

最后看下parent-child的聚合,一个例子:

GET /company/branch/_search
{
  "size" : 0,
  "aggs": {
    "country": {
      "terms": { 
        "field": "country"
      },
      "aggs": {
        "employees": {
          "children": { 
            "type": "employee"
          },
          "aggs": {
            "hobby": {
              "terms": { 
                "field": "hobby"
              }
            }
          }
        }
      }
    }
  }
}

上面聚合的意思是:

按国家分组,然后算组内的员工再根据其爱好进行分组

最后,parent-child模式,支持多层的关系

一个对多对多,目前官网上给出了3层关系的例子,从社区上来看说是支持无限层级的关系映射,但是超过3层的映射,官网没有给出使用例子,具体的使用还得使用者去测试,不过现实情况包含3级以上的关系数据应该非常少了。

一个的3级例子的mapping:

PUT /company
{
  "mappings": {
    "country": {},
    "branch": {
      "_parent": {
        "type": "country" 
      }
    },
    "employee": {
      "_parent": {
        "type": "branch" 
      }
    }
  }
}

多了一级国家的映射,总体的关系是:

一个国家可以有多个分公司,每个分公司又可以有多个员工

看下,数据例子:

(1)先插入国家数据

POST /company/country/_bulk
{ "index": { "_id": "uk" }}
{ "name": "UK" }
{ "index": { "_id": "france" }}
{ "name": "France" }

(2)在插入公司数据

POST /company/branch/_bulk
{ "index": { "_id": "london", "parent": "uk" }}
{ "name": "London Westmintster" }
{ "index": { "_id": "liverpool", "parent": "uk" }}
{ "name": "Liverpool Central" }
{ "index": { "_id": "paris", "parent": "france" }}
{ "name": "Champs Élysées" }

注意parent是父的,公司的route用的是city

(3)插入员工数据

PUT /company/employee/1?parent=london&routing=uk 
{
  "name":  "Alice Smith",
  "dob":   "1970-10-24",
  "hobby": "hiking"
}

第三层的插入数据用了parent字段来确保和父文档的关联,又用了routding字段来确保和父文档,祖父文档位于同一个shard里面。

注意如果超过3层,routing字段一定最顶层的文档的路由值,而parent字段则是其真正的关联的父文档。超过3层的映射官网没有给出例子,具体是否是那样用的,有兴趣的朋友可以自行测试,多层的父子关系会消耗更多的内存,以及性能更糟糕所以设计上应该尽量避免出现这种情况,此外如果非得设计,注意parent id字段应该尽量短的,从而在doc value中获的更好的压缩以减少使用的内存。

https://discuss.elastic.co/t/would-it-be-possible-the-relation-grate-grandparent-grate-grandchild-in-elasticsearch/26875/4

前面文章介绍了,在es里面的几种数据组织关系,包括array[object],nested,以及今天要说的Parent-Child。

Parent-Child与Nested非常类似,都可以用来处理一对多的关系,如果多对多的关系,那就拆分成一对多在处理。前面提到nested的缺点是对数据的更新需要reindex整个nested结构下的所有数据,所以注定了它的使用场景一定是查询多更新少的场景,如果是更新多的场景,那么nested的性能未必会很好,而Parent-Child就非常适合在更新多的场景,因为Parent-Child的数据存储都是独立的,只要求父子文档都分布在同一个shard里面即可而nested模式下,不仅要求在同一个shard下还必须是同一个sengment里面的同一个block下,这种模式注定了nested查询的性能要比Parent-Child好,但是更新性能就大大不如Parent-Child了,对比nested模式,Parent-Child主要有下面的几个特点:

(1) 父文档可以被更新,而无须重建所有的子文档

(2)子文档的添加,修改,或者删除不影响它的父文档和其他的子文档,这尤其是在子文档数量巨大而且需要被添加和更新频繁的场景下Parent-Child能获取更好的性能

(3)子文档可以被返回在搜索结果里面

ElasticSearch在内存里面维护了一个父子关系的映射表,以便于能够加速查询,这种映射使用的是doc-value,如果数据量巨大内存放不下,会自动的保存到磁盘中,当然此时性能也会下降。

下面来看一个例子,首先我们要定义mapping:

{
  "order": 0,
  "template": "pc_test*",
  "settings": {
    "index": {
      "number_of_replicas": "0",
      "number_of_shards": "3"
    }
  },
  "mappings": {
    "employee": {
      "_parent": {
        "type": "branch"
      }
    },
    "branch": {}
  },
  "aliases": {}
}

branch:代表一个分公司

employee:代表员工

关系: 一个公司可以包含多个员工

下面开始插入数据,首先我们先插入公司数据:

POST /company/branch/_bulk
{ "index": { "_id": "london" }}
{ "name": "London Westminster", "city": "London", "country": "UK" }
{ "index": { "_id": "liverpool" }}
{ "name": "Liverpool Central", "city": "Liverpool", "country": "UK" }
{ "index": { "_id": "paris" }}
{ "name": "Champs Élysées", "city": "Paris", "country": "France" }

注意插入公司数据的type是branch,数据的id用的是city字段,

添加员工数据的时候,要指定的父文档是属于哪个,这样才能把父子数据给关联到同一台机器上。

PUT /company/employee/1?parent=london 
{
  "name":  "Alice Smith",
  "dob":   "1970-10-24",
  "hobby": "hiking"
}

parent id字段有两个用途:

(1)它创建了连接父子文档的关系并且确保了子文档一定和父文档存在一个shard里面

(2)默认情况下es用的是文档的id字段进行hash取模分片的,如果父文档的id字段被指定,那么路由字段就是id,而在子文档中我们指定parent的值也是父文档的id字段,所以就一定确保了父子文档都在一个shard里面,在父子文档的关系中,index,update,add,delete包括search在使用的时候都必须设置路由字段,否则查询结果会出错。

继续插入子文档:

POST /company/employee/_bulk
{ "index": { "_id": 2, "parent": "london" }}
{ "name": "Mark Thomas", "dob": "1982-05-16", "hobby": "diving" }
{ "index": { "_id": 3, "parent": "liverpool" }}
{ "name": "Barry Smith", "dob": "1979-04-01", "hobby": "hiking" }
{ "index": { "_id": 4, "parent": "paris" }}
{ "name": "Adrien Grand", "dob": "1987-05-11", "hobby": "horses" }

注意:如果parent的值改变了,必须删除这个parent下面的所有子文档然后删除本身,最后添加新的父文档,再添加新的子文档,否则parent值改变后,父文档的parent改变了,子的没改变会出现父子不在同一个shard里面,从而导致查询出错。

下面来看下,如何查询父子关系的数据,这里面主要有两个查询方法:

(1)has_child

使用子文档的字段当成查询条件,查询出符合条件的父文档的数据

一个查询例子如下:

GET /company/branch/_search
{
  "query": {
    "has_child": {
      "type": "employee",
      "query": {
        "range": {
          "dob": {
            "gte": "1980-01-01"
          }
        }
      }
    }
  }
}

这里面关于父文档的score,是由所有子文档的评分通过一个计算方法得来的,这里可以设置,有5种策略:

none:忽略评分 avg:所有子文档的平均分 min:所有子文档的最小分 max:所有子文档的最大分 sum:所有子文档的得分和

通过下面的查询,可以看出评分对排序的影响:

GET /company/branch/_search
{
  "query": {
    "has_child": {
      "type":       "employee",
      "score_mode": "max",
      "query": {
        "match": {
          "name": "Alice Smith"
        }
      }
    }
  }
}

得分设置为none拥有更快的查询性能,因为少了额外的计算

此外has_child查询还可以接受两个限制参数min_children和max_children,在查询的时候根据子文档的个数做过滤,看下面的一个例子:

GET /company/branch/_search
{
  "query": {
    "has_child": {
      "type":         "employee",
      "min_children": 2, 
      "query": {
        "match_all": {}
      }
    }
  }
}

上面的查询仅仅查询最子文档个数符合过滤条件的父文档,has_child也可以使用filter查询。

(2)has_parent

has_parent查询和has_child相反,通过查询父文档的字段,从而得到子文档的数据。

一个例子如下:

GET /company/employee/_search
{
  "query": {
    "has_parent": {
      "type": "branch", 
      "query": {
        "match": {
          "country": "UK"
        }
      }
    }
  }
}

has_parent也支持score_mode,有两种设置一个none,一个score因为每个child只有一个parent,所以不需要做聚合的评分。

最后看下parent-child的聚合,一个例子:

GET /company/branch/_search
{
  "size" : 0,
  "aggs": {
    "country": {
      "terms": { 
        "field": "country"
      },
      "aggs": {
        "employees": {
          "children": { 
            "type": "employee"
          },
          "aggs": {
            "hobby": {
              "terms": { 
                "field": "hobby"
              }
            }
          }
        }
      }
    }
  }
}

上面聚合的意思是:

按国家分组,然后算组内的员工再根据其爱好进行分组

最后,parent-child模式,支持多层的关系

一个对多对多,目前官网上给出了3层关系的例子,从社区上来看说是支持无限层级的关系映射,但是超过3层的映射,官网没有给出使用例子,具体的使用还得使用者去测试,不过现实情况包含3级以上的关系数据应该非常少了。

一个的3级例子的mapping:

PUT /company
{
  "mappings": {
    "country": {},
    "branch": {
      "_parent": {
        "type": "country" 
      }
    },
    "employee": {
      "_parent": {
        "type": "branch" 
      }
    }
  }
}

多了一级国家的映射,总体的关系是:

一个国家可以有多个分公司,每个分公司又可以有多个员工

看下,数据例子:

(1)先插入国家数据

POST /company/country/_bulk
{ "index": { "_id": "uk" }}
{ "name": "UK" }
{ "index": { "_id": "france" }}
{ "name": "France" }

(2)在插入公司数据

POST /company/branch/_bulk
{ "index": { "_id": "london", "parent": "uk" }}
{ "name": "London Westmintster" }
{ "index": { "_id": "liverpool", "parent": "uk" }}
{ "name": "Liverpool Central" }
{ "index": { "_id": "paris", "parent": "france" }}
{ "name": "Champs Élysées" }

注意parent是父的,公司的route用的是city

(3)插入员工数据

PUT /company/employee/1?parent=london&routing=uk 
{
  "name":  "Alice Smith",
  "dob":   "1970-10-24",
  "hobby": "hiking"
}

第三层的插入数据用了parent字段来确保和父文档的关联,又用了routding字段来确保和父文档,祖父文档位于同一个shard里面。

注意如果超过3层,routing字段一定最顶层的文档的路由值,而parent字段则是其真正的关联的父文档。超过3层的映射官网没有给出例子,具体是否是那样用的,有兴趣的朋友可以自行测试,多层的父子关系会消耗更多的内存,以及性能更糟糕所以设计上应该尽量避免出现这种情况,此外如果非得设计,注意parent id字段应该尽量短的,从而在doc value中获的更好的压缩以减少使用的内存。

https://discuss.elastic.co/t/would-it-be-possible-the-relation-grate-grandparent-grate-grandchild-in-elasticsearch/26875/4


  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
视频资源太大,这里提供百度云链接: 资源包括项目源码和所需的数据: 01-1 _课程导学~1.mp4 01-2 说明和建议~1.mp4 02-1 -术语介绍 .mp4 02-2 Document介绍.mp4 02-3 index介绍 .mp4 02-4 -restapi介绍 .mp4 02-5 -index_api .mp4 02-6 -document_api.mp4 03-01 -书的目录与索引.mp4 03-02 -正排与倒排索引简介.mp4 03-03 -倒排索引详解.mp4 03-04 -分词介绍.mp4 03-05 -analyze_api .mp4 03-06 -自带分词器.mp4 03-07 -中文分词.mp4 03-08 -自定义分词之CharacterFilter .mp4 03-09 自定义分词之Tokenizer .mp4 03-10 -自定义分词之 TokenFilter .mp4 03-11 -自定义分词.mp4 03-12 -分词使用说明 .mp4 03-13 -官方文档说明.mp4 04-01 -mapping简介.avi 04-02 -自定义 mapping .avi 04-03 -mapping演示.avi 04-04 -copy_to参数说明.avi 04-05 -index参数说明.avi 04-06 -index_options参数说明.avi 04-07 -mapping文档说明.avi 04-08 -数据类型.avi 04-09 -dynamic-mapping简介.avi 04-10 -dynamic日期与数字识别.avi 04-11 -dynamic-template简介.avi 04-12 -自定义mapping的建议.avi 04-13 -索引模板.mp4.avi 05-01 -SearchAPI概览.avi 05-02 -URISearch详解与演示.avi 05-03 -QueryDSL简介.avi 05-04 -字段类查询简介及match-query.avi 05-05 -相关性算分.mp4.avi 05-06 -match-phrase-query_音频.mp4.avi 05-07 -query-string-query.avi 05-08 -simple-query-string-query.avi 05-09 -term-terms-query.avi 05-10 -range-query.avi 05-11 -复合查询介绍及ConstantScore.avi 05-12 -bool-query.avi 05-13 -count-and-source-filtering.avi 06-01 -分布式介绍及cerebro.avi 06-02 -构建集群.avi 06-03 -副本与分片.avi 06-04 -两个问题.avi 06-05 -集群状态.avi 06-06 -故障转移.mp4.avi 06-07 -文档分布式存储.avi 06-08 -脑裂问题.avi 06-09 -shard详解.avi 07-1 -Query-Then-Fetch.avi 07-2 -相关性算分.avi 07-3 -sorting-doc-values-fielddata.avi 07-4 -分页与遍历-fromsize.avi 07-5 分页与遍历.avi 07-6 分页与遍历-search_after.avi 07-7 文档说明.mp4.avi 08-1 -聚合分析简介.avi 08-2 -metric聚合分析.avi 08-3 -bucket聚合分析.avi 08-4 -bucket和metric聚合分析.avi 08-5 -pipeline聚合分析.avi 08-6 -作用范围.avi 08-7 -排序.avi 08-8 -原理与精准度问题.avi 08-9 -文档说明.avi 09-1 -数据建模简介.avi 09-2 -ES数据建模配置相关介绍.avi 09-3 -ES数据建模实例.mp4.avi 09-4 -Nested_Object.avi 09-5 -Parent_Child.avi 09-6 -nested_vs_parent_child.avi 09-7 -reindex.avi 09-8 其他建议.avi 10-1 生产环境部署建议.avi 10-2 写性能优化.avi 10-3 读性能优化.avi 10-4 如何设定shard数.avi 10-5 xpack监控功能介绍.avi 11-1 入门及架构简介.avi 11-2 -Life_of_an_Event.avi 11-3 -queue简介.avi 11-4 -线程简介.avi 11-5 配置简介.avi 11-6 多实例运行.avi 11-7 pipeline配置简介.avi 12-01 input插件详解及glob讲解.avi 12-02 -codec插件详解.avi 12-03 filter插件简介及date插件讲解.avi 12-04 filter插件之grok简介(上).avi 12-05 filter插件之grok简介(下).avi 12-06 filter插件之dissect讲解.avi 12-07 filter插件之mutate 讲解.avi 12-08 filter插件之 json讲解.avi 12-09 filter 插件之geoip和ruby 讲解.avi 12-10 output插件简介.avi 12-11 文档说明.avi 123.bat 13-1 -Logstash实战建议.avi 13-2 -实战之apacheLogs(上).avi 13-3 实战之apacheLogs(下).avi 13-4 实战之csv.avi 13-5 监控运维建议.avi 14-1 beats简介.avi 14-2 Filebeat_Demo.avi 14-3 Filebeat 简介及流程介绍.avi 14-4 Filebeat常见架构及ingest_node介绍.avi 14-5 Filebeat_Module简介.avi 15-1 -简介.avi 15-2 -Module简介.avi 15-3 -实战.mp4.avi 16-1 1-简介(1).avi 16-1 1-简介(1).avi.baiduyun.downloading 16-1 1-简介.avi 16-2 2-实战.avi 17-1 1-Heartbeat.avi 17-2 2-Community_beats.avi 18-1 -配置与线上部署建议.avi 18-2 -Index_Pattern_Objects_Settings使用.avi 19-1 -导入数据.avi 19-2 -Discover实战.avi 20-1 -可视化简介.avi 20-2 -Basic_Charts_介绍.avi 20-3 -Basic_Charts_其他说明.avi 20-4 -Data图表介绍.avi 20-5 -Map图表介绍.avi 20-6 -Timelion介绍.avi 20-7 -VisualBuilder介绍.avi 20-8 -other图表介绍.avi 20-9 -Dashboard介绍.avi 21-1 -项目介绍.avi 21-2 项目实战.avi 22-1 介绍和数据导入.avi 22-2 -实战.avi 23-1 项目简介.avi 23-2 实战(上).avi 23-3 实战(下).avi 24-1 课程总结.avi codes.zip project.zip 文件树.txt

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值