ElasticsearchCRUD使用(九)【Elasticsearch父子,孙子节点文件和路由】
2017-05-11 22:27
627 查看
本文介绍如何使用ElasticsearchCRUD在Elasticsearch中创建父,子和孙子文档。 如果创建相互关联的文档,那么文件全部保存到Elasticsearch中的同一个分片很重要。 搜索性能更好,如果可以为搜索定义特定的分片。
当创建父文档和子文档关系时,父定义对于子文档是足够的。 这样可确保将子文档保存到同一分片中。 一旦使用了孙子文档,就需要一个路由定义,否则孙子文档不会总是被保存到同一个分片中,创建子文档的所有优点都将丢失。
不同类型也需要映射定义。 默认情况下,每种类型将被保存到自己的索引中。 这是改变,所以关系中的所有类型都保存到相同的索引:leagues。
具有父映射的创建索引如下发送:
发送第一个子类PUT请求如下所示。 路由仅使用必需属性定义。 不需要其他选项,因为如果使用属性,以下请求将发送到Elasticsearch,然后重新路由,这会导致性能损失。
孙子类映射PUT请求发送如下:
添加文档请求作为批量请求的一部分发送。 ElasticsearchCRUD在bulk请求中发送所有添加,更新和删除请求。 然后可以将不同的请求优化为单个请求。
此请求使用父Id以及路由Id。 因为文档是一级的子级,所以两个id是相同的。
PUT请求再次以bulk 请求发送。 这当然可以与以前的请求一起发送,但是demo目的是单独发送的。
现在索引中存在3个文档,可以从搜索引擎中选择文档。
响应:
当创建父文档和子文档关系时,父定义对于子文档是足够的。 这样可确保将子文档保存到同一分片中。 一旦使用了孙子文档,就需要一个路由定义,否则孙子文档不会总是被保存到同一个分片中,创建子文档的所有优点都将丢失。
步骤1:定义文档模型
在本应用程序中使用了LeagueCup,
Team和
Player类。
LeagueCup类是父类。 它有一个
Team类的列表。
Team类有一个子
Player类的列表。 我们希望将所有文档保存在同一个索引中,并确保将子文件和孙子节文档保存到同一个分片中。 子文档需要
Key属性定义,以便ElasticsearchCrud知道哪个属性被用作
_id定义。
public class LeagueCup { public long Id { get; set; } public string Name { get; set; } public string Description { get; set; } public List<Team> Teams { get; set; } } public class Team { [Key] public long Id { get; set; } public string Name { get; set; } public string Stadium { get; set; } public List<Player> Players { get; set; } } public class Player { [Key] public long Id { get; set; } public string Name { get; set; } public int Goals { get; set; } public int Assists { get; set; } public string Position { get; set; } public int Age { get; set; } }
步骤2:使用正确的映射创建索引
要使用映射创建索引,需要更改ElasticsearchCRUD中上下文的默认配置。ElasticsearchSerializerConfiguration
Config包含所有必需的配置。 我们希望将每个子文档保存为单独的映射或索引类型,并且还可以处理每种类型的所有子文档。 使用
UserDefinedRouting,路由也被强制用于子文档。 这不是默认的,因为如果没有使用孙子文档,这不是必需的。 Elasticsearch中的默认配置将完整的子树保存为嵌套项,处理所有子项,并且不添加路由。
不同类型也需要映射定义。 默认情况下,每种类型将被保存到自己的索引中。 这是改变,所以关系中的所有类型都保存到相同的索引:leagues。
private static readonly IElasticsearchMappingResolver ElasticsearchMappingResolver = new ElasticsearchMappingResolver(); private const bool SaveChildObjectsAsWellAsParent = true; private const bool ProcessChildDocumentsAsSeparateChildIndex = true; private const bool UserDefinedRouting = true; private static readonly ElasticsearchSerializerConfiguration Config = new ElasticsearchSerializerConfiguration(ElasticsearchMappingResolver, SaveChildObjectsAsWellAsParent, ProcessChildDocumentsAsSeparateChildIndex, UserDefinedRouting); private const string ConnectionString = "http://localhost:9200"; static void Main(string[] args) { //定义类型的映射,以便所有使用与父级相同的索引 ElasticsearchMappingResolver.AddElasticSearchMappingForEntityType(typeof(LeagueCup), MappingUtils.GetElasticsearchMapping("leagues")); ElasticsearchMappingResolver.AddElasticSearchMappingForEntityType(typeof(Team), MappingUtils.GetElasticsearchMapping("leagues")); ElasticsearchMappingResolver.AddElasticSearchMappingForEntityType(typeof(Player), MappingUtils.GetElasticsearchMapping("leagues")); CreateIndexWithRouting(); }
CreateIndexWithRouting方法创建一个新的索引,具有三种类型的映射。 context.CreateIndex()在三个不同的PUT请求中执行,每个类型一个。
private static void CreateIndexWithRouting() { //使用路由作为子父关系。 如果您使用孙子文档,则需要这样做。 //如果路由确保孙子文档保存到与父文档相同的分片。 // -------------- //如果仅使用父文档和子文档,则不需要路由。 子文档被保存 //使用父定义与父文档相同的分片。 // -------------- //可以使用配置参数定义路由定义:ElasticsearchSerializerConfiguration中的UserDefinedRouting //var config = new ElasticsearchSerializerConfiguration(ElasticsearchMappingResolver, SaveChildObjectsAsWellAsParent, // ProcessChildDocumentsAsSeparateChildIndex, UserDefinedRouting); using (var context = new ElasticsearchContext(ConnectionString, Config)) { context.TraceProvider = new ConsoleTraceProvider(); //在Elasticsearch中创建索引 //这创建了一个索引`leagues`和3种类型,leaguecup, team, player var ret = context.CreateIndex<LeagueCup>(); } }
具有父映射的创建索引如下发送:
PUT http://localhost:9200/leagues/ HTTP/1.1 Content-Type: application/json Host: localhost:9200 Content-Length: 192 Expect: 100-continue Connection: Keep-Alive { "settings": { "number_of_shards":5, "number_of_replicas":1 }, "mappings": { "leaguecup": { "properties": { "id":{ "type" : "long" }, "name":{ "type" : "string" }, "description":{ "type" : "string" } } } } }
发送第一个子类PUT请求如下所示。 路由仅使用必需属性定义。 不需要其他选项,因为如果使用属性,以下请求将发送到Elasticsearch,然后重新路由,这会导致性能损失。
PUT http://localhost:9200/leagues/team/_mappings HTTP/1.1 Content-Type: application/json Host: localhost:9200 Content-Length: 174 Expect: 100-continue { "team": { "_parent": { "type":"leaguecup" }, "_routing": { "required":"true" }, "properties": { "id": { "type" : "long" }, "name":{ "type" : "string" }, "stadium":{ "type" : "string" } } } }
孙子类映射PUT请求发送如下:
PUT http://localhost:9200/leagues/player/_mappings HTTP/1.1 Content-Type: application/json Host: localhost:9200 Content-Length: 265 Expect: 100-continue { "player": { "_parent":{"type":"team"}, "_routing":{"required":"true"}, "properties":{"id":{ "type" : "long" }, "name":{ "type" : "string" }, "goals":{ "type" : "integer" }, "assists":{ "type" : "integer" }, "position":{ "type" : "string" }, "age":{ "type" : "integer" } } } }
步骤3:添加LeagueCup文档
现在索引和类型映射存在,可以添加一个新的LeagueCup文件。private static long CreateNewLeague() { var swissCup = new LeagueCup {Description = "Nataional Cup Switzerland", Id = 1, Name = "Swiss Cup"}; using (var context = new ElasticsearchContext(ConnectionString, Config)) { context.TraceProvider = new ConsoleTraceProvider(); context.AddUpdateDocument(swissCup, swissCup.Id); context.SaveChanges(); } return swissCup.Id; }
添加文档请求作为批量请求的一部分发送。 ElasticsearchCRUD在bulk请求中发送所有添加,更新和删除请求。 然后可以将不同的请求优化为单个请求。
context.SaveChanges()发送所有待处理的请求。
POST http://localhost:9200/_bulk HTTP/1.1 Content-Type: application/json Host: localhost:9200 Content-Length: 131 Expect: 100-continue {"index":{"_index":"leagues","_type":"leaguecup","_id":"1"}} {"id":1,"name":"Swiss Cup","description":"Nataional Cup Switzerland"}
步骤4:添加Team文档
team 请求使用父类LeagueCup的父ID发送。
/// <summary> /// parentId是父对象的id /// Elasticsearch需要路由标识,强制所有子对象都保存到同一个分片。 这对性能有好处。 /// 因为这是一个第一级的子级,所以routingId和parentId是一样的。 /// </summary> private static long AddTeamToCup(long leagueId) { var youngBoys = new Team {Id=2,Name="Young Boys", Stadium="Wankdorf Bern"}; using (var context = new ElasticsearchContext(ConnectionString, Config)) { context.TraceProvider = new ConsoleTraceProvider(); context.AddUpdateDocument(youngBoys, youngBoys.Id, new RoutingDefinition { ParentId = leagueId, RoutingId = leagueId }); context.SaveChanges(); } return youngBoys.Id; }
此请求使用父Id以及路由Id。 因为文档是一级的子级,所以两个id是相同的。
POST http://localhost:9200/_bulk HTTP/1.1 Content-Type: application/json Host: localhost:9200 Content-Length: 136 Expect: 100-continue {"index":{"_index":"leagues","_type":"team","_id":"2","_parent":1,"_routing":1}} {"id":2,"name":"Young Boys","stadium":"Wankdorf Bern"}
步骤5:添加Player文档
然后可以将player添加到
team父级的索引中,并将路由添加到
leagueCup顶级父级。
static void AddPlayerToTeam(long teamId, long leagueId) { var yvonMvogo = new Player { Id = 3, Name = "Yvon Mvogo", Age = 20, Goals = 0, Assists = 0, Position = "Goalkeeper" }; using (var context = new ElasticsearchContext(ConnectionString, Config)) { context.TraceProvider = new ConsoleTraceProvider(); context.AddUpdateDocument(yvonMvogo, yvonMvogo.Id, new RoutingDefinition { ParentId = teamId, RoutingId = leagueId }); context.SaveChanges();
PUT请求再次以bulk 请求发送。 这当然可以与以前的请求一起发送,但是demo目的是单独发送的。
POST http://localhost:9200/_bulk HTTP/1.1 Content-Type: application/json Host: localhost:9200 Content-Length: 167 Expect: 100-continue {"index":{"_index":"leagues","_type":"player","_id":"3","_parent":2,"_routing":1}} {"id":3,"name":"Yvon Mvogo","goals":0,"assists":0,"position":"Goalkeeper","age":20}
现在索引中存在3个文档,可以从搜索引擎中选择文档。
player文档的
GET请求需要父Id和路由Id。
private static Player GetPlayer(long playerId, long leagueId, long teamId) { Player player; using (var context = new ElasticsearchContext(ConnectionString, Config)) { context.TraceProvider = new ConsoleTraceProvider(); player = context.GetDocument<Player>(playerId, new RoutingDefinition { ParentId = teamId, RoutingId = leagueId }); } return player; }
GetPlayer请求如下发送:
GET http://localhost:9200/leagues/player/3?parent=2&routing=1 HTTP/1.1 Host: localhost:9200
响应:
HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Content-Length: 167 { "_index":"leagues", "_type":"player", "_id":"3","_version":1, "found":true," _source": { "id":3, "name": "Yvon Mvogo", "goals":0, "assists":0, "position":"Goalkeeper", "age":20 } }
结论:
在Elasticsearch中定义和使用子文档和孙子文档非常简单。 如果要优化搜索性能,则需要将文档保存到同一个分片。 这是通过路由实现的。 如果只使用父文件和子文档,则只需要父Id。 如果同时更新和添加所有树结构,也可以使用嵌套文档。 所有数据结构都有优缺点。 应根据您的要求选择正确的。相关文章推荐
- ElasticsearchCRUD使用(十四)【ElasticsearchCRUD搜索查询和过滤】
- ElasticsearchCRUD使用(十二)【Elasticsearch的German分析器】
- 使用elasticsearch,Elasticsearch Scripts disabled
- 搭建ELK(ElasticSearch+Logstash+Kibana)日志分析系统(七) elasticsearch.yml配置文件详细说明
- Elasticsearch(二)【Elasticsearch.Net基本使用】
- [增删改查] SpringBoot 整合 ElasticSearch 之 ElasticsearchRepository 的 CRUD、分页接口
- Elasticsearch——使用_cat查看Elasticsearch状态
- ElasticsearchCRUD使用(十五)【批量插入数据】
- SpringBoot项目通过 spring data elasticsearch使用elasticsearch
- ElasticSearch学习13_ElasticSearch RESTful搜索引擎_Java Jest使用入门
- elasticsearch-1.5.2生产环境配置文件(Elasticsearch.xml)集群配置
- ElasticSearch ik,elasticsearch-jdbc 使用 和 yii2 实例
- js中使用DOM复制(克隆)指定节点名数据到新的XML文件中的代码
- 在Mac OS X中使用mtr诊断路由节点问题
- 使用logstash+elasticsearch+kibana快速搭建日志平台
- 使用logstash+elasticsearch+kibana快速搭建日志平台
- 使用Logstash + Elasticsearch作为大数据索引、分析工具
- 使用windows search 搜索文件和文件夹(一)
- 使用 Docker 部署和迁移多节点的 ElasticSearch-Logstash-Kibana 集群
- 使用windows search 搜索文件和文件夹(一)