ElasticSearch 入门教程 - 基础篇之三

大纲

版本说明

软件版本描述
ElasticSearch7.4.2ElasticSearch 的版本
Curl7.29.0Curl 的版本

Mapping

Mapping 简介

Mapping 是用来定义文档(Document),以及它所包含的字段(Field)是如何存储和检索的,这类似创建 MySQL 数据库表时指定表的字段类型,主要作用如下:

  • 定义 Index 下的字段名
  • 定义字段类型,比如数值型、浮点型、布尔型等
  • 定义倒排索引相关的设置,比如是否索引、记录 Position 等

一句话简单概括就是,Mapping 决定了 ES 在建立倒排索引和进行检索时对文档采取的相关策略,如数字类型、日期类型、文本类型等等。


值得一提的是,检索时用到的分析策略,要和建立索引时的分析策略相同,否则将导致数据分析不准确。ES 对不同的类型有不同的存储和检索策略,例如:

  • Exact Value - 精确匹配:对 Exact Value(如 date),在索引的分词阶段,会将整个 Value 作为一个关键词建立到倒排索引中。
  • Full Text - 全文检索:对于 Full Text 型的数据类型(如 text),在索引时会经过各类处理,包括分词、Normalization(时态转换、单复数的转换、同义词转换、大小写转换、缩写)等,才会建立到索引数据中,更深入的话就涉及到 NPL 自然语义处理。

Mapping Type

每个索引都拥有唯一的 Mapping Type,用来决定文档将如何被索引。Mapping Type 由下面两部分组成:

  • Meta fields:元字段,用于自定义如何处理文档的相关元数据。元字段包括文档的 _index_type_id_source 等字段。
  • Fields or properties:映射类型,包含与文档相关的字段或属性的列表。

特别注意

从 ES 6.0.0 及以上的版本开始,Mapping Type 已经被官方移除。

常用字段类型

提示

ES 完整的字段类型说明请查看 官方文档

核心字段类型

  • 布尔类型:boolean
  • 二进制类型:binary
  • 日期类型:date、date_nanos
  • 字符串型:text、keyword(精确匹配,不会进行分词)
  • 数字类型:long、integer、short、byte、double、float、half_float、scaled_float
  • 范围类型:integer_range、float_range、long_range、double_range、date_range

复合字段类型

  • 数组类型:array(支持不针对特定的类型)
  • 对象类型:object(用于单个 JSON 对象)
  • 嵌套类型:nested(用于 JSON 对象数组)
  • 地理位置类型:geo_point(用于描述地理坐标,如经纬度)、geo_shape(用于描述复杂形状,如地理图形)

专用字段类型

  • IP 类型:ip(记录 IPV4 和 IPV6 地址)
  • 哈希类型:murmur3(记录字符串的 Hash 值)
  • 补全类型:completion(提供自动补全的提示)
  • 令牌计数类型:token_count(用于统计字符串中的词条数量)
  • 抽取类型:percolator(接受特定领域查询语言 - Query DSL 的查询)
  • 附件类型:attachment(支持存储附件,如 Microsoft Office、Open Document、ePub、HTML 等格式的内容)

多字段的特性

通常用于为不同的目的用不同的方法索引同一个字段,这样可以更好地满足各种搜索需求。例如一个字符串类型的字段可以设置为 text 来支持全文检索,与此同时也可以让这个字段拥有 keyword 类型来做排序和聚合。大多数的字段类型,都是通过 fields 参数来支持多字段的特性。

数据类型自动猜测

以下的 JSON 数据类型,ES 会自动猜测其字段类型。对于其他 ES 不会自动猜测的数据类型,则需要手动通过 Mapping 来指定其字段类型。

JSON TypeJSON ValueES Type
布尔型 true、falseboolean
整数 123long
浮点数 123.45double
日期 2020-09-15date
字符串 foostring

Mapping 操作

版本兼容说明

  • ES 6 及以下版本拥有 type 的概念

  • ES 7 及以上版本移除了 type 的概念

    • 关系型数据库中两个数据库表是互相独立的,即使它们里面有相同名称的列也不影响使用,但在 ES 中不是这样的。ES 是基于 Lucene 开发的搜索引擎,而 ES 中不同 type 下名称相同的 filed 最终在 Lucene 中的处理方式是一样的。
    • 比如两个不同 type 下的两个 user_name,在 ES 同一个索引下其实被认为是同一个 filed,必须在两个不同的 type 中定义相同的 filed 映射。否则,不同 type 中的相同字段名称就会在处理中出现冲突的情况,导致 Lucene 处理效率下降。ES 去掉 type 的支持,就是为了提高处理数据的效率。
  • 在 ES 7.x 版本里,URL 中的 type 参数为可选项,也就是索引一个文档时不再要求提供文档类型。而在 ES 8.x 版本里,不再支持 URL 中的 type 参数。ES 不同版本的兼容方案如下:

    • 1)将索引从多类型迁移到单类型,即每种类型文档都建一个独立的索引。
    • 2)将已存在的索引下的类型数据,全部迁移到指定位置。

查询 Mapping

  • 查询 bank 索引下的映射
1
curl -X GET http://127.0.0.1:9200/bank/_mapping
  • 返回的 JSON 结果
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
{
"bank": {
"mappings": {
"properties": {
"account_number": {
"type": "long"
},
"address": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"age": {
"type": "long"
},
"balance": {
"type": "long"
},
......
}
}
}
}

创建 Mapping

  • 创建索引并指定映射,这里的 URL 不需要带 _mapping
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PUT /user
{
"mappings": {
"properties": {
"age": {
"type": "integer"
},
"email": {
"type": "keyword"
},
"name": {
"type": "text"
}
}
}
}
  • 若希望新增数据,可以使用以下方式,其中的 _doc 是类型(固定不变的)
1
2
3
4
5
6
POST /user/_doc/1
{
"age ": 18,
"email": "233443@qq.com",
"name": "Jim"
}

新增 Mapping

  • 添加新的字段映射
1
2
3
4
5
6
7
8
9
10
PUT /user/_mapping
{
"properties": {
"employee-id": {
"type": "keyword",
"index": false,
"doc_values": false
}
}
}
属性可选说明
index默认 true,如果为 false,表示该字段不会被索引,也就是在检索结果里面会出现,但字段本身并不能当做检索条件。
doc_values默认 true,如果为 false,表示该字段不可以做排序、聚合以及脚本操作,这样更节省磁盘空间。还可以通过设定 doc_values 为 true,index 为 false 来让字段不能被搜索,但可以用于排序、聚合以及脚本操作。

更新 Mapping

对于已经存在的映射字段,ES 不支持更新,这是因为 Lucence 实现的倒排索引生成后不允许修改。映射字段的更新,必须先创建新的索引,然后进行数据迁移。

特别注意

  • 由于从 ES 7 及以上版本开始,Type 的概念已经被官方移除,因此 ES 6 及以下版本在迁移数据时,需要指定 Type,而其他版本则不需要指定 Type
  • 先创建新的索引,并指定新的映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PUT /new_user
{
"mappings": {
"properties": {
"age": {
"type": "integer"
},
"email": {
"type": "text"
},
"name": {
"type": "keyword"
}
}
}
}
  • 将旧索引下的数据进行迁移,以下是 ES 7 及以上版本的写法,其中的 source 用于指定旧索引,dest 用于指定新索引
1
2
3
4
5
6
7
8
9
POST /_reindex
{
"source": {
"index": "user"
},
"dest": {
"index": "new_user"
}
}
  • 将旧索引下的数据进行迁移,以下是 ES 6 及以下版本的写法,必须指定 type,其中的 source 用于指定旧索引,dest 用于指定新索引
1
2
3
4
5
6
7
8
9
10
POST /_reindex
{
"source": {
"index": "user",
"type": "vip"
},
"dest": {
"index": "new_user"
}
}

分词

一个 Tokenizer(分词器)接收一个字符流,将之分割为独立的 Tokens(词元,通常是独立的单词),然后输出 Tokens 流。例如,Whitespace Tokenizer 遇到空白字符时分割文本。它会将文本 Quick brown fox! 分割为 [Quick, brown, fox!]。 该 Tokenizer(分词器)还负责记录各个 Term(词条)的顺序或 Position 位置(用于 Phrase 短语和 Word Proximity 词近邻查询),以及 Term(词条)所代表的原始 Word(单词)的 Start(起始)和 End(结束)的 Character Offsets(字符偏移量,用于高亮显示搜索的内容)。Elasticsearch 提供了很多内置的分词器,可以用来构建 Custom Analyzers(自定义分词器)。

IK 分词器简介

IKAnalyzer 是一个开源的,基于 Java 语言开发的轻量级的中文分词工具包。从 2006 年 12 月推出 1.0 版开始,IKAnalyzer 已经推出了 3 个大版本。最初,它是以开源项目 Lucene 为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的 IKAnalyzer 3.0 则发展为 面向 Java 的公用分词组件,独立于 Lucene 项目,同时提供了对 Lucene 的默认优化实现。

安装 IK 分词器

可以从 GitHub 仓库 下载 IK 分词器的安装包,然后按照以下步骤安装即可。值得一提的是,IK 分词器的版本号必须与 ES 的版本号一致。

  • 安装 IK 分词器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 进入 ES 的 plugins 目录
$ cd /usr/local/elasticsearch/plugins

# 下载文件
$ wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip

# 解压文件
$ unzip -d ik elasticsearch-analysis-ik-7.4.2.zip

# 删除文件
$ rm -rf elasticsearch-analysis-ik-7.4.2.zip

# 文件授权
$ chmod -R 777 ik

# 重启 ES 服务器
  • 查看 IK 分词器是否安装成功
1
2
3
4
5
6
7
# 进入 ES 的 bin 目录
$ cd /usr/local/elasticsearch/bin

# 查看插件列表
$ elasticsearch-plugin list

# 如果看到 IK 分词器出现在插件列表中,则说明安装成功

测试 IK 分词器

观察下述的结果,能够看出来使用不同的分词器,分词结果有明显的区别,所以以后定义一个索引不能再使用默认的 Mapping 了,要手动建立 Mapping,因为要选择合适的分词器。

提示

  • IK 分词器,提供了两种颗粒度拆分(ik_smart,ik_max_word)
  • ik_smart(最粗粒度拆分),分割的粒度较小
  • ik_max_word(最细粒度划分),分割的力度较大
  • 使用默认的分词器,无法对中文进行正确分词
1
2
3
4
POST /_analyze
{
"text": "我是中国人"
}
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
{
"tokens" : [
{
"token" : "我",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
},
{
"token" : "是",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "中",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
},
{
"token" : "国",
"start_offset" : 3,
"end_offset" : 4,
"type" : "<IDEOGRAPHIC>",
"position" : 3
},
{
"token" : "人",
"start_offset" : 4,
"end_offset" : 5,
"type" : "<IDEOGRAPHIC>",
"position" : 4
}
]
}
  • 使用 ik_smart 分词器,可以对中文进行正确分词
1
2
3
4
5
POST /_analyze
{
"analyzer": "ik_smart",
"text": "我是中国人"
}
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
{
"tokens" : [
{
"token" : "我",
"start_offset" : 0,
"end_offset" : 1,
"type" : "CN_CHAR",
"position" : 0
},
{
"token" : "是",
"start_offset" : 1,
"end_offset" : 2,
"type" : "CN_CHAR",
"position" : 1
},
{
"token" : "中国人",
"start_offset" : 2,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 2
}
]
}
  • 使用 ik_max_word 分词器,可以对中文进行正确分词
1
2
3
4
5
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "我是中国人"
}
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
{
"tokens" : [
{
"token" : "我",
"start_offset" : 0,
"end_offset" : 1,
"type" : "CN_CHAR",
"position" : 0
},
{
"token" : "是",
"start_offset" : 1,
"end_offset" : 2,
"type" : "CN_CHAR",
"position" : 1
},
{
"token" : "中国人",
"start_offset" : 2,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "中国",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "国人",
"start_offset" : 3,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 4
}
]
}

自定义词库

IK 分词器自身不能识别最新的网络流行语,但可以通过自定义词库来解决。

  • 第一步:更改 IK 分词器的配置文件 IKAnalyzer.cfg.xml,配置文件在 ES 的 plugins 目录下,例如 /usr/local/elasticsearch/plugins/ik/config/IKAnalyzer.cfg.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict"></entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://192.168.1.103/es/fenci.txt</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

提示

  1. remote_ext_dict 参数用于指定远程扩展字典所在的网络位置(URL)。值得一提的是,这里可以将远程扩展字典通过文件的方式(一行一个分词)存放在 Nginx 服务器,也可以自行开发 HTTP 接口并返回扩展字典的内容。
  2. 远程扩展字典默认支持热更新,要求 Http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,只要有一个发生变化,IK 插件就会去自动抓取新的字典进而更新词库。
  3. 更改完 IK 分词器的配置文件后,必须重启 ES 服务器,否则远程扩展字典无法生效。
  • 第二步:重启 ES 服务器

  • 第三步:上述配置完成之后,ES 只会对新增的数据用新词分词,历史数据是不会重新分词的。如果希望历史数据重新分词,需要执行以下的 HTTP 请求:

1
curl -X POST my_index/_update_by_query?conflicts=proceed

参考资料