Elastic Stack 8 快速上手
本系列 Elastic Stack 实战教程总共涵盖 5 个实验,目的是帮助初学者快速掌握 Elastic Stack 的基本技能。
- 实验 1:Elastic Stack 8 快速上手
- 实验 2:ILM 索引生命周期管理
- 实验 3:快照备份与恢复
- 实验 4:使用 Fleet 管理 Elastic Agent 监控应用
- 实验 5:Elasticsearch Java API Client 开发
1 Elastic Stack 介绍
Elastic Stack 的核心产品包括 Elasticsearch、Kibana、Beats 和 Logstash,可以帮助用户安全可靠地获取任何来源、任何格式的数据,并实时地对数据进行搜索、分析和可视化。
Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,Elasticsearch 作为 Elastic Stack 的核心,集中存储数据。
Kibana 提供了酷炫的可视化界面, 使用 Kibana 可以对 Elasticsearch 索引中的数据进行搜索、查看以及交互操作,用户可以利用图表、表格以及地图对数据进行多元化的分析和呈现。
Beats 和 Logstash 提供了数据摄取的功能。其中 Beats 是一个轻量级的数据摄取工具,Logstash 则提供了类似于 ELT 的功能。
在下面的实验中,将会介绍如何在 ECS 实例上部署 Elasticsearch 和 Kibana,以及 Elasticsearch 常用 API 的使用方法。
2 部署 Elasticsearch
本小节将指导您如何部署一个单节点的 Elasticsearch 集群。
出于安全原因,Elasticsearch 默认不允许使用 root 用户启动。执行以下命令,创建 elastic 用户,设置密码为 elastic123 ,并切换到该用户。
useradd -s /bin/bash -m elastic
echo "elastic:elastic123" | chpasswd
su - elastic
下载和解压 Elasticsearch 安装文件。
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.2.3-linux-x86_64.tar.gz
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.2.3-linux-x86_64.tar.gz.sha512
shasum -a 512 -c elasticsearch-8.2.3-linux-x86_64.tar.gz.sha512
tar -xzf elasticsearch-8.2.3-linux-x86_64.tar.gz
cd elasticsearch-8.2.3/
Elasticsearch 目录结构如下所示。
执行如下命令,启动 Elasticsearch。
./bin/elasticsearch
启动成功后,会输出以下内容,保存好以下两个内容:
- elastic 用户密码。
- Kibana 注册 token。
在启动过程中 Elasticsearch 需要连接公网获取 IP 地址地理位置的相关信息,由于网络原因可能导致数据下载失败而产生以下报错,该报错对 Elasticsearch 运行没有影响,用户可以忽略。
浏览器输入 https://<ESC 公网 IP>:9200
访问 Elasticsearch,注意本场景中提供了两台 ECS 实例,请填写正确的 ECS 公网 IP 地址。输入用户名:elastic,密码:VMtfK-YaNOptoBDnNaRY(根据上面输出填写)。用户认证通过后,会显示以下内容:
- name: 节点名,默认使用主机名作为节点名。
- cluster_name: 集群名,默认的集群名是 elasticsearch,同一个集群中的所有节点的 cluster.name 要保持一致。
- cluster_uuid: 集群的唯一标识符。
- version: 版本信息。
至此我们已经成功部署了一个单节点的 Elasticsearch 集群。
3 部署 Kibana
本小节将指导您如何部署 Kibana 并连接到 Elasticsearch。
打开一个新的终端, 切换到 elastic 用户。
su - elastic
执行如下命令,下载和解压 Kibana 安装文件。
curl -O https://artifacts.elastic.co/downloads/kibana/kibana-8.2.3-linux-x86_64.tar.gz
curl https://artifacts.elastic.co/downloads/kibana/kibana-8.2.3-linux-x86_64.tar.gz.sha512 | shasum -a 512 -c -
tar -xzf kibana-8.2.3-linux-x86_64.tar.gz
cd kibana-8.2.3/
Kibana 目录结构如下所示:
编辑 config/kibana.yml 配置文件,修改以下内容,允许 Kibana 服务监听本机所有 IP 地址。
server.host: "0.0.0.0"
执行如下命令,启动 Kibana。
./bin/kibana
启动成功后,会输出以下内容。
浏览器输入 http://<ESC 公网 IP>:5601/?code=321491
访问 Kibana 界面,code=321491 修改为上图终端输出的内容。在 Enrollment token 输入框中填入第 1 小节启动 Elasticsearch 后输出的 Kibana 注册 token,然后点击 Configure Elastic。
接着 Kibana 会自动完成注册设置。
Kibana 注册成功后会出现登录界面,输入 elastic 用户名密码登录 Elasticsearch。
在注册成功后,在 config/kibana.yml 配置文件中会自动添加 Elasticsearch 相关的连接信息和 SSL 证书,用户无需关注。
4 往集群中注册新节点
本小节将指导您如何往 Elasticsearch 集群中注册新节点。
首先在第一个终端,键盘按 Ctrl + C 停止在前台运行的 Elasticsearch 进程。
4.1 修改第一个节点配置
默认情况下,Elasticsearch transport 服务的 9300 端口只在 127.0.0.1 上监听。如果要想和其他节点进行交互,我们需要更改当前节点的 config/elasticsearch.yml 文件,修改内容如下。
transport.host: 0.0.0.0
当允许 transport 服务在非环回地址监听后,Elasticsearch 将处于生产模式,在启动之前需要进行一些引导检查,我们需要修改一些系统参数确保能够通过检查。
在当前 elastic 用户下执行 exit
命令退回 root 用户,在 root 用户修改以下系统参数。
增加进程可使用的最大内存映射区域数,编辑 /etc/sysctl.conf 文件,添加以下内容。
vm.max_map_count=262144
执行以下命令立即生效:
sysctl -p
增加进程可使用的最大文件描述符数量,编辑 /etc/security/limits.conf 文件,添加以下内容。
elastic - nofile 65535
执行以下命令立即生效:
ulimit -n 65535
切换到 elastic 用户。
su - elastic
重新启动 Elasticsearch,-d 参数表示在后台运行 Elasticsearch。
cd elasticsearch-8.2.3
./bin/elasticsearch -d
执行 tail -f logs/elasticsearch.log
命令可以查看 Elasticsearch 的启动日志。
确认 Elasticsearch 启动成功以后,执行以下命令,生成新的注册 token,用于注册新节点到 Elasticsearch 集群。
bin/elasticsearch-create-enrollment-token -s node
# 返回的 token
eyJ2ZXIiOiI4LjIuMyIsImFkciI6WyIxMC4yMC42MS4xNzk6OTIwMCJdLCJmZ3IiOiJiYTM3YjVmMTU4YTc4MmFiNWQ3N2M0YWI1ODFiMjc1ZjMxMTYxZDcxOWQ2YTA2NTU1NGU0MjBjYzNlZTc3YzMwIiwia2V5IjoiY1A3WGxJRUJFLU9sOTh0Qm5Deno6UXZTWEdOZ3BTYktaWXdic19pTS1fUSJ9
4.2 注册新节点
接下来配置新节点,打开一个新的终端, 执行 ssh root@<ESC 公网 IP>
命令登录到另一个节点上。
在 root 用户下修改以下系统参数。 增加进程可使用的最大内存映射区域数,编辑 /etc/sysctl.conf 文件,添加以下内容。
vm.max_map_count=262144
执行以下命令立即生效:
sysctl -p
增加进程可使用的最大文件描述符数量,编辑 /etc/security/limits.conf 文件,添加以下内容。
elastic - nofile 65535
执行以下命令立即生效:
ulimit -n 65535
执行以下命令,创建 elastic 用户,设置密码为 elastic123 ,并切换到该用户。
useradd -s /bin/bash -m elastic
echo "elastic:elastic123" | chpasswd
su - elastic
下载和解压 Elasticsearch 安装文件。
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.2.3-linux-x86_64.tar.gz
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.2.3-linux-x86_64.tar.gz.sha512
shasum -a 512 -c elasticsearch-8.2.3-linux-x86_64.tar.gz.sha512
tar -xzf elasticsearch-8.2.3-linux-x86_64.tar.gz
cd elasticsearch-8.2.3/
在当前节点的 config/elasticsearch.yml 文件中,添加以下内容。
transport.host: 0.0.0.0
执行以下命令,启动新节点,并往 Elasticsearch 集群进行注册,在 --enrollment-token
参数后面跟上前面生成的 token。
bin/elasticsearch --enrollment-token eyJ2ZXIiOiI4LjIuMyIsImFkciI6WyIxMC4yMC42MS4xNzk6OTIwMCJdLCJmZ3IiOiJiYTM3YjVmMTU4YTc4MmFiNWQ3N2M0YWI1ODFiMjc1ZjMxMTYxZDcxOWQ2YTA2NTU1NGU0MjBjYzNlZTc3YzMwIiwia2V5IjoiY1A3WGxJRUJFLU9sOTh0Qm5Deno6UXZTWEdOZ3BTYktaWXdic19pTS1fUSJ9
在 Kibana 的 Dev Tools 中执行以下命令,查询集群中的节点,可以看到第二个节点已经成功加入集群。默认使用主机名作为节点名。
GET _cat/nodes?v
# 返回结果
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
10.20.61.179 21 95 10 0.14 0.27 0.20 cdfhilmrstw * elastic-demo
10.20.61.180 6 85 2 0.74 0.28 0.09 cdfhilmrstw - node-2
在注册成功后,在 config/elasticsearch.yml 配置文件中会自动添加 Elasticsearch 相关的配置信息,用户无需关注。
节点的所需 SSL 证书也会自动拷贝到新节点上。
5 写入文档
本小节将指导您如何将数据写入 Elasticsearch。
Elasticsearch 具有 Schemaless的能力,能够自动检测 JSON 源数据的类型并为数据中的新字段创建新的映射。正是由于这种灵活的特性,使得我们可以不用事先为索引定义 mapping(映射)就可以往索引中写入数据。
点击 Management -> Dev Tools -> Console,打开 Kibana Console 界面。在左侧面板中执行以下命令,往 books 索引中写入一条文档。Elasticsearch 会自动帮我们创建 books 索引。
# 写入单条文档
POST books/_doc
{
"title": "Programming Kubernetes",
"isbn": "9781617297618",
"pageCount": 345,
"category": "Cloud Native"
}
在右侧面板会返回响应结果,可以看到文档已经被成功创建了,doc id 是随机生成的 YTeEhYEBGHa5rBjoiL-Q。
如果想要在写入文档时指定 doc id,可以使用 PUT <index-name>/_doc/<doc-id>
。如果指定 id 的文档不存在,则直接创建新的文档;否则先删除现有文档,再创建新的文档,同时文档版本号(_version, _seq_no)会增加。
# 设置写入的 doc id 为 1
PUT books/_doc/1
{
"title": "Istio in Action",
"isbn": "9781617295829",
"pageCount": 480,
"category": "Cloud Native"
}
使用 bulk API 可以在单次请求中批量写入多条文档,这样能极大减少写入请求的网络开销。
POST books/_bulk
{"index": {"_id": 2}}
{"title":"Elasticsearch in Action","isbn":"9781617299858","pageCount":475,"category": "Search"}
{"index": {"_id": 3}}
{"title":"Relevant Search","isbn":"9781617292774","pageCount":360,"category": "Search"}
{"index": {"_id": 4}}
{"title":"Modern Java in Action","isbn":"9781617293566","pageCount":592,"category": "Java"}
6 查询文档
本小节将指导您如何查询 Elasticsearch 中的数据。
使用 GET <index-name>/_doc/<doc-id>
可以获取索引中指定 doc id 的文档。执行以下命令,获取 doc id 为 1 的文档。
GET books/_doc/1
响应结果如下,除了返回文档的内容以外,还包含了一些元数据字段:
- _index: 文档所属的索引名。
- _id: 文档 id。
- _version: 文档的版本号,_version 属于单个文档,修改删除操作会在当前文档的 _version 基础上自增 1。
- _seq_no: 索引的版本号,_seq_no 属于整个索引,修改删除操作会整个索引的 _seq_no 基础上自增 1。
- _primary_term: 每当主分片发生重新分配时(例如故障转移,主分片选举),_primary_term 会自增 1。该参数主要是为了防止主分片重新分配到新节点后,原主分片所在的节点恢复后覆盖新节点上主分片写入的数据。
- _source: 文档原始的 JSON 数据。
{
"_index" : "books", // 索引名
"_id" : "1",
"_version" : 2,
"_seq_no" : 3,
"_primary_term" : 1,
"found" : true,
"_source" : { // 文档内容
"title" : "Istio in Action",
"isbn" : "9781617295829",
"pageCount" : 480,
"category" : "Cloud Native"
}
}
查询 books 索引,不指定任何过滤条件,默认最多返回 10 条文档。
GET books/_search
返回了当前索引中的全部 4 条文档。
- took: 搜索请求总共耗费了多少毫秒。
- timed_out: 查询是否超时。如果超时,Elasticsearch 将会返回超时前已经成功从每个分片获取的结果。
- _shards: 查询的分片计数。
- total: 需要查询的分片总数,包括未分配的分片。
- successful: 执行请求成功的分片数。
- skipped: 跳过的分片数,Elasticsearch 在查询前先会通过轻量级的检查判断分片上是否含符合条件的文档,通常发生在查询中带有 range 过滤条件的情况下。
- failed: 执行请求失败的分片数。
- hits: 返回的文档和元数据。
- total: 匹配到的文档总数。
- max_score: 返回最高的文档分数。
- hits: 文档对象数组。
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 5,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "books",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"title" : "Istio in Action",
"isbn" : "9781617295829",
"pageCount" : 480,
"category" : "Cloud Native"
}
},
{
"_index" : "books",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"title" : "Elasticsearch in Action",
"isbn" : "9781617299858",
"pageCount" : 475,
"category" : "Search"
}
},
{
"_index" : "books",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Relevant Search",
"isbn" : "9781617292774",
"pageCount" : 360,
"category" : "Search"
}
},
{
"_index" : "books",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"title" : "Modern Java in Action",
"isbn" : "9781617293566",
"pageCount" : 592,
"category" : "Java"
}
},
{
"_index" : "books",
"_id" : "ZDfNjoEBGHa5rBjoYL_5",
"_score" : 1.0,
"_source" : {
"title" : "Programming Kubernetes",
"isbn" : "9781617297618",
"pageCount" : 345,
"category" : "Cloud Native"
}
}
]
}
}
Elasticsearch 在不设置字段映射的情况下, 默认会将字符串类型的字段设置成 text 类型,并加上一个 keyword 子字段,子字段的类型是 keyword。text 类型的字段默认使用 standard 分析器解析文本,按照 Unicode 文本分割算法对文本进行切分,去除标点符号,并将切分后的单词转换为小写,主要用于全文搜索;keyword 类型的字段使用 keyword 分析器解析文本,不对文本进行切分,保留原始的文本内容,用于精确匹配和聚合查询。
使用 match 查询 title 字段中含有 action 的文档,不区分大小写。
GET books/_search
{
"query": {
"match": {
"title": "action"
}
}
}
这次只返回了 title 中含有 action 的 3 条文档。
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 0.35667494,
"hits" : [
{
"_index" : "books",
"_id" : "1",
"_score" : 0.35667494,
"_source" : {
"title" : "Istio in Action",
"isbn" : "9781617295829",
"pageCount" : 480,
"category" : "Cloud Native"
}
},
{
"_index" : "books",
"_id" : "2",
"_score" : 0.35667494,
"_source" : {
"title" : "Elasticsearch in Action",
"isbn" : "9781617299858",
"pageCount" : 475,
"category" : "Search"
}
},
{
"_index" : "books",
"_id" : "4",
"_score" : 0.31387398,
"_source" : {
"title" : "Modern Java in Action",
"isbn" : "9781617293566",
"pageCount" : 592,
"category" : "Java"
}
}
]
}
}
使用 term 查询精确匹配 category.keyword 字段值是 Cloud Native 的文档。上面介绍到 keyword 类型的字段保留原始文本,用于精确匹配,因此这里的查询区分大小写,如果查询 cloud native 将匹配不到文档。
GET books/_search
{
"query": {
"term": {
"category.keyword": {
"value": "Cloud Native"
}
}
}
}
返回结果如下,匹配到了 2 条文档。
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.87546873,
"hits" : [
{
"_index" : "books",
"_id" : "1",
"_score" : 0.87546873,
"_source" : {
"title" : "Istio in Action",
"isbn" : "9781617295829",
"pageCount" : 480,
"category" : "Cloud Native"
}
},
{
"_index" : "books",
"_id" : "ZDfNjoEBGHa5rBjoYL_5",
"_score" : 0.87546873,
"_source" : {
"title" : "Programming Kubernetes",
"isbn" : "9781617297618",
"pageCount" : 345,
"category" : "Cloud Native"
}
}
]
}
}
如果我们想同时通过多个条件查询文档,可以使用 bool 组合查询。bool 查询接受以下 4 个参数:
- must: must 中的所有条件都要符合,计算相关性得分。
- must_not: 排除所有符合 must_not 条件的文档,不计算相关性得分。
- filter: 只过滤符合条件的文档,不计算相关性得分。
- should:
- 情况 1: bool 查询中只包含 should ,不包含 must 查询,此时文档必须满足至少一个条件,minimum_should_match 可以控制满足条件的个数或者百分比。
- 情况 2:bool 查询中同时包含 should 和 must 查询,此时文档不必满足 should 中的条件,但是如果满足条件,会增加相关性得分。
下面的查询表示同时满足 title 字段中含有 action 以及 category.keyword 字段值是 Cloud Native 两个条件的文档才算匹配。
GET books/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "action"
}
},
{
"term": {
"category.keyword": {
"value": "Cloud Native"
}
}
}
]
}
}
}
返回结果如下,只有 1 条文档同时满足以上两个条件。
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.3991625,
"hits" : [
{
"_index" : "books",
"_id" : "1",
"_score" : 1.3991625,
"_source" : {
"title" : "Istio in Action",
"isbn" : "9781617295829",
"pageCount" : 480,
"category" : "Cloud Native"
}
}
]
}
}
Elasticsearch 除了文本搜索以外,还支持聚合查询,可以帮助我们对数据进行分析。例如我们可以通过以下命令查询 books 索引中每种类别书籍的数量。
GET books/_search
{
"size": 0, // 不返回文档,只返回聚合结果
"aggs": { // 聚合查询
"my_category_agg": {
"terms": {
"field": "category.keyword",
"size": 10
}
}
}
}
返回结果如下,可以看到 Cloud Native 类别的书籍有 2 个,Search 类别的书籍有 1 个, Java 类别的书籍有 1 个。
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 5,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"my_category_agg" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "Cloud Native",
"doc_count" : 2
},
{
"key" : "Search",
"doc_count" : 2
},
{
"key" : "Java",
"doc_count" : 1
}
]
}
}
}
7 更新文档
本小节将指导您如何更新 Elasticsearch 中的数据。
使用 PUT <index-name>/_doc/<doc-id>
这种语法的效果是覆盖数据,可以理解为先删除现有文档,再创建新的文档。如果该 doc id 的文档不存在,则创建一个新的文档。
PUT books/_doc/1
{
"title": "Cloud Native Patterns",
"isbn": "9781617294297",
"category": "Cloud Native"
}
查询 doc id 为 1 的这条文档。
GET books/_doc/1
返回结果如下,新的文档中只含有我们更新时指定 3 个字段,而原始文档中的 pageCount 字段消失了。
{
"_index" : "books",
"_id" : "1",
"_version" : 2,
"_seq_no" : 5,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "Cloud Native Patterns",
"isbn" : "9781617294297",
"category" : "Cloud Native"
}
}
那么如何在不影响文档中其他字段的情况下对文档进行更新呢?可以使用 PUT <index-name>/_update/<doc-id>
语法,在请求体的 doc 参数中指定要更新的字段内容。
POST books/_update/1
{
"doc": { // 指定要更新的字段内容
"pageCount": 400
}
}
查询 doc id 为 1 的这条文档。
GET books/_doc/1
返回结果如下,pageCount 字段的值被设置为了 400,其他字段保持不变。
{
"_index" : "books",
"_id" : "1",
"_version" : 3,
"_seq_no" : 6,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "Cloud Native Patterns",
"isbn" : "9781617294297",
"category" : "Cloud Native",
"pageCount" : 400
}
}
Elasticsearch 还支持通过脚本的方式更新字段,通过编写脚本可以实现更加复杂的处理逻辑。下面命令表示,当 pageCount 字段的值大于 200 时,将 pageCount 的值加 200,否则将 pageCount 的值设置为 99。
POST books/_update/1
{
"script": {
"source": """
if(ctx._source.pageCount > 200) {
ctx._source.pageCount += 200;
}else {
ctx._source.pageCount = 99;
}
"""
}
}
返回结果如下,由于 pageCount 原本的值 400 大于 200,因此 pageCount 的值被更新为了 600(400+200)。
{
"_index" : "books",
"_id" : "1",
"_version" : 4,
"_seq_no" : 7,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "Cloud Native Patterns",
"isbn" : "9781617294297",
"category" : "Cloud Native",
"pageCount" : 600
}
}
除了单条更新文档以外,我们还可以使用 Update By Query API 来根据查询条件批量更新文档。执行以下命令,为 pageCount 小于 400 的书籍添加一个新的字段 description,设置值为 quickStart。
POST books/_update_by_query
{
"query": {
"range": {
"pageCount": {
"lte": 400
}
}
},
"script": {
"source": "ctx._source.description = 'quickStart'"
}
}
不论是关系型数据库,还是 Elasticsearch,只要有数据更新,并发控制是永恒的话题。在 Elasticsearch 中,数据的更新基于版本进行乐观锁并发控制。在 Elasticsearch 6.7 版本之后推荐使用 _seq_no 和 _primary_term 两个参数进行并发控制。
先获取当前索引的 _seq_no 和 _primary_term 的值。
GET books/_doc/1
返回结果如下。
{
"_index" : "books",
"_id" : "1",
"_version" : 4,
"_seq_no" : 9,
"_primary_term" : 1,
"found" : true,
"_source" : {
"pageCount" : 400,
"isbn" : "9781617294297",
"description" : "quickStart",
"title" : "Cloud Native Patterns",
"category" : "Cloud Native"
}
}
更新文档时指定 _seq_no 和 _primary_term。
PUT books/_doc/1?if_seq_no=9&if_primary_term=1
{
"doc": {
"pageCount": 500
}
}
返回结果如下,提示更新成功。
{
"_index" : "books",
"_id" : "1",
"_version" : 5,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 10,
"_primary_term" : 1
}
如果指定的 _seq_no 和 _primary_term 不等于当前更新的文档中的值,那么更新将会失败。
PUT books/_doc/1?if_seq_no=9&if_primary_term=1
{
"doc": {
"pageCount": 500
}
}
返回结果如下,提示版本冲突,当前文档 _seq_no 的值已经更新为 10 了,但是还使用老版本 _seq_no 的值 9 来更新。
{
"error" : {
"root_cause" : [
{
"type" : "version_conflict_engine_exception",
"reason" : "[1]: version conflict, required seqNo [9], primary term [1]. current document has seqNo [10] and primary term [1]",
"index_uuid" : "3bYmNTQpRyqOmcwUG1HvNg",
"shard" : "0",
"index" : "books"
}
],
"type" : "version_conflict_engine_exception",
"reason" : "[1]: version conflict, required seqNo [9], primary term [1]. current document has seqNo [10] and primary term [1]",
"index_uuid" : "3bYmNTQpRyqOmcwUG1HvNg",
"shard" : "0",
"index" : "books"
},
"status" : 409
}
8 删除文档
本小节将指导您如何删除 Elasticsearch 中的数据。
使用 DELETE <index-name>/_doc/<doc-id>
语法可以删除索引中指定 doc id 的文档。执行以下命令,删除 doc id 为 1 的文档。
DELETE books/_doc/1
除了单条更新文档以外,我们还可以使用 Delete By Query API 来根据查询条件批量删除文档。执行以下命令,删除所有 pageCount 小于等于 400 的文档。
POST books/_delete_by_query
{
"query": {
"range": {
"pageCount": {
"lte": 400
}
}
}
}
9 索引设置
本小节将指导您如何设置 Elasticsearch 中的索引。
Elasticsearch 便捷的特性使得我们可以在不对索引做任何设置的情况下,便可以对索引进行读写操作。然而,默认设置有时候并不能满足我们的需求。
比如说,我们有一个 3 节点 Elasticsearch 集群,我们想在搜索数据时尽可能地利用多节点的优势,提高搜索的效率。那么我们可以在 settings 中通过 number_of_shards
参数修改索引的分片数。
再比如,默认情况下,数字类型的字段将被映射为 long 类型,pageCount 字段表示书籍的页数,数字并不会很大。那么我们可以在 mappings 中显式指定 pageCount 字段的类型为 integer,从而节省存储空间。
执行以下命令,创建一个新的索引 index-1,设置索引分片数为 3,设置 pageCount 字段的类型为 integer。
PUT index-1
{
"settings": { // 索引设置
"number_of_shards": 3 // 设置索引分片数,默认是 1
},
"mappings": { // 字段映射
"properties": {
"pageCount": {
"type": "integer" // 将字段类型设置为 integer,节省存储空间
}
}
}
}
查看索引设置。
GET index-1
返回结果如下。
{
"index-1" : {
"aliases" : { },
"mappings" : {
"properties" : {
"pageCount" : {
"type" : "integer" // 字段类型为 integer
}
}
},
"settings" : {
"index" : {
"routing" : {
"allocation" : {
"include" : {
"_tier_preference" : "data_content"
}
}
},
"number_of_shards" : "3", // 索引分片数
"provided_name" : "index-1",
"creation_date" : "1655965811025",
"number_of_replicas" : "1",
"uuid" : "pP-M1Re2Qg2k2H6vztvkYg",
"version" : {
"created" : "8020399"
}
}
}
}
}
10 文本分析
本小节将指导您如何使用 Analyze API 测试分析器,以及如何自定义分析器。
在第 5 小节中,我们提到了 standard 和 keyword 两个分析器,这两个分析器属于 Elasticsearch 内置的分析器,我们可以直接拿来使用。有关 Elasticsearch 内置分析器的详情参见 Built-in analyzer reference。
Elasticsearch 提供了 Analyze API 用于查看分词器是如何解析文本内容的,在为字段设置分析器之前可以使用 Analyze API 来测试分析器是否满足需求。
执行以下命令,使用默认的 standard 分析器分析文本。
POST _analyze
{
"analyzer": "standard", // standard 是默认使用的分析器,可以省略
"text": "Elasticsearch in Action."
}
返回结果如下,文本被切分为 elasticsearch, in, action 3 个单词,去掉了末尾的句号,并且转换为小写,tokens 数组中的每个参数含义如下:
- token: 切分后的词项。
- start_offset: 词项在文本中的起始偏移。
- end_offset: 词项在文本中的结束偏移。
- type: 词项的类型。
- position: 分词的位置,第 1 个词项从 0 开始。
{
"tokens" : [
{
"token" : "elasticsearch",
"start_offset" : 0,
"end_offset" : 13,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "in",
"start_offset" : 14,
"end_offset" : 16,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "action",
"start_offset" : 17,
"end_offset" : 23,
"type" : "<ALPHANUM>",
"position" : 2
}
]
}
使用 whitespace 分析器分析相同的文本。
POST _analyze
{
"analyzer": "whitespace",
"text": "Elasticsearch in Action."
}
返回结果如下。可以发现 whitespace 分析器根据空格切分单词,不做小写转换,也不会去除标点符号。
{
"tokens" : [
{
"token" : "Elasticsearch",
"start_offset" : 0,
"end_offset" : 13,
"type" : "word",
"position" : 0
},
{
"token" : "in",
"start_offset" : 14,
"end_offset" : 16,
"type" : "word",
"position" : 1
},
{
"token" : "Action.",
"start_offset" : 17,
"end_offset" : 24,
"type" : "word",
"position" : 2
}
]
}
使用 keyword 分析器分析文本。
POST _analyze
{
"analyzer": "keyword",
"text": "Elasticsearch in Action."
}
返回结果如下,keyword 分析器不会对文本进行分词处理,原始文本被完整地保留为一个词项。
{
"tokens" : [
{
"token" : "Elasticsearch in Action.",
"start_offset" : 0,
"end_offset" : 24,
"type" : "word",
"position" : 0
}
]
}
如果我们想要对索引中已有字段的分析器进行测试,可以通过以下方式指定索引字段的分析器对文本进行分析。category.keyword 是 keyword 类型的字段,因此测试文本的时候使用的便是 keyword 分析器。
POST books/_analyze
{
"field": "category.keyword",
"text": "Cloud Compute"
}
返回结果如下。
{
"tokens" : [
{
"token" : "Cloud Compute",
"start_offset" : 0,
"end_offset" : 13,
"type" : "word",
"position" : 0
}
]
}
以上提到的 standard, whitespace, keyword 三种分析器都是 Elasticsearch 内置的分析器,我们也可以根据实际需求自定义分析器。
一个标准的 Analyzer(分析器)由以下 3 部分组成:
- Character filters(字符过滤器):对输入的文本字符进行第一步处理,例如去掉 HTML 标签(htlm_strip),或者将表情字符转换成英文单词(mapping)等。
- Tokenizer(分词器):按照指定的规则对文本进行分词处理,例如按照空格切分单词(whitespace)。分词器还负责标记每个单词的顺序、位置以及单词在原文本中开始和结束的偏移量。
- Token filters(分词过滤器):对切分后的单词进行处理,进而对分词结果进行归一化处理,例如字母的大小写转换(uppercase/lowercase),删除停用词(stop),标记同义词(synonym),提取词干(snowball)等等。
在分析器中,Tokenizer 是必须设置的,有且只有一个,Character filters 和 Token filters 是可选的,以数组的形式进行设置,按顺序执行。
如下所示,创建一个自定义分析器 my-analyzer,Character filters 使用 html_strip 字符过滤器剔除输入文本中 HTML 标签,tokenizer 使用 whitespace 分词器按照空格切分单词,Token filters 使用 uppercase 字符过滤器将切分后的每个单词转换为大写。最后在 mapping 中设置 content 字段使用我们自定义的分析器 my-analyzer。
PUT my-index
{
"settings": {
"analysis": {
"analyzer": {
"my-analyzer": {
"type": "custom", // 自定义分析器
"char_filter": [ // 字符串过滤器
"html_strip" // 去掉 HTML 标签
],
"tokenizer": "whitespace", // 分词器,按照空格切分单词
"filter": [ // 分词过滤器
"uppercase" // 将切分后的每个单词转换为大写
]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "my-analyzer" // 设置字段使用自定义分析器
}
}
}
}
测试自定义分析器。
POST my-index/_analyze
{
"field": "content",
"text": "<h1>Quick fox jumps</h1>"
}
返回结果如下所示,输入的字符串 <h1>Quick fox jumps</h1>
分别经过了 Character filters(去掉 HTML 标签),Tokenizer(根据空格切分),Token filters(大写字母转换),最后将结果 ["QUICK", "FOX", "JUMPS"] 写入倒排索引中。
{
"tokens" : [
{
"token" : "QUICK",
"start_offset" : 4,
"end_offset" : 9,
"type" : "word",
"position" : 0
},
{
"token" : "FOX",
"start_offset" : 10,
"end_offset" : 13,
"type" : "word",
"position" : 1
},
{
"token" : "JUMPS",
"start_offset" : 14,
"end_offset" : 19,
"type" : "word",
"position" : 2
}
]
}