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

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

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

步骤1:定义文档模型

在本应用程序中使用了LeagueCupTeamPlayer 类。 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。 如果同时更新和添加所有树结构,也可以使用嵌套文档。 所有数据结构都有优缺点。 应根据您的要求选择正确的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值