分类:backend

Elasticsearch.Nest 教程系列 6-1 分析:Writing analyzers | 编写分析器


ES 中的“分析”是指将文本(如任何电子邮件的正文)转换为 tokens 或 terms 的过程,这些 tokens 或 terms 被添加到倒排索引中以进行搜索。分析是由分析器执行的,该分析器可以是内置分析器,也可以是自定义的分析器。

分词器由 3 部分组成:

  • Character Filters:处理原始文本,如去除 html。
    • 一般有 0 个或多个
  • Tokenizer:按照规则切分为单词。
    • 一般只有 1 个
  • Token filter:加工切分后的单词,如转小写、删除停用词,增加同义词等。
    • 一般有 0 个或多个

Elasticsearch.Nest 教程系列 5-3 索引:Ingest Pipeline | Ingest 管道


Ingest 管道是一系列按照声明的顺序执行的处理器。

假设有以下 POCO 类:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string IpAddress { get; set; }
    public GeoIp GeoIp { get; set; }
}

public class GeoIp
{
    public string CityName { get; set; }
    public string ContinentName { get; set; }
    public string CountryIsoCode { get; set; }
    public GeoLocation Location { get; set; }
    public string RegionName { get; set; }
}

创建一个 Ingestion 管道:
eg:假设为 Person 文档建立索引,创建一个 Ingestion 管道,在被索引到 ES 之前对其进行相关操作:

  • 将 LastName 转换为大写
  • 将姓名的首字母索引到其他字段—这里用 initials 字段
  • 将 IpAddress 转换为具体地址。

Elasticsearch.Nest 教程系列 5-2 索引:Ingest Node | 使用 Ingest 节点


Ingest Node 的作用

在文档索引发生之前,会使用 Ingest 节点进行预处理文档。Ingest 节点会拦截批量和索引请求,之后应用转换(mapping),然后将文档传递回索引或 Bulk API。

  • 即:在数据被索引之前,通过预定义好的处理管道对数据进行预处理。

默认情况下,所有节点都启用 Ingest,也就是说任何节点都可以处理 Ingest 任务。

  • 当然,你也可以创建某些业务专用的 Ingest 节点。

Elasticsearch 会将自动将索引请求重新路由到 Ingest 节点,而您可以优化此路径。

Elasticsearch.Nest 教程系列 5-1 索引:Indexing | 索引(添加)文档


索引(Indexing):是指讲文档添加到 ES 中的过程,目前 Nest 提供了以下 3 种 Indexing 方式:

  • Indexing Documents
  • Ingest Node
  • Ingest Pipelines

NEST 提供了多种索引文档的方式:

假设有 Person 类:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Elasticsearch.Nest 教程系列 4-7 映射:Multi fields | 多字段映射


在 ES 中,多字段映射是很有用的,如:

  • 一个 string 类型的属性会被推断为 ES 的 包含 keyword 子字段的 text 数据类型,其中:
    • text 数据类型适用于全文搜索。
    • keyword 适用于结构搜索、排序以及聚合。
  • 另外多字段映射适用于不同的分析器,以满足不同的全文本搜索需求。

假设有如下模型类:

public class Person
{
    public string Name { get; set; }
}

Elasticsearch.Nest 教程系列 4-6 映射:Ignoring properties | 映射时忽略 POCO 上的某些属性


POCO 类属性进行映射时,可以通过以下几种方式忽略部分属性

  • 在使用继承了 ElasticsearchPropertyAttributeBase 的各特性上使用 Ignore 属性:如在使用 TextAttribute 时,使用 Ingore 属性进行属性忽略。
  • 在使用 PropertyNameAttribute 的时候使用Ignore属性。
  • 使用 ConnectionSettings.DefaultMappingFor(Func<ClrTypeMappingDescriptor,
    IClrTypeMapping> selector) 方法的时候进行指定需要忽略的属性。
  • 使用能够被 IElasticsearchSerializer 识别的 Ignore 特性,内建的 SourceSerializer 可以使用 [IgnoreAttribute] 进行忽略。

忽略类中的部分属性

示例:

Elasticsearch.Nest 教程系列 9-8 转换:Routing Inference | 路由推断


有的时候,我们在索引文档的时候,会要求指定 routing,否则会报“[routing] is missing for ….”之类的错误,ES 原生直接通过 ?routing 的形式来进行指定,如:

PUT /index/_doc/1?routing=1  
{
    "parentProperty": "a parent prop",
    "id": 1,
    "parentChildRelation": {
        "name":"myparent"
    }
}

那么在 NEST 中,如何指定 routing?NEST 提供了以下几种方法:

隐式转换

以下数据类型会自动进行隐式转换来显式创建路由:

  • Int32
  • Int64
  • String
  • Guid

以上几个数据类型,在被声明为 Routing 类型的 方法/属性使用的时候,会自动进行隐式转换——转换成 Routing 类型,如下:

Elasticsearch.Nest 教程系列 4-5 映射:Parent/Child relationships | 子父级关系映射


如果把 官方示例 放在你本地进行测试,会发现没有问题,但当你连接测试 ES 服务器的时候,就会报各种错误:

  • 这是因为官方使用了 InMemoryConnection ,所有请求并不会真实发送到测试 ES 服务器,规避掉了不少现实问题。以下示例基于官方文档进行了修正,使用本地搭建测试 ES 服务器。

父子关系的数据结构,在日常开发过程中,使用还是比较平凡的:

  • 在 ES 6.x 之前,你可以在一个索引中包含多种 type(类型)。通过给定类型的特殊 _parent 字段映射,可以创建 1对N 关系 的父-子文档。
    • 通过这种方式在索引子项时,你需要传递一个 _parent id 作为路由键(需要确保父项、其子项及所有祖先都保存在同一个分片上)。
  • 但从 ES 6.x 开始,type 不再支持多类型,固定 _type=_doc。(另外在 ES5 的时候,ES 的 index 相当于 DB,_type 相当于 DB 中的表名,但从 ES7 开始,ES 的 index 相当于 DB中的表,_type 固定为 _doc)

嵌套对象和父子结构文档的差异对比和使用建议:

Nested Object Parent/Child
优点 文档存储再一起,读取性能高 父子文档可以独立更新
缺点 更新嵌套的子文档时,需要更新整个文档 需要额外的内存维护关系。读取性能相对差
适用场景 子文档偶尔更新,以查询为主 子文档更新频繁

既然索引不再允许将不同 _type 类型存储在同一索引中,那么如何创建父联接?

Elasticsearch.Nest 教程系列 4-4 映射:through the Visitor Pattern | 通过访问者模式进行映射


通过访问者模式,可以对所有或者特定的属性进行应用转换。

.AutoMap() 方法内部实现了 Visitor Pattern,默认访问者 NoopPropertyVisitor 不执行任何操作,并允许你实现自己的访问方法。

常规使用:

示例:自定义一个访问者,以禁用数字和布尔类型的 doc 值:

public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Salary { get; set; }
    public DateTime Birthday { get; set; }
    public bool IsManager { get; set; }
    public List<Employee> Employees { get; set; }
    public TimeSpan Hours { get; set; }
}

Elasticsearch.Nest 教程系列 4-3 映射:Fluent mapping | 通过 lambda 表达式流畅映射


Fluent mapping 允许你可以最大程度地控制 POCOs 到 ES 字段的映射。你可以认为这种方式类似于 Automapper 中通过 CreateMap<T1, T2>().ForMember 来显式指定属性之间的映射。Fluent mapping 同样需要你显式指定每一个 POCO 属性跟 ES 字段的映射关系。

下面使用 Company 和 Employee 这 2 个 类来进行演示,两者之间的关系如下:

public class Company
{
    public string Name { get; set; }
    public List<Employee> Employees { get; set; }
}

public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Salary { get; set; }
    public DateTime Birthday { get; set; }
    public bool IsManager { get; set; }
    public List<Employee> Employees { get; set; }
    public TimeSpan Hours { get; set; }
}