ElasticSearch 索引

1. 索引管理

Elasticsearch 索引管理主要包括如何进行索引的创建、索引的删除、副本的更新、索引读写权限、索引别名的配置等等内容。

1.1. 索引删除

ES 索引删除操作向 ES 集群的 http 接口发送指定索引的 delete http 请求即可,可以通过 curl 命令,具体如下:

1
curl -X DELETE http://{host}:{port}/{index}

如果删除成功,它会返回如下信息,具体示例如下:

1
curl -X DELETE http://127.0.0.1:9200/my_index?pretty

为了返回的信息便于读取,增加了 pretty 参数:

1
2
3
{
"acknowledged" : true
}

1.2. 索引别名

ES 的索引别名就是给一个索引或者多个索引起的另一个名字,典型的应用场景是针对索引使用的平滑切换。

首先,创建索引 my_index,然后将别名 my_alias 指向它,示例如下:

1
2
PUT /my_index
PUT /my_index/_alias/my_alias

也可以通过如下形式:

1
2
3
4
5
6
POST /_aliases
{
"actions": [
{ "add": { "index": "my_index", "alias": "my_alias" }}
]
}

也可以在一次请求中增加别名和移除别名混合使用:

1
2
3
4
5
6
7
POST /_aliases
{
"actions": [
{ "remove": { "index": "my_index", "alias": "my_alias" }}
{ "add": { "index": "my_index_v2", "alias": "my_alias" }}
]
}

需要注意的是,如果别名与索引是一对一的,使用别名索引文档或者查询文档是可以的,但是如果别名和索引是一对多的,使用别名会发生错误,因为 ES 不知道把文档写入哪个索引中去或者从哪个索引中读取文档。

2. Settings

Elasticsearch 索引的配置项主要分为静态配置属性动态配置属性,静态配置属性是索引创建后不能修改,而动态配置属性则可以随时修改。

ES 索引设置的 api 为 **__settings_**,完整的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
PUT /my_index
{
"settings": {
"index": {
"number_of_shards": "1",
"number_of_replicas": "1",
"refresh_interval": "60s",
"analysis": {
"filter": {
"tsconvert": {
"type": "stconvert",
"convert_type": "t2s",
"delimiter": ","
},
"synonym": {
"type": "synonym",
"synonyms_path": "analysis/synonyms.txt"
}
},
"analyzer": {
"ik_max_word_synonym": {
"filter": [
"synonym",
"tsconvert",
"standard",
"lowercase",
"stop"
],
"tokenizer": "ik_max_word"
},
"ik_smart_synonym": {
"filter": [
"synonym",
"standard",
"lowercase",
"stop"
],
"tokenizer": "ik_smart"
}
},
"mapping": {
"coerce": "false",
"ignore_malformed": "false"
},
"indexing": {
"slowlog": {
"threshold": {
"index": {
"warn": "2s",
"info": "1s"
}
}
}
},
"provided_name": "hospital_202101070533",
"query": {
"default_field": "timestamp",
"parse": {
"allow_unmapped_fields": "false"
}
},
"requests": {
"cache": {
"enable": "true"
}
},
"search": {
"slowlog": {
"threshold": {
"fetch": {
"warn": "1s",
"info": "200ms"
},
"query": {
"warn": "1s",
"info": "500ms"
}
}
}
}
}
}
}

2.1. 固定属性

  • **_index.creation_date_**:顾名思义索引的创建时间戳。
  • **_index.uuid_**:索引的 uuid 信息。
  • **_index.version.created_**:索引的版本号。

2.2. 索引静态配置

  • **_index.number_of_shards**:索引的主分片数,默认值是 **5**。这个配置在索引创建后不能修改;在 es 层面,可以通过 **es.index.max_number_of_shards** 属性设置索引最大的分片数,默认为 **1024_**。
  • **_index.codec**:数据存储的压缩算法,默认值为 **LZ4**,可选择值还有 **best_compression_**,它比 LZ4 可以获得更好的压缩比(即占据较小的磁盘空间,但存储性能比 LZ4 低)。
  • **_index.routing_partition_size_**:路由分区数,如果设置了该参数,其路由算法为:( hash(_routing) + hash(_id) % index.routing_parttion_size ) % number_of_shards。如果该值不设置,则路由算法为 hash(_routing) % number_of_shardings_routing 默认值为 _id

静态配置里,有重要的部分是配置分析器(config analyzers)。

  • index.analysis

    :分析器最外层的配置项,内部主要分为 char_filter、tokenizer、filter 和 analyzer。

    • **_char_filter_**:定义新的字符过滤器件。
    • **_tokenizer_**:定义新的分词器。
    • **_filter_**:定义新的 token filter,如同义词 filter。
    • **_analyzer_**:配置新的分析器,一般是 char_filter、tokenizer 和一些 token filter 的组合。

2.3. 索引动态配置

  • **_index.number_of_replicas**:索引主分片的副本数,默认值是 **1_**,该值必须大于等于 0,这个配置可以随时修改。
  • **_index.refresh_interval**:执行新索引数据的刷新操作频率,该操作使对索引的最新更改对搜索可见,默认为 **1s**。也可以设置为 **-1_** 以禁用刷新。

3. Mapping 详解

在 Elasticsearch 中,**Mapping**(映射),用来定义一个文档以及其所包含的字段如何被存储和索引,可以在映射中事先定义字段的数据类型、字段的权重、分词器等属性,就如同在关系型数据库中创建数据表时会设置字段的类型。

Mapping 会把 json 文档映射成 Lucene 所需要的扁平格式

一个 Mapping 属于一个索引的 Type

  • 每个文档都属于一个 Type
  • 一个 Type 有一个 Mapping 定义
  • 7.0 开始,不需要在 Mapping 定义中指定 type 信息

3.1. 映射分类

在 Elasticsearch 中,映射可分为静态映射和动态映射。在关系型数据库中写入数据之前首先要建表,在建表语句中声明字段的属性,在 Elasticsearch 中,则不必如此,Elasticsearch 最重要的功能之一就是让你尽可能快地开始探索数据,文档写入 Elasticsearch 中,它会根据字段的类型自动识别,这种机制称为动态映射,而静态映射则是写入数据之前对字段的属性进行手工设置。

静态映射

静态映射是在创建索引时手工指定索引映射。静态映射和 SQL 中在建表语句中指定字段属性类似。相比动态映射,通过静态映射可以添加更详细、更精准的配置信息。

如何定义一个 Mapping

1
2
3
4
5
6
7
8
PUT /books
{
"mappings": {
"type_one": { ... any mappings ... },
"type_two": { ... any mappings ... },
...
}
}

动态映射

动态映射是一种偷懒的方式,可直接创建索引并写入文档,文档中字段的类型是 Elasticsearch 自动识别的,不需要在创建索引的时候设置字段的类型。在实际项目中,如果遇到的业务在导入数据之前不确定有哪些字段,也不清楚字段的类型是什么,使用动态映射非常合适。当 Elasticsearch 在文档中碰到一个以前没见过的字段时,它会利用动态映射来决定该字段的类型,并自动把该字段添加到映射中,根据字段的取值自动推测字段类型的规则见下表:

JSON 格式的数据 自动推测的字段类型
null 没有字段被添加
true or false boolean 类型
浮点类型数字 float 类型
数字 long 类型
JSON 对象 object 类型
数组 由数组中第一个非空值决定
string 有可能是 date 类型(若开启日期检测)、double 或 long 类型、text 类型、keyword 类型

下面举一个例子认识动态 mapping,在 Elasticsearch 中创建一个新的索引并查看它的 mapping,命令如下:

1
2
PUT books
GET books/_mapping

此时 books 索引的 mapping 是空的,返回结果如下:

1
2
3
4
5
{
"books": {
"mappings": {}
}
}

再往 books 索引中写入一条文档,命令如下:

1
2
3
4
5
6
PUT books/it/1
{
"id": 1,
"publish_date": "2019-11-10",
"name": "master Elasticsearch"
}

文档写入完成之后,再次查看 mapping,返回结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"books": {
"mappings": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"publish_date": {
"type": "date"
}
}
}
}
}

使用动态 mapping 要结合实际业务需求来综合考虑,如果将 Elasticsearch 当作主要的数据存储使用,并且希望出现未知字段时抛出异常来提醒你注意这一问题,那么开启动态 mapping 并不适用。在 mapping 中可以通过 dynamic 设置来控制是否自动新增字段,接受以下参数:

  • **true**:默认值为 true,自动添加字段。
  • **false**:忽略新的字段。
  • **strict**:严格模式,发现新的字段抛出异常。

3.2. 基础类型

类型 关键字
字符串类型 string、text、keyword
数字类型 long、integer、short、byte、double、float、half_float、scaled_float
日期类型 date
布尔类型 boolean
二进制类型 binary
范围类型 range

3.3. 复杂类型

类型 关键字
数组类型 array
对象类型 object
嵌套类型 nested

3.4. 特殊类型

类型 关键字
地理类型 geo_point
地理图形类型 geo_shape
IP 类型 ip
范围类型 completion
令牌计数类型 token_count
附件类型 attachment
抽取类型 percolator

3.5. Mapping 属性

Elasticsearch 的 mapping 中的字段属性非常多,具体如下表格:

属性名 描述
type 字段类型,常用的有 text、integer 等等。
index 当前字段是否被作为索引。可选值为 **_true_**,默认为 true。
store 是否存储指定字段,可选值为 true
norms 是否使用归一化因子,可选值为 true
index_options 索引选项控制添加到倒排索引(Inverted Index)的信息,这些信息用于搜索(Search)和高亮显示:**_docs:只索引文档编号(Doc Number);freqs:索引文档编号和词频率(term frequency);positions:索引文档编号,词频率和词位置(序号);offsets_**:索引文档编号,词频率,词偏移量(开始和结束位置)和词位置(序号)。默认情况下,被分析的字符串(analyzed string)字段使用 _positions_,其他字段默认使用 docs_。此外,需要注意的是 index_option 是 elasticsearch 特有的设置属性;临近搜索和短语查询时,_index_option 必须设置为 _offsets_,同时高亮也可使用 postings highlighter。
term_vector 索引选项控制词向量相关信息:**_no:默认值,表示不存储词向量相关信息;yes:只存储词向量信息;with_positions:存储词项和词项位置;with_offsets:存储词项和字符偏移位置;with_positions_offsets**:存储词项、词项位置、字符偏移位置。_term_vector 是 lucene 层面的索引设置。
similarity 指定文档相似度算法(也可以叫评分模型):**_BM25_**:ES 5 之后的默认设置。
copy_to 复制到自定义 _all 字段,值是数组形式,即表明可以指定多个自定义的字段。
analyzer 指定索引和搜索时的分析器,如果同时指定 search_analyzer 则搜索时会优先使用 _search_analyzer_。
search_analyzer 指定搜索时的分析器,搜索时的优先级最高。
null_value 用于需要对 Null 值实现搜索的场景,只有 Keyword 类型支持此配置。

4. 索引查询

4.1. 多个 index、多个 type 查询

Elasticsearch 的搜索 api 支持一个索引(index)的多个类型(type)查询以及多个索引(index)的查询。

例如,我们可以搜索 twitter 索引下面所有匹配条件的所有类型中文档,如下:

1
GET /twitter/_search?q=user:shay

我们也可以搜索一个索引下面指定多个 type 下匹配条件的文档,如下:

1
GET /twitter/tweet,user/_search?q=user:banon

我们也可以搜索多个索引下匹配条件的文档,如下:

1
GET /twitter,elasticsearch/_search?q=tag:wow

此外我们也可以搜索所有索引下匹配条件的文档,用_all 表示所有索引,如下:

1
GET /_all/_search?q=tag:wow

甚至我们可以搜索所有索引及所有 type 下匹配条件的文档,如下:

1
GET /_search?q=tag:wow

4.2. URI 搜索

Elasticsearch 支持用 uri 搜索,可用 get 请求里面拼接相关的参数,并用 curl 相关的命令就可以进行测试。

如下有一个示例:

1
GET twitter/_search?q=user:kimchy

如下是上一个请求的相应实体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"timed_out": false,
"took": 62,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1.3862944,
"hits": [
{
"_index": "twitter",
"_type": "_doc",
"_id": "0",
"_score": 1.3862944,
"_source": {
"user": "kimchy",
"date": "2009-11-15T14:12:12",
"message": "trying out Elasticsearch",
"likes": 0
}
}
]
}
}

URI 中允许的参数:

名称 描述
q 查询字符串,映射到 query_string 查询
df 在查询中未定义字段前缀时使用的默认字段
analyzer 查询字符串时指定的分词器
analyze_wildcard 是否允许通配符和前缀查询,默认设置为 false
batched_reduce_size 应在协调节点上一次减少的分片结果数。如果请求中潜在的分片数量很大,则应将此值用作保护机制,以减少每个搜索请求的内存开销
default_operator 默认使用的匹配运算符,可以是AND或者OR,默认是OR
lenient 如果设置为 true,将会忽略由于格式化引起的问题(如向数据字段提供文本),默认为 false
explain 对于每个 hit,包含了具体如何计算得分的解释
_source 请求文档内容的参数,默认 true;设置 false 的话,不返回_source 字段,可以使用_source_include_source_exclude参数分别指定返回字段和不返回的字段
stored_fields 指定每个匹配返回的文档中的存储字段,多个用逗号分隔。不指定任何值将导致没有字段返回
sort 排序方式,可以是fieldNamefieldName:asc或者fieldName:desc的形式。fieldName 可以是文档中的实际字段,也可以是诸如_score 字段,其表示基于分数的排序。此外可以指定多个 sort 参数(顺序很重要)
track_scores 当排序时,若设置 true,返回每个命中文档的分数
track_total_hits 是否返回匹配条件命中的总文档数,默认为 true
timeout 设置搜索的超时时间,默认无超时时间
terminate_after 在达到查询终止条件之前,指定每个分片收集的最大文档数。如果设置,则在响应中多了一个 terminated_early 的布尔字段,以指示查询执行是否实际上已终止。默认为 no terminate_after
from 从第几条(索引以 0 开始)结果开始返回,默认为 0
size 返回命中的文档数,默认为 10
search_type 搜索的方式,可以是dfs_query_then_fetchquery_then_fetch。默认为query_then_fetch
allow_partial_search_results 是否可以返回部分结果。如设置为 false,表示如果请求产生部分结果,则设置为返回整体故障;默认为 true,表示允许请求在超时或部分失败的情况下获得部分结果

4.3. 查询流程

在 Elasticsearch 中,查询是一个比较复杂的执行模式,因为我们不知道那些 document 会被匹配到,任何一个 shard 上都有可能,所以一个 search 请求必须查询一个索引或多个索引里面的所有 shard 才能完整的查询到我们想要的结果。

找到所有匹配的结果是查询的第一步,来自多个 shard 上的数据集在分页返回到客户端之前会被合并到一个排序后的 list 列表,由于需要经过一步取 top N 的操作,所以 search 需要进过两个阶段才能完成,分别是 query 和 fetch。