Elasticsearch

What is Elasticsearch?

官网Quick start

官网Elasticsearch Guide

Elasticsearch 技术分析系列

Elasticsearch 快速入门elk7版本教程

(多看官网,需要用到什么就去官网查。另外Elasticsearch 技术分析系列文章适合学习时阅读)

我们选择elasticsearch 6.4.0版本进行学习。

Elasticsearch介绍

Elasticsearch是一个高度可扩展的、开源的、基于 Lucene 的全文搜索和分析引擎。它允许您快速,近实时地存储,搜索和分析大量数据,并支持多租户。 Elasticsearch也使用Java开发并使用 Lucene 作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单。

Elasticsearch是一个高度可扩展的、开源的、基于 Lucene 的全文搜索和分析引擎。它允许您快速,近实时地存储,搜索和分析大量数据,并支持多租户。

Elasticsearch使用Java开发并使用 Lucene 作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单。

不过,Elasticsearch 不仅仅是 Lucene 和全文搜索,我们还能这样去描述它:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 分布式的实时分析搜索引擎
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据

而且,所有的这些功能被集成到一个服务里面,你的应用可以通过简单的RESTful API、各种语言的客户端甚至命令行与之交互。

版本选择

在决定使用 Elasticsearch 的时候首先要考虑的是版本问题,Elasticsearch 目前有三个常用的稳定的主版本:2.x5.x6.x(排除 0.x1.x)。

Elasticsearch 可以在这里查看所有历史版本。你可能会发现没有 3.x 和 4.x,ES 从 2.4.6 直接跳到了 5.0.0。这是为什么?其实是为了ELK(ElasticSearch, logstash, kibana)技术栈的版本统一,免的给用户带来混乱。

我们知道 elasticsearchkibanalogstash 都是 Elastic Stack 成员, 而且很多时候都是配合使用,那么为了协同开发,避免造成版本困惑,所以需要统一版本,至少保持在一个主版本号下。

在 Elasticsearch 是 2.x (2.x 的最后一版 2.4.6 的发布时间是 July 25, 2017) 的情况下,kibana 已经是 4.x(Kibana 4.6.5 的发布时间是 July 25, 2017),那么在 kibana 的下一主版本肯定是 5.x 了,所以 Elasticsearch 直接将自己的主版本发布为 5.0.0 了。统一之后,我们选版本就不会犹豫困惑了,我们选定 elasticsearch 的版本后再选择相同版本的 kibana 就行了,不用担忧版本不兼容的问题。

版本选择可以从以下几个方面考虑:

  • 版本问题

    • 2.x 版本较老,无法体验新功能,且性能不如 5.x
    • 6.x 版本有点新,网上资料相对比较少
  • 数据迁移

    • 2.x 版本数据可以直接迁移到 5.x
    • 5.X 版本的数据可以直接迁移到 6.x; 但是 2.x 版本数据无法直接迁移到 6.x
  • 周边工具

    • 2.x 版本周边工具版本比较混乱;Kibana 等工具的对应版本需要自己查,不好匹配
    • 5.x 之后 Kibana 等工具的主版本号进行了统一
  • Sql 语法支持

    • 2.x5.x6.x 都可以安装 Elasticsearch-sql 插件,使用熟悉的SQL语法查询 Elasticsearch
    • 6.3.0 以后内置支持 SQL 模块,这个 SQL 模块是属于 X-Pack 的一部分

我们选择elasticsearch 6.4.0版本进行学习。

安装Elasticsearch

Docker安装ElasticSearch和Kibana

使用docker安装Elasticsearch会方便很多。

创建网关

因为ES和Kibana建议以一个独立的服务存在,所以这里我们给ES和Kibana单独创建一个network:

1
docker network create es-net

可以通过docker network ls命令查看所以的network。

安装elasticsearch

首先docker pull拉取镜像:

1
docker pull elasticsearch:6.4.0

接下来docker run启动容器(直接这样可能会报错,看下面的新方法!):

1
2
3
4
5
6
7
8
docker run -d --name es6 \
--network es-net \
-p 9200:9200 -p 9300:9300 \
-v /home/root/dockerVolumes/es/es6.4.0/data:/usr/share/elasticsearch/data \
-v /home/root/dockerVolumes/es/es6.4.0/config:/usr/share/elasticsearch/config \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
elasticsearch:6.4.0

参数说明:

  • -d:后台运行容器,并返回容器ID
  • --name es6:为该容器指定名称为es6
  • --network es-net:指定network(注意指定的network要与安装Kibana时指定的network一样)
  • -p 9200:9200 -p 9300:9300:端口映射(主机(宿主)端口:容器端口)。即宿主机开放出9200端口,指向容器内的9200端口;宿主机开放出9300端口,指向容器内的9300端口(9200:Web端口,与RESTful客户端通信;9300:为TCP端口,与Java做通信)
  • -v /home/root/dockerVolumes/es/es6.4.0/data:/usr/share/elasticsearch/data:绑定数据卷(/宿主机绝对路径目录:/容器内目录),将容器内的/usr/share/elasticsearch/data挂载到宿主机的/home/root/dockerVolumes/es/es6.4.0/data文件夹下(ES在Docker中的所有数据都存储在容器中的 /usr/share/elasticsearch/data 中)
  • -v /home/root/dockerVolumes/es/es6.4.0/config:/usr/share/elasticsearch/config:绑定数据卷(/宿主机绝对路径目录:/容器内目录),将容器内的/usr/share/elasticsearch/config挂载到宿主机的/home/root/dockerVolumes/es/es6.4.0/config文件夹下(ES在Docker中的所有配置都存储在容器中的 /usr/share/elasticsearch/config中)
  • -e "discovery.type=single-node":设置环境变量,表示ES以单节点模式启动(虽然名为单节点,但是还是以集群模式启动。所以不加这个参数,默认集群启动,也没有问题)
  • -e ES_JAVA_OPTS="-Xms512m -Xmx512m":设置环境变量,设置JVM参数(如果启动不了,可以添加此参数加大内存设置)

这样启动会发现启动失败,查询日志发现:

docker启动elasticsearch异常java.nio.file.NoSuchFileException

解决办法:先将挂载数据卷配置注释,启动elasticsearch后将elasticsearch容器中的config/文件夹下的文件拷出到宿主机,重新启动即可。

即新的运行方法为:

先不加-v参数进行运行容器:

1
2
3
4
5
6
docker run -d --name es6 \
--network es-net \
-p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
elasticsearch:6.4.0

然后将容器内的数据拷贝到宿主机:

1
docker cp es6:/usr/share/elasticsearch/config /home/root/dockerVolumes/es/es6.4.0/

此时可以发现宿主机下的config文件夹下已经有文件了:

此时删除容器:

1
2
docker stop es6
docker rm es6

这里记得先修改一下宿主机挂载目录的权限,不然会报错:

1
2
chmod 777 /home/root/dockerVolumes/es/es6.4.0/
chmod 777 /home/root/dockerVolumes/es/es6.4.0/data

应该只需要执行chmod 777 /home/root/dockerVolumes/es/es6.4.0/data就行的。如果不修改宿主机目录权限会报错:

然后再完整的运行一次容器:

1
2
3
4
5
6
7
8
docker run -d --name es6 \
--network es-net \
-p 9200:9200 -p 9300:9300 \
-v /home/root/dockerVolumes/es/es6.4.0/data:/usr/share/elasticsearch/data \
-v /home/root/dockerVolumes/es/es6.4.0/config:/usr/share/elasticsearch/config \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
elasticsearch:6.4.0

进入elasticsearch容器内:docker exec -it es6 /bin/bash

可以发现目录结构如下:

目录 配置文件 描述
bin 脚本文件,包括启动 Elasticsearch、安装插件,运行统计数据等。
config elasticsearch.yml 集群配置文件
JDK Java 运行环境
data path.data 数据文件
lib Java 类库
logs path.logs 日志文件
modules 包含所有 ES 模块
plugins 包含所有已安装插件

安装IK分词器

插件安装可以用elasticsearch-plugin install url命令。比如安装:elasticsearch-analysis-ik(分词器),Ik分词器版本要和ES和Kibana版本保持一致。

进入容器内:docker exec -it es6 /bin/bash

plugins安装步骤:

  • 进入plugins文件夹:cd /usr/share/elasticsearch/plugins/
  • 安装插件,elasticsearch-analysis-ik版本与elasticsearch保持一致,即6.4.0elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.4.0/elasticsearch-analysis-ik-6.4.0.zip
  • 退出容器:exit
  • 重启docker容器:docker restart es6

分词器使用验证

  • ik_smart:智能分词,最少切分,宁缺毋滥,保证查准率
  • ik_max_word:最大化分词法,最细粒度划分,尽量多的有意义的分词,保证查全率。ik_max_word分词包含ik_smart

Postman post请求分词测试:http://ip:9200/_analyze

安装kibana

kibanaelasticsearch的版本必须保持严格一致。

首先docker pull拉取镜像:

1
docker pull kibana:6.4.0

接下来docker run启动容器:

1
2
3
4
5
docker run -d --name kibana6 \
--net es-net \
-p 5601:5601 \
-e ELASTICSEARCH_URL=http://es6:9200 \
kibana:6.4.0

参数说明:

  • -d:后台运行容器,并返回容器ID
  • --name kibana6:为该容器指定名称为kibana6
  • --network es-net:指定network(注意指定的network要与安装elasticsearch时指定的network一样)
  • -e ELASTICSEARCH_URL=http://es6:9200:设置环境变量,让kibana能连接到elasticsearch(es6为上面我们创建elasticsearch时取的容器名,9200为创建elasticsearch容器时宿主机对应的容器内9200端口)

进入kibana容器内:docker exec -it kibana6 bash

访问http://ip:5601就可以访问到kibana的界面了。

安装elasticsearch-head

elasticsearch-head是用于Elasticsearch监控的插件。

首先docker pull拉取镜像:

1
docker pull mobz/elasticsearch-head:5

接下来docker run启动容器:

1
2
3
docker run -d --name es-head5 \
-p 9100:9100 \
mobz/elasticsearch-head:5

参数说明:

  • -d:后台运行容器,并返回容器ID
  • --name es-head5:为该容器指定名称为es-head5
  • -p 9100:9100:端口映射(主机(宿主)端口:容器端口),即宿主机开放出9100端口,指向容器内的9100端口

进入elasticsearch-head容器内:docker exec -it es-head5 bash

浏览器访问:http://ip:9100/。如下:

当输入 http://ip:9200/ 点击连接时,会发现无法连接。是因为前后端分离开发,存在跨域问题,需要在服务端做 CORS 的配置。

由于我们进行过数据卷挂载,不需要进入elasticsearch 容器内部,直接在宿主机的/home/root/dockerVolumes/es/es6.4.0/config下修改elasticsearch.yml 配置。

输入vim elasticsearch.yml,添加如下两条配置,注意冒号后面有空格,保存并退出。

1
2
http.cors.enabled: true 
http.cors.allow-origin: "*"

重启容器:docker restart es6

此时输入 http://ip:9200/ 点击连接就可以连接到了:

安装docker安装logstash

logstashelasticsearch的版本必须保持严格一致。

首先docker pull拉取镜像:

1
docker pull logstash:6.4.0

接下来先docker run启动容器(这里先启动容器是为了后面方便docker cp):

1
docker run -d --name=logstash6 logstash:6.4.0

拷贝数据,授予权限:

1
2
docker cp logstash6:/usr/share/logstash /home/root/dockerVolumes/logstash/logstash6.4.0
chmod 777 -R /home/root/dockerVolumes/logstash/logstash6.4.0

接下来修改配置文件中的elasticsearch地址。

输入vim logstash.yml,完整内容如下:

1
2
3
4
5
6
7
http.host: "0.0.0.0"
#根据实际修改es的ip:port
xpack.monitoring.elasticsearch.hosts: [ "http://192.168.31.196:9200" ]
# 主管道的Logstash配置路径,如果指定目录或通配符,配置文件将按字母顺序从目录中读取
path.config: /usr/share/logstash/config/conf.d/*.conf
#Logstash将其日志写到的目录
path.logs: /usr/share/logstash/logs

/home/root/dockerVolumes/logstash/logstash6.4.0/config新建logstash.conf文件,vim logstash.conf,内容如下:

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
input {
file {
#标签
type => "systemlog-localhost"
#采集点(这里一定要注意,由于我是docker启动的logstash,这里是容器内的文件,如果要生成外面日志索引,那么文件的路径一定要挂载正确)
path => "/var/log/messages"
#开始收集点
start_position => "beginning"
#扫描间隔时间,默认是1s,建议5s
stat_interval => "5"
}
}

output {
elasticsearch {
#集群的话,直接添加多个url
hosts => ["172.17.0.3:9200"]
#es的用户名和密码
#user =>"elastic"
#password =>"elastic"
#建立的索引以日期区分
index => "logstash-system-localhost-%{+YYYY.MM.dd}"
}
#在控制台输出logstash的日志
#stdout { codec=> rubydebug }
}

接下来删除旧的logstash然后再docker run一个新的:

1
2
docker stop logstash6
docker rm logstash6
1
2
3
4
5
6
7
docker run -d --name=logstash6 \
-p 5044:5044 \
--privileged=true \
--restart=always \
-v /home/root/dockerVolumes/logstash/logstash6.4.0:/usr/share/logstash \
-v /home/root/dockerVolumes/logstash/logstash6.4.0/log/messages:/var/log/messages \
logstash:6.4.0

其他的参数说明就不细说了,一眼能看懂。具体说一下:

  • --privileged=true:解决docker挂载主机目录时docker容器访问出现cannot open directory:Permission denied
  • --restart=always:只要docker是启动的,容器就会跟着启动

docker compose安装elk

上面用docker一个一个的安装还是比较麻烦,而且很容易出错(其实上面安装时logstash6.4.0是失败的,虽然可以成功跑起来,但是并没有成功在es上创建索引)。

这里还是推荐使用docker compose一次性安装elk。以安装elk7为例。

ElasticSearch基本概念

安装成功后,一切准备就绪,我们正式操作之前,先来了解一些 ES 的核心基本概念,从一开始就理解这些概念将极大地帮助简化学习过程。

Elasticsearch是一个近乎实时(NRT)的搜索平台。这意味着从索引文档到可搜索文档的时间有一点延迟(通常是一秒)。通常有集群,节点,分片,副本等概念。

集群(Cluster)

集群(cluster)是一组具有相同cluster.name的节点集合,他们协同工作,共享数据并提供故障转移和扩展功能,当然一个节点也可以组成一个集群。

集群由唯一名称标识,默认情况下为“elasticsearch”。此名称很重要,因为如果节点设置为按名称加入集群的话,则该节点只能是集群的一部分。

确保不同的环境中使用不同的集群名称,否则最终会导致节点加入错误的集群。

【集群健康状态】

集群状态通过 绿 来标识

  • 绿色 - 一切都很好(集群功能齐全)。
  • 黄色 - 所有数据均可用,但尚未分配一些副本(集群功能齐全)。
  • 红色 - 某些数据由于某种原因不可用(集群部分功能)。

注意:当群集为红色时,它将继续提供来自可用分片的搜索请求,但您可能需要尽快修复它,因为存在未分配的分片。

要检查群集运行状况,我们可以在 Kibana 控制台中运行以下命令GET /_cluster/health,得到如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"cluster_name": "docker-cluster",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 1,
"active_shards": 1,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0,
"delayed_unassigned_shards": 0,
"number_of_pending_tasks": 0,
"number_of_in_flight_fetch": 0,
"task_max_waiting_in_queue_millis": 0,
"active_shards_percent_as_number": 100
}

节点(Node)

节点,一个运行的 ES 实例就是一个节点,节点存储数据并参与集群的索引和搜索功能。

就像集群一样,节点由名称标识,默认情况下,该名称是在启动时分配给节点的随机通用唯一标识符(UUID)。如果不需要默认值,可以定义所需的任何节点名称。此名称对于管理目的非常重要,您可以在其中识别网络中哪些服务器与 Elasticsearch 集群中的哪些节点相对应。

可以将节点配置为按集群名称加入特定集群。默认情况下,每个节点都设置为加入一个名为 cluster 的 elasticsearch 集群,这意味着如果您在网络上启动了许多节点并且假设它们可以相互发现,它们将自动形成并加入一个名为 elasticsearch 的集群。

索引(Index)

索引是具有某些类似特征的文档集合。例如,您可以拥有店铺数据的索引,商品的一个索引以及订单数据的一个索引。

索引由名称标识(必须全部小写),此名称用于在对其中的文档执行索引,搜索,更新和删除操作时引用索引。

类型(Type)(已弃用)

类型,曾经是索引的逻辑类别/分区,允许您在同一索引中存储不同类型的文档,例如,一种类型用于用户,另一种类型用于博客帖子。

在 6.0.0 中弃用,以后将不再可能在索引中创建多个类型,并且将在更高版本中删除类型的整个概念

文档(Document)

文档是可以建立索引的基本信息单元。例如,您可以为单个客户提供文档,为单个产品提供一个文档,为单个订单提供一个文档。该文档以JSON(JavaScript Object Notation)表示,JSON是一种普遍存在的互联网数据交换格式。

在索引/类型中,您可以根据需要存储任意数量的文档。请注意,尽管文档实际上驻留在索引中,但实际上必须将文档编入索引/分配给索引中的类型。

分片(Shards)

索引可能存储大量可能超过单个节点的硬件限制的数据。例如,占用1TB磁盘空间的十亿个文档的单个索引可能不适合单个节点的磁盘,或者可能太慢而无法单独从单个节点提供搜索请求。

为了解决这个问题,Elasticsearch 提供了将索引细分为多个称为分片的功能。创建索引时,只需定义所需的分片数即可。每个分片本身都是一个功能齐全且独立的“索引”,可以托管在集群中的任何节点上。

设置分片的目的及原因主要是:

  • 它允许您水平拆分/缩放内容量
  • 它允许您跨分片(可能在多个节点上)分布和并行化操作,从而提高性能/吞吐量

分片的分布方式以及如何将其文档聚合回搜索请求的机制完全由 Elasticsearch 管理,对用户而言是透明的。

在可能随时发生故障的网络/云环境中,分片非常有用,建议使用故障转移机制,以防分片/节点以某种方式脱机或因任何原因消失。为此,Elasticsearch 允许您将索引的分片的一个或多个副本制作成所谓的副本分片或简称副本。

副本(Replicasedit)

副本,是对分片的复制。目的是为了当分片/节点发生故障时提供高可用性,它允许您扩展搜索量/吞吐量,因为可以在所有副本上并行执行搜索。

总而言之,每个索引可以拆分为多个分片。索引也可以复制为零次(表示没有副本)或更多次。复制之后,每个索引将具有主分片(从原始分片复制而来的)和复制分片(主分片的副本)。

可以在创建索引时为每个索引定义分片和副本的数量。创建索引后,您也可以随时动态更改副本数。您可以使用_shrink_splitAPI 更改现有索引的分片数,但这不是一项轻松的任务,所以预先计划正确数量的分片是最佳方法。

默认情况下,Elasticsearch 中的每个索引都分配了5个主分片和1个副本,这意味着如果集群中至少有两个节点,则索引将包含5个主分片和另外5个副本分片(1个完整副本),总计为每个索引10个分片。

elasticsearch和关系型数据库的概念对比

  • 关系型数据库 -> Databases(库) -> Tables(表) -> Rows(行) -> Columns(列)。
  • Elasticsearch -> Indeces(索引) -> Types(类型) -> Documents(文档) -> Fields(属性)。
Elasticsearch 关系型数据库
Databases(库) Indeces(索引)
Tables(表) Types(类型)
Rows(行) Documents(文档)
Columns(列) Fields(属性)

Elasticsearch集群可以包含多个索引(indices)(数据库),每一个索引可以包含多个类型 (Types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。

虽然这么类比,但是毕竟是两个差异化的产品,而且上面也说过在以后的版本中类型 (Types) 可能会被删除,所以一般我们创建索引都是一个种类对应一个索引。生鲜就创建商品的索引,生活用品就创建生活用品的索引,而不会说创建一个商品的索引,里面既包含生鲜的类型,又包含生活用品的类型。

而且你可能已经注意到索引(index)这个词在Elasticsearch中有着不同的含义,所以有必要在此做一下区分:

「索引」含义的区分

  • 索引(名词) 如上文所述,一个索引(index)就像是传统关系数据库中的数据库,它是相关文档存储的地方,index的复数是 indices 或 indexes。
  • 索引(动词) 「索引一个文档」表示把一个文档存储到索引(名词)里,以便它可以被检索或者查询。这很像SQL中的INSERT关键字,差别是,如果文档已经存在,新的文档将覆盖旧的文档。
  • 倒排索引 传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。Elasticsearch和Lucene使用一种叫做**倒排索引(inverted index)**的数据结构来达到相同目的。

小结

我们假设有一个集群由三个节点组成(Node1 , Node2 , Node3)。 它有两个主分片(P0 , P1),每个主分片有两个副本分片(R0 , R1)。相同分片的副本不会放在同一节点,所以我们的集群看起来如下图所示 “有三个节点和一个索引的集群”。

类似于关系型数据库:数据库集群,假如有个用户表,我担心数据量过大,我新建了多个用户表(即 Shard),将用户信息数据切分成多份,然后根据某种规则分到这些用户表中,我又担心某个表会出现异常造成数据丢失,我又将每个表分别备份了一次(即 Replica )。

副本是乘法,越多越浪费,但也越保险。分片是除法,分片越多,单分片数据就越少也越分散。

ElasticSearch交互

目前与 elasticsearch 交互主要有两种方式:Client APIRESTful API

  • Client API方式:Elasticsearch 为Go,Java等等语言提供了官方的客户端和插件,所有这些都可以在 Elasticsearch Clients 中找到。(Elasticsearch Go Client

  • RESTful API with JSON over HTTP:所有其他语言可以使用 RESTful API 通过端口 9200 和 Elasticsearch 进行通信,你可以用web 客户端访问 Elasticsearch 。事实上,正如你所看到的,你甚至可以使用 curl 命令来和 Elasticsearch 交互。(Elasticsearch API约定

    一个 Elasticsearch 请求和任何 HTTP 请求一样由若干相同的部件组成:

    1
    curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'

    < >标记的部件:

ElasticSearch数据格式

在应用程序中对象很少只是一个简单的键和值的列表。通常,它们拥有更复杂的数据结构,可能包括日期、地理信息、其他对象或者数组等。

也许有一天你想把这些对象存储在数据库中。使用关系型数据库的行和列存储,这相当于是把一个表现力丰富的对象挤压到一个非常大的电子表格中:你必须将这个对象扁平化来适应表结构,通常一个字段对应一列,而且又不得不在每次查询时重新构造对象。

Elasticsearch 是面向文档的,意味着它存储整个对象或文档。Elasticsearch不仅存储文档,而且每个文档的内容可以被检索。在 Elasticsearch 中,是对文档进行索引、检索、排序和过滤而不是对行列数据。这是一种完全不同的思考数据的方式,也是Elasticsearch能支持复杂全文检索的原因。

Elasticsearch 使用JSON作为文档的序列化格式。JSON序列化被大多数编程语言所支持,并且已经成为 NoSQL领域的标准格式。 它简单、简洁、易于阅读。几乎所有的语言都有相应的模块可以将任意的数据结构或对象转化成 JSON 格式,只是细节各不相同。

JSON数据示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
}

操作ElasticSearch:索引的应用

REST APIs

一切准备就绪之后我们开始使用起来,体验 ElasticSearch 的世界。

首先,我们来查看我们的所有索引信息:

1
2
3
4
5
6
GET _search
{
"query": {
"match_all": {}
}
}

返回结果如下:

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
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": ".kibana",
"_type": "doc",
"_id": "config:6.4.0",
"_score": 1,
"_source": {
"type": "config",
"updated_at": "2022-06-17T11:08:55.923Z",
"config": {
"buildNum": 17929,
"telemetry:optIn": true
}
}
}
]
}
}

可以发现,当前只有一个索引,是.kibana,当然这不是我们自己的,这是kibana的。

kibanaDev Tools这里找到Console

创建第一个简单索引

我们创建一个 NBA 球队的索引,开始我们的学习之路,注意索引名称必须是小写

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
PUT nba
{
"settings":{
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings":{
"nba":{
"properties":{
"name_cn":{
"type":"text"
},
"name_en":{
"type":"text"
},
"gymnasium":{
"type":"text"
},
"topStar":{
"type":"text"
},
"championship":{
"type":"integer"
},
"date":{
"type":"date",
"format":"yyyy-MM-dd HH:mm:ss|| yyy-MM-dd||epoch_millis"
}
}
}
}
}

字段说明:

字段名称 字段说明
nba 索引
number_of_shards 分片数
number_of_replicas 副本数
name_cn 球队中文名
name_en 球队英文名
gymnasium 球馆名称
championship 总冠军次数
topStar 当家球星
date 加入NBA年份

如果格式书写正确,我们会得到如下返回信息,表示创建成功:

1
2
3
4
5
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "nba"
}

新增索引数据

索引创建完成之后,我们往索引中加入球队数据,1,2,3 是我们指定的ID,如果不写ES会提供一个默认ID。

其实我们可以不创建上面的索引 mapping 直接推送数据,但是这样ES会根据数据信息自动为我们设定字段类型,这会造成索引信息不准确的风险

依次运行以下几个PUT命令:

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
PUT /nba/nba/1
{
"name_en":"San Antonio Spurs SAS",
"name_cn":"圣安东尼安马刺",
"gymnasium":"AT&T中心球馆",
"championship": 5,
"topStar":"蒂姆·邓肯",
"date":"1995-04-12"
}

PUT /nba/nba/2
{
"name_en":"Los Angeles Lakers",
"name_cn":"洛杉矶湖人",
"gymnasium":"斯台普斯中心球馆",
"championship": 16,
"topStar":"科比·布莱恩特",
"date":"1947-05-12"
}

PUT /nba/nba/3
{
"name_en":"Golden State Warriors",
"name_cn":"金州勇士队",
"gymnasium":"甲骨文球馆",
"championship": 6,
"topStar":"斯蒂芬·库里",
"date":"1949-06-13"
}

PUT /nba/nba/4
{
"name_en":"Miami Heat",
"name_cn":"迈阿密热火队",
"gymnasium":"美国航空球场",
"championship": 3,
"topStar":"勒布朗·詹姆斯",
"date":"1988-06-13"
}

PUT /nba/nba/5
{
"name_en":"Cleveland Cavaliers",
"name_cn":"克利夫兰骑士队",
"gymnasium":"速贷球馆",
"championship": 1,
"topStar":"勒布朗·詹姆斯",
"date":"1970-06-13"
}

以最后一个PUT命令为例,运行成功后会返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "nba",
"_type": "nba",
"_id": "5",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}

索引创建成功

查询索引数据

上面新增完数据之后,这时候我们再执行开始的 MATCH_ALL

1
2
3
4
5
6
GET _search
{
"query": {
"match_all": {}
}
}

就会发现我们自己的索引信息也在查询的结果里面了,只是查询的结果是全部信息,其中包括索引、分片和副本的信息,内容比较多。我们可单独查询自己需要的索引信息。

Elasticsearch 提供丰富且灵活的查询语言叫做 DSL 查询 (Query DSL) ,它允许你构建更加复杂、强大的搜索。(Query DSL

匹配查询 match,match_all

1.查询全部球队的信息:

1
2
3
4
5
6
GET /nba/nba/_search
{
"query": {
"match_all": {}
}
}

得到的查询结果如下:

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
84
85
86
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 3,
"successful": 3,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 5,
"max_score": 1,
"hits": [
{
"_index": "nba",
"_type": "nba",
"_id": "2",
"_score": 1,
"_source": {
"name_en": "Los Angeles Lakers",
"name_cn": "洛杉矶湖人",
"gymnasium": "斯台普斯中心球馆",
"championship": 16,
"topStar": "科比·布莱恩特",
"date": "1947-05-12"
}
},
{
"_index": "nba",
"_type": "nba",
"_id": "4",
"_score": 1,
"_source": {
"name_en": "Miami Heat",
"name_cn": "迈阿密热火队",
"gymnasium": "美国航空球场",
"championship": 3,
"topStar": "勒布朗·詹姆斯",
"date": "1988-06-13"
}
},
{
"_index": "nba",
"_type": "nba",
"_id": "5",
"_score": 1,
"_source": {
"name_en": "Cleveland Cavaliers",
"name_cn": "克利夫兰骑士队",
"gymnasium": "速贷球馆",
"championship": 1,
"topStar": "勒布朗·詹姆斯",
"date": "1970-06-13"
}
},
{
"_index": "nba",
"_type": "nba",
"_id": "1",
"_score": 1,
"_source": {
"name_en": "San Antonio Spurs SAS",
"name_cn": "圣安东尼安马刺",
"gymnasium": "AT&T中心球馆",
"championship": 5,
"topStar": "蒂姆·邓肯",
"date": "1995-04-12"
}
},
{
"_index": "nba",
"_type": "nba",
"_id": "3",
"_score": 1,
"_source": {
"name_en": "Golden State Warriors",
"name_cn": "金州勇士队",
"gymnasium": "甲骨文球馆",
"championship": 6,
"topStar": "斯蒂芬·库里",
"date": "1949-06-13"
}
}
]
}
}

响应的数据结果分为两部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
----------------first part--------------------
"took": 0,
"timed_out": false,
"_shards": {
"total": 3,
"successful": 3,
"skipped": 0,
"failed": 0
},
---------------second part---------------------
"hits": {
"total": 0,
"max_score": null,
"hits": []
}
}

第一部分为分片副本信息,第二部分 hits 包装的为查询的数据集。

**注意:**响应内容不仅会告诉我们哪些文档被匹配到,而且这些文档内容完整的被包含在其中。也就是说我们在给用户展示搜索结果时需要用到的所有信息都有了。

2.查询英文名称为:“Golden State Warriors” 的球队信息:

1
2
3
4
5
6
7
8
GET /nba/nba/_search
{
"query": {
"match": {
"name_en": "Golden State Warriors"
}
}
}

查询结果为:

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
{
"took": 27,
"timed_out": false,
"_shards": {
"total": 3,
"successful": 3,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 2.2085102,
"hits": [
{
"_index": "nba",
"_type": "nba",
"_id": "3",
"_score": 2.2085102,
"_source": {
"name_en": "Golden State Warriors",
"name_cn": "金州勇士队",
"gymnasium": "甲骨文球馆",
"championship": 6,
"topStar": "斯蒂芬·库里",
"date": "1949-06-13"
}
}
]
}
}

过滤查询 Filter

我们让搜索变的复杂一些。我们想要找到当家球星是勒布朗·詹姆斯,但是我们只想得到总冠军大于1次的球队。我们的语句将做一些改变用来添加过滤器(filter),它允许我们有效的执行一个结构化搜索:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /nba/nba/_search
{
"query": {
"bool": {
"filter": {
"range": {
"championship": {
"gt": 1
}
}
},
"must": {
"match": {
"topStar": "勒布朗·詹姆斯"
}
}
}
}
}

返回结果如下:

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
{
"took": 82,
"timed_out": false,
"_shards": {
"total": 3,
"successful": 3,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 4,
"max_score": 2.4835496,
"hits": [
{
"_index": "nba",
"_type": "nba",
"_id": "4",
"_score": 2.4835496,
"_source": {
"name_en": "Miami Heat",
"name_cn": "迈阿密热火队",
"gymnasium": "美国航空球场",
"championship": 3,
"topStar": "勒布朗·詹姆斯",
"date": "1988-06-13"
}
},
{
"_index": "nba",
"_type": "nba",
"_id": "1",
"_score": 0.7261542,
"_source": {
"name_en": "San Antonio Spurs SAS",
"name_cn": "圣安东尼安马刺",
"gymnasium": "AT&T中心球馆",
"championship": 5,
"topStar": "蒂姆·邓肯",
"date": "1995-04-12"
}
},
{
"_index": "nba",
"_type": "nba",
"_id": "3",
"_score": 0.66301036,
"_source": {
"name_en": "Golden State Warriors",
"name_cn": "金州勇士队",
"gymnasium": "甲骨文球馆",
"championship": 6,
"topStar": "斯蒂芬·库里",
"date": "1949-06-13"
}
},
{
"_index": "nba",
"_type": "nba",
"_id": "2",
"_score": 0.13353139,
"_source": {
"name_en": "Los Angeles Lakers",
"name_cn": "洛杉矶湖人",
"gymnasium": "斯台普斯中心球馆",
"championship": 16,
"topStar": "科比·布莱恩特",
"date": "1947-05-12"
}
}
]
}
}

我们可以发现每次查询,查询结果里面都有一个 _score字段,一般Elasticsearch根据相关评分排序,相关评分是根据文档与语句的匹配度来得出, _score值越高说明匹配度越高。

Elasticsearch进行全文字段搜索且首先返回相关性最大的结果。相关性(relevance)概念在Elasticsearch中非常重要,而这也是它与传统关系型数据库中记录只有匹配和不匹配概念最大的不同。


ElasticSearch索引映射Mapping问题

数据库建表的时候,我们的DDL语句一般都会指定每个字段的存储类型,例如:varchar,int,datetime等等,目的很明确,就是更精确的存储数据,防止数据类型格式混乱。

1
2
3
4
5
6
7
8
9
10
CREATE TABLE `shop_` (
`id_` varchar(36) NOT NULL COMMENT 'id',
`shop_name_` varchar(50) DEFAULT NULL COMMENT '商品名称',
`shop_integral_` int(11) DEFAULT NULL COMMENT '兑换所需积分',
`shop_money_` decimal(10,0) DEFAULT NULL COMMENT '劵面金额',
`start_time_` datetime DEFAULT NULL COMMENT '有效开始时间',
`end_time_` datetime DEFAULT NULL COMMENT '有效结束时间',
`is_delete_` int(1) DEFAULT '1' COMMENT '是否删除-1:有效,0:删除',
PRIMARY KEY (`id_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

在 Elasticsearch中也是这样,创建索引的时候一般也需要指定索引的字段类型,这种方式成为映射(Mapping)。

这在上面我们Add数据时就已经接触到了,如:

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
PUT nba
{
"settings":{
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings":{
"nba":{
"properties":{
"name_cn":{
"type":"text"
},
"name_en":{
"type":"text"
},
"gymnasium":{
"type":"text"
},
"topStar":{
"type":"text"
},
"championship":{
"type":"integer"
},
"date":{
"type":"date",
"format":"yyyy-MM-dd HH:mm:ss|| yyy-MM-dd||epoch_millis"
}
}
}
}
}

所以接下来就看一看ES的字段类型。

字段类型

映射(Mapping)针对的是文档的字段,数据库中有varcharintdatetime等数据类型,那么我们ElasticSearch中又有哪些字段类型,每个字段类型都代表什么意思呢?

ElasticSearch更新频繁,以下内容是针对6.x版本的,对于5.x版本以及之前的版本可能有所不同,未来版本也许也会有所改变(所以一定要针对自己的版本查阅官方文档

Elasticsearch支持文档字段的多种不同数据类型,根据官方文档的分类,可以划分为以下几个类别:
核心数据类型复杂数据类型Geo(地理)数据类型专用数据类型多字段。(Field data types

核心数据类型

  • 字符串类型:主要包括:textkeyword
  • 数字类型:主要包括:long, integer, short, byte, double, float, half_float, scaled_float
  • 日期类型: 主要包括:date ,date_nanos
  • 布尔类型:boolean
  • 二进制类型:binary
  • 范围数据类型:integer_range, float_range, long_range, double_range, date_range

这里我们重点介绍下textkeyword的区别:

  • text:用于索引全文值的字段,例如电子邮件正文或产品说明。这些字段是analyzed,它们通过分词器传递 ,以在被索引之前将字符串转换为单个术语的列表。分析过程允许Elasticsearch搜索单个单词中每个完整的文本字段。文本字段不用于排序,很少用于聚合(尽管 重要的文本聚合 是一个值得注意的例外)
  • keyword:用于索引结构化内容的字段,例如电子邮件地址,主机名,状态代码,邮政编码或标签。它们通常用于过滤,排序,和聚合。keyword字段只能按其确切值进行搜索。如果您需要索引电子邮件正文或产品说明等全文内容,则可能应该使用text字段

官网:For example, you can index strings to both text and keyword fields. However, text field values are analyzed for full-text search while keyword strings are left as-is for filtering and sorting.

有时候一个字段同时拥有全文类型(text)和关键字类型(keyword)是有用的:一个用于全文搜索,另一个用于聚合和排序。这可以通过多字段类型来实现。

复杂数据类型

Geo数据类型

专用数据类型

多字段

有时候单纯的一个字段类型满足不了我们复杂的需求,为了不同的目的,以不同的方式索引同一个字段通常很有用。多字段也是ES的一种数据类型,只不过结合了更多的功能。

例如,对于字符串字段,我们既可以将它映射为text类型用于全文搜索,亦可以将它映射为keyword类型用于排序或聚合,或者,还可以使用标准分词器、英语分词器和其他语言分词器索引文本字段。

大多数数据类型都通过fields参数支持多字段。例如对于城市名称的多字段映射,可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"cityName": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
}
}
}
}
}
}
}

映射

映射是定义一个文档及其包含的字段如何存储和索引的过程。例如,使用映射来定义:

  • 应将哪些字符串字段视为全文字段
  • 哪些字段包含数字,日期或地理位置
  • 是否应将文档中所有字段的值索引到catch-all _all字段中
  • 日期值的格式
  • 自定义规则以控制动态添加字段的映射

其实在 ElasticSearch中可以不需要事先定义映射(Mapping),文档写入ElasticSearch时,会根据文档字段自动识别类型,但是通过这种自动识别的字段不是很精确,对于一些复杂的需要分词的就不适合了

根据是否自动识别映射类型,我们可以将映射分为动态映射静态映射

  • 动态映射:不事先指定映射类型(Mapping),文档写入ElasticSearch时,ES会根据文档字段自动识别类型,这种机制称之为动态映射。
  • 静态映射:人为事先定义好映射,包含文档的各个字段及其类型等,这种方式称之为静态映射,亦可称为显式映射。

动态映射

Elasticsearch最重要的功能之一是它试图摆脱你的方式,让你尽快开始探索你的数据。Elasticsearch试图让你成功安装环境之后就可以直接使用。要索引文档,您不必首先创建索引、定义映射类型和定义字段,其实您只需索引一个文档数据,然后索引、类型和字段将自动生效。

索引一个图书的文档:

1
2
3
4
5
6
PUT /library/book/1
{
"bookId":1,
"bookName":"Java核心技术卷I",
"publishDate":"2014-03-12"
}

返回结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "library",
"_type": "book",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}

我们看下mapping映射信息:GET library/_mapping

得到结果如下:

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
{
"library": {
"mappings": {
"book": {
"properties": {
"bookId": {
"type": "long"
},
"bookName": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"publishDate": {
"type": "date"
}
}
}
}
}
}

重点关注mapping节点的内容。可以看到,我们并没有创建索引映射,Elasticsearch自动根据文档数据为我们映射了字段类型,bookId的映射类型为long;bookName的映射类型为多字段的即为text,同时也为keyword;publishDate的映射类型为date。可以看到ES的动态映射功能还是蛮强大的。

默认情况下,当在文档中找到以前未见过的字段时,Elasticsearch会自动将这个新字段添加到类型映射中。我们可以在文档和object级别禁用这项功能,具体操作方式就是通过将dynamic参数设置为falsestrict设为false是忽略新字段,而设为strict是如果遇到未知字段,就抛出异常

假设启用了动态字段映射功能,则使用一些简单的规则来确定字段应具有的数据类型:

JSON datatype Elasticsearch datatype
null 没有字段添加
true or false boolean
integer long
object object
array 依赖于数组中首个非空值
string 可以是日期字段、double或long字段,也可以是带有关键字子字段的文本字段。

上面这些是可以动态检测到的字段数据类型,而其他的字段必须要显式映射数据类型了。


对于string字符串字段,动态映射的结果会有多种,可能映射为日期date类型,也可能映射为doublelong类型,也可能映射为带有关键字的text类型,具体结果要看配置的检测类型,是日期检测还是数字检测。

日期检测

如果date_detection启用(默认启用),则检查新字符串字段以查看其内容是否与dynamic_date_formats指定的任何日期模式匹配 。如果找到匹配项,那么则添加为具有对应格式的date新字段。(dynamic_date_formats默认值为:[ "strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]

例如:

1
2
3
4
PUT my_index/_doc/1
{
"create_date": "2015/09/02"
}

通过GET my_index/_mapping得到的结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"my_index": {
"mappings": {
"_doc": {
"properties": {
"create_date": {
"type": "date",
"format": "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
}
}
}
}
}
}

动态日期检测可以通过设置date_detectionfalse来禁用:

1
2
3
4
5
6
7
8
9
10
11
12
13
PUT my_index2
{
"mappings": {
"_doc": {
"date_detection": false
}
}
}

PUT my_index2/_doc/1
{
"create": "2022/09/02"
}

禁用之后,通过GET my_index2/_mapping获取映射类型,得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"my_index2": {
"mappings": {
"_doc": {
"date_detection": false,
"properties": {
"create": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}

数字检测

虽然JSON支持本机浮点和整数数据类型,但某些应用程序或语言有时可能将数字呈现为字符串**。通常,正确的解决方案是显式映射这些字段,但可以启用数字检测(默认情况下禁用)**以自动执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUT my_index3
{
"mappings":{
"_doc":{
"numeric_detection":true
}
}
}

PUT my_index3/_doc/1
{
"my_float":"1.0",
"my_integer":"1"
}

通过GET my_index3/_mapping获取映射类型,得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"my_index3": {
"mappings": {
"_doc": {
"numeric_detection": true,
"properties": {
"my_float": {
"type": "float"
},
"my_integer": {
"type": "long"
}
}
}
}
}
}

可以看到my_float字段将添加为float类型,my_integer字段将添加为long类型。

除了上面列出的选项外,还可以进一步自定义动态字段映射规则dynamic_templates,动态模板允许您定义可应用于动态添加字段的自定义映射,具体取决于:

  • Elasticsearch检测到的数据类型match_mapping_type
  • 字段的名称,带match和unmatch或match_pattern
  • 字段的完整虚线路径,带path_match和path_unmatch

更多具体内容可参考官方文档Dynamic templates。这里就不多叙述了。

静态映射(显式映射)

动态映射的自动类型推测功能并不是100%正确的,这就需要静态映射机制。静态映射与关系数据库中创建表语句类型,需要事先指定字段类型。相对于动态映射,静态映射可以添加更加详细字段类型、更精准的配置信息等。

既然可以自定义映射字段类型,那么那些复杂的字段类型和分词器我们都可以根据自己需求添加了,以提供了字段映射使用的各种映射参数的详细说明,这些映射参数对于某些或所有字段数据类型是通用的。

映射参数 说明
analyzer 分析器
normalizer 在 Elasticsearch 中处理字符串类型的数据时,如果我们想把整个字符串作为一个完整的 term 存储,我们通常会将其类型 type 设定为 keyword。但有时这种设定又会给我们带来麻烦,比如同一个数据再写入时由于没有做好清洗,导致大小写不一致,比如 apple、Apple两个实际都是 apple,但当我们去搜索 apple时却无法返回 Apple的文档。要解决这个问题,就需要 Normalizer出场了。
boost 单个字段可以自动提升以计数更多的相关性得分
coerce 强制尝试清除脏值以适合字段的数据类型。数据并不总是干净的,根据它的生成方式,数字可能会在JSON正文中呈现为真正的JSON数字,例如5,但它也可能呈现为字符串,例如"5"。或者,应该是整数的数字可以替代地呈现为浮点,例如5.0,或甚至 “5.0”。
copy_to copy_to参数允许您创建自定义 _all字段,可以将多个字段的值复制到组字段中,然后可以将其作为单个字段进行查询。
doc_values
dynamic 设置动态映射
enabled enabled设置只能应用于映射类型和 object字段,导致Elasticsearch完全跳过对字段内容的解析
fielddata
eager_global_ordinals
format 格式化日期
ignore_above
ignore_malformed
index_options
index
fields
norms
null_value 当字段设置为null,(或空数组或null值数组)时,它被视为该字段没有值。不能被索引或搜索
position_increment_gap
properties
search_analyzer
similarity
store
term_vector