Elasticsearch 学习笔记
Elasticsearch 学习笔记
简介
Elasticsearch(简称:ES) 是一个免费且开放的分布式搜索和分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件;Elastic Stack 是一套适用于数据采集、扩充、存储、分析和可视化的免费开源工具。人们通常将 Elastic Stack 称为 ELK Stack(代指 Elasticsearch、Logstash 和 Kibana),目前 Elastic Stack 包括一系列丰富的轻量型数据采集代理,这些代理统称为 Beats,可用来向 Elasticsearch 发送数据。
官方网站:https://www.elastic.co/cn/
官方英文文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/getting-started.html
官方中文文档:https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
Elasticsearch 用途
- 应用程序搜索
- 网站搜索
- 企业搜索
- 日志处理和分析
- 基础设施指标和容器监测
- 应用程序性能监测
- 地理空间数据分析和可视化
- 安全分析
- 业务分析
Elasticsearch 基本概念
索引(Index):索引是具有相同结构的文档集合。在 Elasticsearch 中,索引是个非常重要的内容,对于 Elasticsearch 的大部分操作都是基于索引来完成的。它类似于关系数据库中的数据库。每个索引可以包含多个文档和类型。索引可以分为分片,每个分片可以分布在不同的节点上。
类型(Type):Elasticsearch 中的类型类似数据库中的表。但是从 7.0 版本开始,Elasticsearch 不再支持 Type 的概念,不同的类型使用单独的索引来管理。所有的文档类型都默认使用
_doc。文档(Document):文档类似于关系数据库中的一行记录。每个文档都有一个唯一标识符(ID),并且有一个 JSON 格式的数据内容。
映射(Mapping):映射定义了索引中每个字段的数据类型和属性。ElasticSearch 会根据映射将数据存储在倒排索引中。
倒排索引:ElasticSearch 使用倒排索引来实现文档搜索。它将文档中的每个单词都存储到一个排序后的列表中,并将单词所出现的文档 ID 与之关联。这样,就可以通过关键字来快速查找匹配的文档。
举例:比如搜索关键词“小米手机”,保存记录中有
1.南方小米、2.东北大米、3.小米手机。ES 会对搜索的关键词进行分词,分词后匹配如下:
关键词 ID 小 1,3 米 1,2,3 手 3 机 3 从表中,可以看到,每个分词对应着一组 ID。然后 ES 会对这些匹配的 ID 进行评分,即按照 ID 的出现次数。从分词表中可以看出 ID 为 3 的匹配次数最多。最终 ES 会按照评分从高到低排序查询出数据,即[3.小米手机, 1.南方小米, 2.大米]。
分片(shard):分片是将一个索引(index)拆分成多个部分的过程,每个部分称为一个分片。每个分片包含部分索引数据和索引元数据,这些分片可以在不同的节点上存储和处理,从而提高性能和可扩展性。分片包含主分片和副本分片,每个索引默认都有一个主分片和一个副本分片,并且放到不同的节点上。
- 主分片(primary shard):主分片是索引的原始分片,包含索引的一部分数据。每个主分片在一个节点上维护,可以被分配到集群中的任何节点上。这有点儿类似关系型数据库的水平分库分表,或类似 Redis 的集群模式,通过减少单点存储的数据量来达到提高性能的目的。**注意:主分片数量一旦确定就无法再修改了,如果必须要修改只能重新创建索引,并将原索引的数据迁移到新索引中。虽然 Elasticsearch 提供了自动迁移的 API,但如果数据量过大,迁移会非常耗时。因此,建议一开始就规划好分片数量,尽可能避免后期修改。**
- 副本分片(replica shard):副本分片是主分片的复制品,每个主分片可以有一个或多个副本。当主分片的数据发生变化,Elasticsearch 会自动将数据同步到副本分片上。当主分片失效,Elasticsearch 会自动选举一个副本分片作为主分片,以达到容错的目的。正常情况下,副本分片会作为只读分片提供查询。这有点类似关系型数据库的主从模式,通过主从达到读写分离的目的,当主节点故障,则从节点自动提升为主节点。
主分片和副本分片被创建时,Elasticsearch 会自动将它们放到不同的节点上,以防止机器节点故障后,副本分片无法被提升为主分片。
分片划分建议
主分片的大小最好控制在 30GB 以内,即每个分片存储的数据不要超过 30G,并且主分片的最大数量最好不好超过 100 个。主分片的数量确定可以参考这个公式
预估索引数据大小 / 每个分片限制大小。比如:预计索引的数据有 500GB,每个索引控制在 30GB。
分片数量 = 500 / 30 = 16
即:可以定义 16 个主分片。
这里只是一个参考,实际划分还要参考硬件性能、并发量等条件。另外,尽量避免单个索引的数量过大,如果数据量本身就很大,可以定义多个索引来存储。比如日志信息,日志是每时每刻都在产生的,可以设定一个时间范围,比如每个月的日志使用一个单独的索引,即索引定义为
logs_<年>_<月>。这样做的好处是避免了单个索引的数据量过大,并且当集群扩展节点时,数据会分配到新的节点存储,这样也能达到平衡的目的。
安装 Elasticsearch
Elasticsearch 有简单安装和集群安装两种方式。如果只是想简单体验 Elasticsearch 的功能,可以下载一个 Elasticsearch 的安装包进行安装即可。或使用 Docker 镜像进行安装。
但在实际使用中,Elasticsearch 一般都使用的是集群环境。因此,本章节介绍怎么使用 Docker 搭建一个 Elasticsearch 的集群环境。使用 Docker 安装 Elasticsearch 集群需要先安装 Docker 和 Docker Compose 环境。
本次安装的 Elasticsearch 版本是 7.17.7,因为在安装 IK 分词器时发现,IK 分词器的版本只到 7.17.7。
安装前准备
使用 Docker 安装 Elasticsearch,需要先下载一个镜像,然后运行这个镜像。从镜像运行的容器中复制出 Elasticsearch 所需的配置文件。后面会以编排的形式将配置文件挂载上去。
下载镜像
1
docker pull elasticsearch:7.17.7使用镜像运行容器
1
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.17.7查看运行的镜像
1
docker ps -a命令执行结果如下:
1
2
3-- console log --
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3277e5f102ab elasticsearch:7.17.7 "/tini -- /usr/local…" 18 seconds ago Up 13 seconds 0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 0.0.0.0:9300->9300/tcp, :::9300->9300/tcp elasticsearch进入容器
1
docker exec -it <容器ID> /bin/bash重点:成功进入容器后需要查询几个关键的信息。
查询目录所属用户
使用
ll命令查看当前目录,返回如下信息1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18-- console log --
drwxrwxr-x 1 root root 58 Mar 24 02:33 ./
drwxr-xr-x 1 root root 27 Jan 31 05:40 ../
-rw-r--r-- 1 root root 220 Jan 31 05:40 .bash_logout
-rw-r--r-- 1 root root 3771 Jan 31 05:40 .bashrc
drwxrwxr-x 3 elasticsearch root 17 Mar 24 02:33 .cache/
-rw-r--r-- 1 root root 807 Jan 31 05:40 .profile
-r--r--r-- 1 root root 3860 Jan 31 05:33 LICENSE.txt
-r--r--r-- 1 root root 642830 Jan 31 05:35 NOTICE.txt
-r--r--r-- 1 root root 2710 Jan 31 05:33 README.asciidoc
drwxrwxr-x 1 elasticsearch root 6 Jan 31 05:39 bin/
drwxrwxr-x 1 elasticsearch root 36 Mar 24 02:33 config/
drwxrwxr-x 1 elasticsearch root 19 Mar 24 02:33 data/
dr-xr-xr-x 1 root root 17 Jan 31 05:38 jdk/
dr-xr-xr-x 3 root root 4096 Jan 31 05:38 lib/
drwxrwxr-x 1 elasticsearch root 37 Mar 24 02:33 logs/
dr-xr-xr-x 61 root root 4096 Jan 31 05:38 modules/
drwxrwxr-x 1 elasticsearch root 6 Jan 31 05:35 plugins/这里主要关注三个重要的信息,config、data、logs 这三个目录的所属用户是
elasticsearch。这里是个大坑,后面我们在编排服务时,挂载目录的所属用户要和这里的所属用户保持一致,否则启动会提示权限错误。查看用户的 id 信息
1
id elasticsearch命令返回结果如下:
1
2-- console log --
uid=1000(elasticsearch) gid=1000(elasticsearch) groups=1000(elasticsearch),0(root)这里的 uid 的值是 1000,这个值一定要记好。后面会用到这个值。
查询当前路径
1
pwd命令返回结果如下:
1
2-- console log --
/usr/share/elasticsearch这个路径也要记录下来,后面也会用到。确定好路径之后,可以使用
exit命令退出容器。
复制配置文件目录到宿主机
这一步是将容器下的配置目录复制到宿主机上,这里就用到了前面查询到的
/usr/share/elasticsearch路径。1
2
3
4
5# 新建用于保存配置文件的临时目录
mkdir -p ~/elasticsearch/example
# 复制容器中的配置文件到宿主机
docker cp <容器ID>:/usr/share/elasticsearch/config ~/elasticsearch/example/config停止并删除 Elasticsearch 容器
前面启动容器只是为了获取配置文件,配置文件复制出来以后,容器就可以停止了。
1
docker rm $(docker stop <容器ID>)
修改 elasticsearch.yml 配置文件
单节点启动的配置文件比较简单,我们需要将其修改成集群模式的配置。
1 | |
编排服务
由于搭建的是集群,所以最好是将每个节点的配置、数据、日志等分开保存。
创建主从节点目录
1
2
3
4
5
6
7
8# 分别创建节点的数据文件目录
mkdir -p ~/elasticsearch/node1/data ~/elasticsearch/node2/data ~/elasticsearch/node3/data
# 分别创建三个节点的日志文件目录
mkdir -p ~/elasticsearch/node1/logs ~/elasticsearch/node2/config ~/elasticsearch/node3/logs
# 创建插件目录,由于插件是各个节点共享的,因此只创建一个目录即可。
mkdir -p ~/elasticsearch/plugins复制上一小节编写好的配置文件到节点目录
1
2
3cp -r ~/elasticsearch/example/config ~/elasticsearch/node1/config
cp -r ~/elasticsearch/example/config ~/elasticsearch/node2/config
cp -r ~/elasticsearch/example/config ~/elasticsearch/node3/config复制完成后,分别修改三个节点的配置。主要的修改项如下:
- node.name:集群节点名称,它将作为节点的标识存在,最好是每个节点使用不同的名称。
- discovery.seed_hosts:集群节点发现,此处配置除了自己以外的其他节点。可以是 hostname 或 IP:9300,这里的 9300 是集群交互端口,不写会默认使用 9300,如果你使用的是 IP 地址,建议还是写上。
给新建的目录授权
这里为了后续的操作方便,可以直接给
elasticsearch目录授权。另外还需要给新建的目录指定所属用户,这里就用到了前面查询到的 uid。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 设置权限
chmod -R o+xrw ~/elasticsearch
# 指定data目录的所属用户
chown 1000 ~/elasticsearch/node1/data
chown 1000 ~/elasticsearch/node2/data
chown 1000 ~/elasticsearch/node3/data
# 指定logs目录的所属用户
chown 1000 ~/elasticsearch/node1/logs
chown 1000 ~/elasticsearch/node2/logs
chown 1000 ~/elasticsearch/node3/logs
# 指定日志目录的所属用户
chown 1000 ~/elasticsearch/node1/config
chown 1000 ~/elasticsearch/node2/config
chown 1000 ~/elasticsearch/node3/config
# 指定插件目录的所属用户
chown 1000 -R ~/elasticsearch/plugins此处使用的是递归授权,即 elasticsearch 目录及其子目录和文件,授予其他用户的执行和读写权限。另外,使用 chown 指定所属用户之后,使用
ll命令查看时,发现目录的所属用户并不是elasticsearch。这是因为宿主机上不存在这个用户,但是它会有一个用户与容器中的elasticsearch用户对应。因此,只要保证宿主机的用户和容器中的用户uid一致就可以了。新建 Docker 网络
这一步的目的是让后面创建的所有容器都基于这个网络运行。
1
docker network create -d bridge elasticsearch_network此处创建了一个桥接网络,后面的 Elasticsearch 和 Kibana 编排时,都使用这个网络。这样容器之间就可以互联互通了。如若不然,就必须将所有的服务都写入同一个编排文件中。
如果想要查看已经创建的网络,可以使用下面的命令:
1
docker network inspect <网络名称>编写编排文件
在
elasticsearch目录下新建一个elasticsearch-compose.yml文件。编写内容如下:elasticsearch-compose.yml1
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
76version: "3"
services:
es-node1:
container_name: es-node1
hostname: es-node1
image: elasticsearch:7.17.7
restart: always
user: root
ports:
- 9200:9200
- 9300:9300
# 指定使用的网络
networks:
- elasticsearch_network
volumes:
- ~/elasticsearch/node1/config:/usr/share/elasticsearch/config
- ~/elasticsearch/node1/data:/usr/share/elasticsearch/data
- ~/elasticsearch/node1/logs:/usr/share/elasticsearch/logs
- ~/elasticsearch/plugins:/usr/share/elasticsearch/plugins
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "TZ=Asia/Shanghai"
privileged: true
stdin_open: true
tty: true
es-node2:
container_name: es-node2
hostname: es-node2
image: elasticsearch:7.17.7
restart: always
ports:
- 9201:9200
- 9301:9300
# 指定使用的网络
networks:
- elasticsearch_network
volumes:
- ~/elasticsearch/node2/config:/usr/share/elasticsearch/config
- ~/elasticsearch/node2/data:/usr/share/elasticsearch/data
- ~/elasticsearch/node2/logs:/usr/share/elasticsearch/logs
- ~/elasticsearch/plugins:/usr/share/elasticsearch/plugins
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "TZ=Asia/Shanghai"
privileged: true
stdin_open: true
tty: true
es-node3:
container_name: es-node3
hostname: es-node3
image: elasticsearch:7.17.7
restart: always
ports:
- 9202:9200
- 9302:9300
# 指定使用的网络
networks:
- elasticsearch_network
volumes:
- ~/elasticsearch/node3/config:/usr/share/elasticsearch/config
- ~/elasticsearch/node3/data:/usr/share/elasticsearch/data
- ~/elasticsearch/node3/logs:/usr/share/elasticsearch/logs
- ~/elasticsearch/plugins:/usr/share/elasticsearch/plugins
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "TZ=Asia/Shanghai"
privileged: true
stdin_open: true
tty: true
# 开启外部网络访问,此处开启的是一开始创建好的桥接网络
networks:
elasticsearch_network:
external: true注意:JVM 内存的配置不要设置的太小,否则集群无法启动。我这里配置的是最低要求,实际使用时,可以根据情况自行调整。一般调整策略为:(宿主机总内存 / 实例数量 / 2) + 1,计算结果取整数。比如宿主机内存是 8G,部署了 3 个节点实例,实际计算 2.3333,取整数为 2,则我这里每个节点的最大内存不要超过 2GB。最小内存取最大内存的一半即可,但是不要低于 512M。
启动集群
1 | |
启动完成后,分别使用 9200、9201、9202 三个端口访问 Elasticsearch。如果都能正常访问,则说明搭建成功。也可以访问下面的地址来查看集群状态。
http://<IP>:<端口>/_cluster/health?pretty,成功访问后返回如下信息:
1 | |
其中"status": "green"是指集群状态正常。number_of_nodes是指当前活动的节点数。
安装 Kibana
Kibana 是一个开源的分析与可视化平台,它可用于管理 Elasticsearch。
注意:Kibana 的版本最好和 Elasticsearch 的版本保持一致。
安装前准备
此处的步骤和安装 Elasticsearch 时的步骤,大致相同,都需要先运行一个临时容器,然后从中复制出配置文件。
下载 Kibana 镜像
1
2# 再次声明,Kibana 的版本最好与 ElasticSearch 保持一致。
docker pull kibana:7.17.7运行一个临时容器
1
docker run -d --name kibana -p 5601:5601 kibana:7.17.7进入容器
1
docker exec -it <容器ID> /bin/bash查看目录及权限
1
2# 有些容器不能使用 ll 命令,如果不能用,可以使用 ls -l
ls -l命令执行结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14-- console log --
total 1152
-rw-rw-r-- 1 kibana root 3860 Jan 30 12:30 LICENSE.txt
-rw-rw-r-- 1 kibana root 1128786 Jan 30 12:30 NOTICE.txt
-rw-rw-r-- 1 kibana root 3970 Jan 30 12:30 README.txt
drwxrwxr-x 2 kibana root 94 Jan 30 12:30 bin
drwxrwxr-x 1 kibana root 24 Jan 30 12:44 config
drwxrwxr-x 1 kibana root 18 Mar 24 04:10 data
drwxrwxr-x 6 kibana root 108 Jan 30 12:30 node
drwxrwxr-x 682 kibana root 20480 Jan 30 12:30 node_modules
-rw-rw-r-- 1 kibana root 740 Jan 30 12:30 package.json
drwxrwxr-x 2 kibana root 6 Jan 30 12:30 plugins
drwxrwxr-x 9 kibana root 131 Jan 30 12:30 src
drwxrwxr-x 3 kibana root 79 Jan 30 12:30 x-pack查看目录的完整路径
1
pwd命令执行结果如下:
1
2-- console log --
/usr/share/kibana查看 kibana 的 uid
1
id kibana命令执行结果如下:
1
2-- console log --
uid=1000(kibana) gid=1000(kibana) groups=1000(kibana),0(root)
将上面查询到的结果记录下来,后面会用到。
复制配置文件目录到宿主机
1
2
3
4
5# 新建用于保存配置文件的临时目录
mkdir -p ~/kibana/example
# 复制容器中的配置文件目录到宿主机临时目录
docker cp <容器ID>:/usr/share/kibana/config ~/kibana/example/config复制完配置文件之后,临时容器就可以删除了,否则后面再启动新实例的时候,会有端口冲突。
修改 kibana.yml 配置文件
kibana.yml
1 | |
Kibana 的配置项很多,这里只列举了一些关键参数。如果想要使用其他相关功能,可以查阅官方文档。
编排服务
创建相关目录
1
2# 创建数据文件目录
mkdir -p ~/kibana/data复制编写好的配置文件到正式目录
1
cp -r ~/kibana/example/config ~/kibana/config给目录授权
这一步可以借鉴前面的 Elasticsearch 授权说明。
1
2
3
4
5
6# 给目录授权
chmod -R o+xrw ~/kibana
# 指定所属用户
chown 1000 ~/kibana/config
chown 1000 ~/kibana/data查看已经运行的 Elasticsearch 容器的网络名
1
docker inspect <容器ID> --format='{{range $key,$value:=.NetworkSettings.Networks}}{{$key}} {{end}}'命令执行结果如下:
1
2-- console log --
elasticsearch_default在书写编排文件时,要让 kibana 加入到这个网络,否则无法使用 hostname 访问集群节点。
编写编排文件
在
kibana目录下新建一个kibana-compose.yml文件,编写内容如下:kibana-compose.yml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24version: "3"
services:
kibana:
container_name: kibana
hostname: kibana
image: kibana:7.17.7
restart: always
ports:
- 5601:5601
# 配置使用的网络
networks:
- elasticsearch_network
volumes:
- ~/kibana/config:/usr/share/kibana/config
- ~/kibana/data:/usr/share/kibana/data
environment:
- "TZ=Asia/Shanghai"
privileged: true
stdin_open: true
# 开启外部网络访问,此处开启的是一开始创建好的桥接网络
networks:
elasticsearch_network:
external: true
启动 kibana
1 | |
容器创建后,可以使用下面的命令查看是否启动成功。
1 | |
确保一切正常之后,在浏览器输入:http://<IP 地址>:5061/,就可以访问 Kibana 管理界面了。
新建工作区
新建工作区的目的是便于管理。当多个项目使用同一个 Elasticsearch 时,可以通过新建工作区来隔离。
进入菜单/Management/Stack Management,在新打开的页面中找到Kibana标签下的工作区选项。点击右上角的创建工作区按钮。按照表单要求填写即可。
开发工具
Kibana 提供了一个非常好用的开发工具。日常的一些 ES 操作都可以在上面完成。
开发工具位置:菜单/Management/开发工具。
索引
索引对于 Elasticsearch 存储来说是最基本的单元。所有的数据都是存储在某个索引上的。
新建索引
简单索引创建
1 | |
命令执行结果如下:
1 | |
此时我们就创建了一个名为demo_user_info的索引。
这里需要注意,一个工作区内不能出现同名的索引。如果我们使用上面的指令执行多次,那么从第二次开始会报错。另外,索引从操作上来将不是必须提前创建的,我们也可以在创建文档的时候创建。比如下面这样:
1 | |
此时,Elasticsearch 会自动帮我们创建一个demo_user_info的索引,并往索引中插入一条数据。
创建索引并设置分片和副本
1 | |
参数说明:
- number_of_shards:分片数量
- number*of_replicas:副本数量,这里需要说明的是,副本总数量 = 分片数量 * 设定的副本数量。比如:分片数量为 2,副本数量为 3,则副本总数量 = 2 _ 3 = 6 个。由此可以看出,这里指定的副本数量是指每个主分片有几个副本。
修改索引
索引不能修改主分片数量,其他修改可以通过相应的指令来修改。
修改副本数量
1 | |
修改映射
以修改userName为例。
1 | |
删除索引
1 | |
迁移分片
对于索引不能修改的设置,可以通过新建索引,然后进行索引迁移的办法来实现。
1 | |
使用举例
关闭索引的写操作
关闭写操作的目的是为了在迁移的过程中,防止有新的写入,导致迁移后的数据不完整。
1
2
3
4PUT /demo_user_info/_settings
{
"blocks.write": true
}参数说明:
blocks.write:写入锁。true-上锁;false-解锁。
创建新索引
1
2
3
4
5
6
7PUT /demo_user_info-2
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}迁移数据到新索引
1
2
3
4
5
6
7
8
9POST _reindex
{
"source": {
"index": "demo_user_info"
},
"dest": {
"index": "demo_user_info-2"
}
}数据迁移完毕后,测试没有问题再删除旧的索引。
索引别名
索引别名是一个可以指向多个索引的别名,它允许用户使用别名来查询索引,这在索引迁移和合并查询时非常有效。但是需要注意,一对多的别名在读写时,一方面性能不好,另一方面可能导致数据不一致。因此,除非必要,否则尽可能使用一对一的别名。
还是以上面的索引迁移为例,我们可以在一开始设计索引时就定义好一个别名,比如user_info,它指向一个具体的索引demo_user_info。
1 | |
待数据迁移完毕后,将别名指向新的索引。
1 | |
这样,我们在查询索引时,就可以一直使用别名user_info来查询。避免索引迁移后进行大量的修改。
映射
Elasticsearch 的映射(Mapping)是指用于定义索引中字段的数据类型、分词器等属性的结构。映射由多个字段映射组成,每个字段映射定义了一个字段的名称、数据类型、分词器、索引方式、存储方式等属性。映射在索引创建时定义,可以用于约束和规范数据,以提高搜索效率和精度。
在 Elasticsearch 中,每个索引都有一个映射,映射中包含了索引中所有字段的定义。下面是一个映射的示例:
1 | |
在这个映射中,包含了 4 个字段映射:
- title:文档标题字段,数据类型为 text。
- content:文档内容字段,数据类型为 text。
- author:作者字段,数据类型为 keyword。
- timestamp:时间戳字段,数据类型为 date。
每个字段映射可以包含多个属性,常用的属性包括:
- type:字段的数据类型,例如 text、keyword、date 等。
- analyzer:用于对文本字段进行分词的分词器。
- index:指定字段是否需要被索引,例如设置为 false 的字段不会被索引。
- store:指定字段是否需要被存储,例如设置为 true 的字段会被存储在磁盘上,方便进行检索。
- format:指定日期字段的格式。
映射的作用主要有两个:
- 约束数据:映射可以规范字段的数据类型、分词器等属性,从而约束数据的输入和格式,避免输入错误或无效数据的出现,提高搜索的准确性和精度。
- 提高搜索效率:映射可以指定字段的索引方式、存储方式等属性,从而提高搜索效率和速度,避免不必要的计算和 IO 操作。
需要注意的是,和主分片一样,映射一旦定义后,就不能够再修改。因此,在设计映射时需要考虑到数据的需求和未来的扩展性,避免在后期需要修改映射的情况。如果需要修改映射并且保证每个文档都有新的映射,只能够通过重新创建索引的方式来实现。
创建映射
通常情况下,映射会在创建索引时一起被创建出来。但如果已经有索引了,也可以再次创建,只不过新增的映射不会作用在历史数据上。
创建索引时同步创建映射
1
2
3
4
5
6
7
8
9
10PUT /my-index
{
"mappings": {
"properties": {
"fieldName": {
"type": "text"
}
}
}
}在已有的索引上创建映射
1
2
3
4
5
6
7
8PUT /my-index/_mapping
{
"properties": {
"fieldName": {
"type": "text"
}
}
}
创建索引使用到的参数也不用刻意去记,当第一个文档路径被成功创建之后,Elasticsearch 会根据数据结构自动推断出映射。之后,我们只需要根据自己的需求,修改对应的参数即可,比如:分词设置、分析器设置等。
另外,每个字段中还可以再定义properties配置,以此来构建一个复杂的数据结构。
修改映射
注意:这里的修改只是修改了字段的索引方式,其它不能修改。索引的 Mapping 字段只能追加,不能删除和类型修改。
1 | |
从执行指令上来看,不难发现修改映射和添加映射一样,都是使用的 PUT 方法。当映射不存在时会创建一个新的映射,当映射存在时会对其进行修改。
小结
结合上面的内容不难看出,在 Elasticsearch 中,所有涉及到结构层面的东西是不能修改的。比如主分片、映射等,因为这些修改会导致数据不完整,因此不支持修改。如果想要修改,只能够通过重建索引来实现。
建议:在使用 Elasticsearch 之前,应当规划好索引并且设计好数据结构。因为就算是 Elasticsearch 支持索引的迁移,这也会给运维的过程增加很多负担,因为每次的索引迁移都需要关闭索引的写入,这也就导致一段时间内,某些功能将不可用。
除此之外,对于数据量非常庞大,并且允许冷热分离的索引,建议采用一对多别名的方式来使用。可以使用时间维度将数据分成多个索引,随着时间的增长逐步增加。然后将别名与一定时间范围内的索引进行绑定来控制查询范围,比如只允许查询三个月内的数据,其他数据只能后台查询。
文档
Elasticsearch 的文档类似关系型数据库中的一行记录,是日常开发过程中使用最多的操作。因此,文档的操作也是 Elasticsearch 学习的重点。
创建文档
要创建一个文档,需要向 Elasticsearch 的一个索引中添加一个 JSON 格式的文档。虽然 Elasticsearch 支持在第一次创建文档时同步创建索引。但如果想要对索引进行设置,比如主分片数量、固定映射字段等操作,就必须先创建好文档再写入数据,因为同步创建索引时使用的是 Elasticsearch 默认值。
下面是一个创建文档的简单示例:
1 | |
这个例子中,使用了 PUT 请求将一个 JSON 格式的文档添加到名为“my_index”的索引中,该文档的 ID 为“1”。文档包含两个字段:“title”和“content”。
在创建文档时,可以选择为文档指定一个唯一的 ID,或者让 Elasticsearch 自动生成一个唯一的 ID。如果您未指定文档 ID,则 Elasticsearch 会自动生成一个唯一的 ID。
除了使用 PUT 请求之外,还可以使用 POST 请求创建文档。例如:
1 | |
在这种情况下,Elasticsearch 会自动生成一个唯一的 ID,并将文档添加到名为“my_index”的索引中。
注意,如果尝试添加一个具有相同 ID 的文档,则 Elasticsearch 会将新文档替换旧文档。如果您希望将文档添加到索引中而不是替换旧文档,则可以使用 POST 请求,并将“_id”字段设置为一个新的唯一值。
在 Elasticsearch 中,PUT 和 POST 请求都可以用来创建新文档,但它们之间有一些重要的区别:
- PUT 请求必须指定文档 ID,而 POST 请求可以让 Elasticsearch 自动生成文档 ID。
- PUT 请求是幂等的,即重复调用同一个 PUT 请求多次不会导致数据的变化,而 POST 请求不是幂等的。
版本号
每个文档都有一个版本号,使用版本号参数可以达到并发控制的效果。
查询版本号
1 | |
这里查询出 ID 为 1 的文档,返回内容如下:
1 | |
这里有三个重要的信息:
- _version:它是 Elasticsearch 的内部版本号,是 Elasticsearch 在创建或更新文档时自动分配的,也是 Elasticsearch 内部控制幂等的机制,不能作为乐观锁来使用。
- _seq_no:文档序列号,这是一个外部版本,每次修改文档序列号都会增加。它可以作为乐观锁来控制并发操作。
- _primary_term:文档所在的分片号,Elasticsearch 的索引是支持分片的,数据放在哪个分片上是有 Elasticsearch 决定的。因此,使用序列号控制并发操作时,需要向 Elasticsearch 说明文档在哪个分片上。
比如我们在修改数据的时候,使用版本号来控制并发,如下:
1 | |
命令执行后,返回数据如下:
1 | |
当再次执行命令时,返回结果如下:
1 | |
这是会看到报错信息,其中有一段信息内容为version conflict, required seqNo [8], primary term [1]. current document has seqNo [9] and primary term [1]。这段话的大致意思是请求的序列号为 8,但当前文档的序列号是 9。这和我们在使用关系型数据库时,通过查询版本号控制幂等几乎是一样的。
修改文档
注意:上面提到创建文档之后,再次使用命令进行操作会对文档进行修改。这里的修改实际上是覆盖数据。
比如我们执行
1
2
3
4PUT /my_index/_doc/1
{
"title": "Elasticsearch文档ssss"
}那么 ID 为 1 的文档将会被新文档覆盖,当再次查询时,会发现
content字段没有了。因此我们在修改文档时,尽可能使用部分修改,即只修改列举出的字段。尽可能不要使用覆盖操作。
Elasticsearch 支持两种文档的修改方式,并且更新只能使用 POST 方式,另外从示例中可以看出,局部修改时也可以使用序列号来进行并发控制。
局部文档更新
1
2
3
4
5
6POST /my_index/_update/1?if_seq_no=15&if_primary_term=1
{
"doc": {
"title": "Elasticsearch文档局部更新2"
}
}使用脚本更新文档
1
2
3
4
5
6POST /my_index/_update/1?if_seq_no=16&if_primary_term=1
{
"script": {
"source": "ctx._source.title = 'Elasticsearch文档脚本更新'"
}
}Elasticsearch 脚本使用的是
Painless语言,它的语法类似Groovy语言,是 Java 的扩展脚本。因此这里可以直接使用它的语法来做更深入的操作。比如:我们在修改的时候,文档内所有的英文改成大写。
1
2
3
4
5
6POST /my_index/_update/1?if_seq_no=17&if_primary_term=1
{
"script": {
"source": "ctx._source.title = 'Elasticsearch文档脚本更新'.toUpperCase()"
}
}这里可以看到,字符串后面调用了一个
toUpperCase()方法。
按条件修改
除了上面提到的根据 ID 修改文档外,也可以根据条件来修改。
1 | |
条件修改是一个非常危险的操作,并且一旦修改文档就无法恢复。尤其是该条件字段在 Elasticsearch 中被分词了,这将会导致所有分词匹配的数据都会被修改。
因此,如果想要使用条件修改就一定要将条件进行精准匹配。如果字段被分词可以在查询字段的后面加上.keyword来精准查询,如果字段没有被分词,可以将match替换成term。
另外,按条件修改只能通过脚本方式修改,不支持局部更新的方式来修改。如果想要修改多个字段,可以使用 params。比如下面这个例子:
1 | |
params 中定义了修改的参数,然后在 source 中使用语法拼接,或在 source 中使用数组来定义每一个需要更新的字段。
删除文档
删除文档时,可以根据 ID 来删除,也可以根据条件来删除。
根据 ID 删除
1
DELETE /my_index/_doc/1按条件删除
1
2
3
4
5
6
7
8POST /my_index/_delete_by_query
{
"query": {
"match": {
"title.keyword": "Elasticsearch文档2"
}
}
}条件删除是一个非常危险的操作,并且一旦删除文档就无法恢复。尤其是该条件字段在 Elasticsearch 中被分词了,这将会导致所有分词匹配的数据都会被删除。
因此,如果想要使用条件删除就一定要将条件进行精准匹配。如果字段被分词可以在查询字段的后面加上
.keyword来精准查询,如果字段没有被分词,可以将match替换成term。
查询文档
以下是 Elasticsearch 常用的一些查询类型:
- match 查询:该查询将在指定的字段中搜索包含给定单词或短语的文档。它支持模糊匹配和距离计算,并且可以与不同的运算符(如 AND 和 OR)结合使用。
- term 查询:该查询将在指定字段中查找包含指定术语的文档。它是精确匹配,不会分词查询字符串,而是在原始文本上执行匹配。
- range 查询:该查询将返回指定范围内的文档。可以指定数字、日期和其他类型的范围。
- bool 查询:该查询将允许你组合多个查询条件,使用布尔运算符进行联合,例如 AND、OR 和 NOT。
- prefix 查询:该查询将在指定字段中搜索以给定前缀开头的文档。
- wildcard 查询:该查询将在指定字段中搜索匹配给定通配符模式的文档。
- fuzzy 查询:该查询将在指定字段中搜索与给定单词类似的文档,支持模糊匹配。
- match_phrase 查询:该查询将在指定字段中搜索包含给定短语的文档,但短语必须以相同的顺序和连续出现。
- match_all 查询:该查询将返回所有文档,可以用于不带任何搜索条件的查询,但是需要指定索引名称。
- multi_match 查询:该查询将在多个字段中搜索包含指定单词或短语的文档。
下面开始说明一些常用的查询案例
查询所有
1 | |
也可以直接使用
1 | |
查询时返回指定字段
1 | |
单条件查询
比如查询title字段:
1 | |
多条件 AND 查询
比如查询title和content都包含某个值:
1 | |
多条件 OR 查询
multi_match:多匹配方式。
1 | |
should:布尔方式。
1 | |
范围查询
比如文档中有一个price字段,查询价格在25.00到50.00的数据。
1 | |
分页查询
1 | |
参数说明:
- from:页索引,即:(页码 - 1) * 页大小。
- size:页大小。
查询排序
比如按照价格降序
1 | |
查询总记录数
1 | |
模糊查询
模糊查询有很多种方式,这里介绍两个常用的。但由于模糊查询对性能的损耗较高,通常情况下不建议模糊查询,而尽可能给予分词搜索。
使用通配符进行模糊查询
1
2
3
4
5
6
7
8
9
10GET /my_index/_search
{
"query": {
"wildcard": {
"content.keyword": {
"value": "*se?rch*"
}
}
}
}匹配说明:
*:表示忽略匹配的内容,也可以理解为模糊位置。
?:是占位符,比如
*se?rch*中,se和rch中间有一个字符可以任意。有几个?表示有几个任意字符。使用相似度进行模糊查询
1
2
3
4
5
6
7
8
9
10
11GET /my_index/_search
{
"query": {
"fuzzy": {
"content": {
"value": "海量",
"fuzziness": 1
}
}
}
}重要参数说明:
- fuzziness:fuzziness 参数用于控制模糊查询的容错率。它指定了查询时能够容忍的最大编辑距离,即允许查询中的单词和目标词之间的最大差异量。其中 1 表示容忍一个字符的差异,值越大匹配度越高,但相对的查询出的数据越不精准。
分词器
Elasticsearch 安装完成后,默认只有一个分词器
standard。这是一个标准分词器,它可以对英文进行很好的分词处理。但是如果使用中文查询,他会将每一个字分成一个词,这对 Elasticsearch 的损耗相当大,因此需要安装一个中文分词器来处理。常用的分词是 ik 中文分词器。
ik 分词器有以下几种常用的分词方式:
- ik_smart:智能分词,可以将一些较复杂的词汇进行正确地拆分。
- ik_max_word:细粒度分词,会将所有可能的词语都进行切分,速度较慢,但精度较高。
- ik_word:粗粒度分词,会将一些常见的词语组合在一起,速度较快,但精度较低。
- ik_pinyin:拼音分词,将汉字转换成拼音,并进行切分。
- ik_synonym:同义词分词,根据自定义的同义词词典进行分词。
- ik_english:英文分词,对英文文本进行分词。
安装 IK 分词器
分词器的版本要和 Elasticsearch 的版本一致,否则无法启动。
下载插件包
下载分词器包
1
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.7/elasticsearch-analysis-ik-7.17.7.zip如果使用
wget下载比较慢,也可以下载到本地电脑,然后上传到服务器。
安装分词器
解压分词器包到插件目录
这里将插件包解压到了插件目录下的
ik目录,ik目录无需提前创建,解压时会自动创建。1
unzip ~/elasticsearch-analysis-ik-7.17.7.zip -d ~/elasticsearch/plugins/ik/给 ik 目录授权
1
chmod o+xrw -R ~/elasticsearch/plugins/ik/指定 ik 目录的所属用户
前面我们已经给插件目录设置所属用户了,由于 ik 目录是后创建的,所以需要重新设置一次。
1
chown 1000 -R ~/elasticsearch/plugins/ik/重新启动 Elasticsearch 集群
1
docker-compose -f ~/elasticsearch/elasticsearch-compose.yml restart重启后,可以使用
watch docker ps监控一段时间,如果一切正常说明重启成功。检查分词器是否生效
选择任意一个 Elasticsearch 节点容器进入。
1
docker exec -it <容器ID> /bin/bash成功进入容器后,使用
elasticsearch-plugin命令查看当前的插件列表。1
elasticsearch-plugin list命令执行结果如下:
1
2-- console log --
ik到这里可以看到,已经安装了一个 ik 插件。
使用案例
我们可以使用 Elasticsearch de _analyze命令来分析一段话,来测试中文分词的具体效果。
1 | |
命令执行结果如下:
1 | |
从执行结果可以看到,关键词被分成了一个完整的词,而不是一个字一个词。
如果使用默认的standard分词器,会看到中文被分成了一个字一个词。