您的位置:首页 > 其它

ElasticsearchCRUD使用(九)【Elasticsearch父子,孙子节点文件和路由】

2017-05-11 22:27 627 查看
本文介绍如何使用ElasticsearchCRUD在Elasticsearch中创建父,子和孙子文档。 如果创建相互关联的文档,那么文件全部保存到Elasticsearch中的同一个分片很重要。 搜索性能更好,如果可以为搜索定义特定的分片。

当创建父文档和子文档关系时,父定义对于子文档是足够的。 这样可确保将子文档保存到同一分片中。 一旦使用了孙子文档,就需要一个路由定义,否则孙子文档不会总是被保存到同一个分片中,创建子文档的所有优点都将丢失。

步骤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。 如果同时更新和添加所有树结构,也可以使用嵌套文档。 所有数据结构都有优缺点。 应根据您的要求选择正确的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: