目录
- (一)初识Elasticsearch - 基础概念学习
- (二)安装Elasticsearch单机服务 以及常见问题的解决
- (三)初探Elasticsearch的主要配置文件(以6.6.0版本为例)
- (四)安装Kibana 6.6.0 以及常见问题的解决
- (五)ES通过Kibana查看Elasticsearch集群的健康状态、节点和索引个数
- (六)通过Kibana插件操作ES中的索引文档 (CRUD操作)
- (七) Elasticsearch查询索引文档的6种方法
- (八)增删改查、打开、关闭Elasticsearch的索引
- (九)Elasticsearch如何定制分词器 (自定义分词策略)
- (十)如何使用Elasticsearch的索引模板(index template)
- (十一)ES配置Elasticsearch的映射 (mapping) 目录
- (十二)如何配置使用Elasticsearch的动态映射 (dynamic mapping)
- (十三)ES的数据类型 (text、keyword、date、object、geo等)
- (十四)Elasticsearch写入索引数据的过程 以及优化写入过程 (底层原理)
- (十五)查询Elasticsearch中的数据 (基于DSL的查询, 包括validate、match、bool)
(一)初识Elasticsearch - 基础概念学习
1 Elasticsearch概述
-
1.1 Elasticsearch是什么
Elasticsearch(ES)是一个基于Apache的开源索引库Lucene而构建的 开源、分布式、具有RESTful接口的全文搜索引擎, 还是一个 分布式文档数据库,可以轻松扩展数以百计的服务器(水平扩展), 用于存储和处理数据. 它可以在很短的时间内存储、搜索和分析海量数据, 通常被作为复杂搜索场景下的核心引擎,由于Lucene提供的API操作起来非常繁琐, 需要编写大量的代码, Elasticsearch对Lucene进行了封装与优化, 并提供了REST风格的操作接口, 开箱即用, 很大程度上方便了开发人员的使用. -
1.2 Elasticsearch的优点
(1) 横向可扩展性: 作为大型分布式集群, 很容易就能扩展新的服务器到ES集群中; 也可运行在单机上作为轻量级搜索引擎使用.
(2) 更丰富的功能: 与传统关系型数据库相比, ES提供了全文检索、同义词处理、相关度排名、复杂数据分析、海量数据的近实时处理等功能.
(3) 分片机制提供更好地分布性: 同一个索引被分为多个分片(Shard), 利用分而治之的思想提升处理效率.
(4) 高可用: 提供副本(Replica)机制, 一个分片可以设置多个副本, 即使在某些服务器宕机后, 集群仍能正常工作.
(5) 开箱即用: 提供简单易用的API, 服务的搭建、部署和使用都很容易操作. -
1.3 Elasticsearch的相关产品
(1) Beats: 是一个代理, 将不同类型的数据发送到Elasticsearch中.
(2) Shield: 提供基于角色的访问控制与审计, 加密通信、认证保护整个ES的数据, 为ES带来企业级的安全性 — 收费产品.
(3) Watcher: 是ES的警报和通知工具, 检测ES的状态, 在异常发生时进行提醒 — 收费产品.
(4) Marvel: 是ES的管理和监控工具, 检测ES集群的索引和节点的活动 — 收费产品. -
1.4 Elasticsearch的使用场景
(1) 维基百科(类似百度百科): 全文检索, 高亮, 搜索推荐;
(2) The Guardian(新闻网站): 用户行为日志(点击, 浏览, 收藏, 评论) + 社交网络数据(对某某新闻的相关看法), 数据分析(将公众对文章的反馈提交至文章作者);
(3) Stack Overflow(IT技术论坛): 全文检索, 搜索相关问题和答案;
(4) GitHub(开源代码管理), 搜索管理其托管的上千亿行代码;
(5) 日志数据分析: ELK技术栈(Elasticsearch + Logstash + Kibana)对日志数据进行采集&分析;
(6) 商品价格监控网站: 用户设定某商品的价格阈值, 当价格低于该阈值时, 向用户推送降价消息;
(7) BI系统(Business Intelligence, 商业智能): 分析某区域最近3年的用户消费额的趋势、用户群体的组成结构等;
(8) 其他应用: 电商、招聘、门户等网站的内部搜索服务, IT系统(OA, CRM, ERP等)的内部搜索服务、数据分析(ES的又一热门使用场景).
2 Elasticsearch的功能概述
-
2.1 分布式的搜索引擎和数据分析引擎
(1) 搜索: 谷歌, 百度, 各大网站的站内搜索(如淘宝网的商品搜索), IT系统的检索(如OA内部的信息查询);
(2) 数据分析: 电商网站中, 对形如最近30天IT书籍销量排名前10的商家有哪些; 新闻网站中: 最近7天访问量排名Top 10的新闻是哪些…
—— 总结: Elasticsearch适用于 在较大用户量、较高访问量的分布式系统中, 对数据进行搜索与分析. -
2.2 全文检索 结构化检索 数据分析
(1) 全文检索: 搜索商品名称包含"编程思想"的商品: select * from products where product_name like “%编程思想%”;
(2) 结构化检索: 搜索商品分类为"计算机科学"的所有商品: select * from products where category_name=‘计算机科学’;
(3) 数据分析: 分析每一种商品分类下有多少件商品: select category_name, count(*) from products group by category_name;
(4) 其他个性化搜索需求: 部分匹配、自动完成(输入联想)、搜索纠错、搜索推荐… -
2.3 海量数据的近实时处理
(1) 分布式: Elasticsearch可将海量数据自动分发到多台服务器上, 进行存储和检索;
(2) 海量数据的处理: 分布式系统构建完成后, 就可通过大规模服务器集群去存储和检索数据 —— 服务器有了处理海量数据的能力;
(3) 基于Elasticsearch的搜索和分析服务可达到秒级响应.
关于近实时:
非近实时: 检索x个数据要花费很长时间(这就不是近实时, 而是离线批处理, batch-processing).
实时: 数据的处理与响应都是立即呈现的, 几乎没有间隔, 这在大数据应用场景下是很难达到的要求.
近实时(near real-time, NRT): 对海量数据进行搜索和分析的响应耗时控制在秒级以内, 方可称为近实时.
3 Elasticsearch的架构
结合Elasticsearch架构图进行相关概念的介绍:
-
3.1 gateway - 门户、网关
ES索引的持久化存储方式, 也就是各类文件系统. 默认是先把索引存放到内存中, 当内存满了时再持久化到硬盘.
ES集群重新启动时就会从gateway中读取索引数据.
ES支持多种类型的gateway: 本地文件系统(默认), 分布式文件系统, Hadoop的HDFS, 以及Amazon的S3云存储服务等. -
3.2 Lucene - 分布式Lucene目录
Gateway的上层是一个分布式的Lucene框架, Lucene之上是ES的模块, 包括:索引模块、搜索模块、映射解析模块等. -
3.2 Discovery - 发现服务
Discovery是ES的节点发现模块, 要组成集群, 不同的节点之间就需要进行通信.
ES集群内部需要选举master节点, 这些工作都是由Discovery模块完成的.
ES支持多种发现机制, 如Zen(默认)、EC2、Gce、Azure等.
ES是一个基于p2p的系统: 先通过广播寻找存在的节点, 再通过多播协议进行节点之间的通信, 同时也支持点对点的交互.
5.x版本关闭了广播, 要开启就需要开发人员自定义. -
3.3 Scripting - 脚本
ES支持在查询语句中插入JavaScript、Python等脚本 —— 由Scripting模块负责解析这些脚本.
使用脚本语句时查询性能可能会稍有降低. -
3.4 3rd Plugins - 三方插件
ES对三方插件的支持非常友好, 因此其开源生态的构建也越发活跃、成熟. -
3.5 Transport - 通信模块
ES内部节点或集群与客户端的交互方式, 节点间通信端口默认为: 9300 - 9400.
ES默认使用TCP协议进行交互, 同时也支持HTTP协议(JSON格式)、Thrift、Servlet、Memcached、ZeroMQ等的传输协议(通过插件方式集成). -
3.6 JMX - Java管理框架
ES通过Java管理框架JMX来管理其应用. -
3.7 RESTful style API - 与集群进行交互
ES最上层是提供给用户的接口, 可以通过RESTful接口与ES集群进行交互.
4 Elasticsearch索引相关概念
-
4.1 term(索引词)
在ES中, 索引词(term)是一个能够被索引的精确值, 可以通过term query进行准确搜索. 比如: foo、Foo、FOO都是不同的索引词. -
4.2 text(文本)
文本是一段普通的非结构化文字, 通常文本会被分析成多个Term, 存储在ES的索引库中.
文本字段一般需要先分析再存储, 查询文本中的关键词时, 需要根据搜索条件搜索出原文本. -
4.3 analysis(分析)
分析是将文本转换为索引词的过程, 分析的结果依赖于分词器.
比如: FOO BAR、Foo-Bar和foo bar可能会被分析成相同的索引词foo和bar, 然后被存储到ES的索引库中. 当通过FoO:bAr进行全文搜索的时候, 搜索引擎根据匹配计算也能在索引库中查找到相关的内容. -
4.4 cluster(集群)
集群由一至多个节点组成, 对外提供索引和搜索服务. 一个节点只能加入到一个集群中.
集群中有且只能有一个节点会被选举为主节点 —— 主从节点是集群内部的说法, 对用户是透明的; ES做到了去中心化: 访问任一节点等价于访问整个集群.
同一网络中, 每个ES集群都要有唯一的名称用于区分, 默认的集群名称为"elasticsearch".
水平扩展时, 只需要将新增节点的集群名称设置为要扩容的集群名称, 该节点就会自动加入集群中. -
4.5 node(节点)
节点是逻辑上独立的服务, 是集群的一部分, 可以存储数据, 并参与集群的索引和检索功能.
节点也有唯一的名称, 用于集群的管理和通信, 节点名称在节点启动时自动分配一个随机的UUID的前7个字符 —— 当然可以自定义.
如果有多个节点在运行, 默认情况下, 这些节点会自动组成一个名为Elasticsearch的集群.
如果只有一个节点在运行, 该节点就会组成只有一个节点的名为Elasticsearch的集群.
每个节点属于哪个集群是通过"集群名称"来决定的. -
4.6 shard(分片)
单台机器(节点)无法存储大量的索引数据, ES可以把一个完整的索引分成多个分片, 分布到不同的节点上, 从而构成分布式索引.
每个分片都是一个Lucene实例, 也就是说每个分片底层都有一个单独的Lucene提供独立的索引和检索服务, 它们可以托管在集群的任一节点上.
单个Lucene中存储的索引文档最大值为 lucene-5843, 极限是2147483519(=integer.max_value - 128) 个文档. 可使用_cat/shards API 监控分片的大小.(1) 分片的好处:
允许水平切分/扩展集群容量;
可在多个分片上进行分布式的、并行的操作, 提高系统的性能和吞吐量.(2) 使用注意事项:
分片的数量只能在创建索引前指定, 创建索引后不能修改.
5.x 版本默认不能通过配置文件elasticsearch.yml定义分片个数. -
4.7 replica(副本)
ES支持为每个Shard创建多个副本, 相当于索引数据的冗余备份.
分片有Primary Shard(主分片)、Replica Shard(副本分片), 建立索引时, 系统会先将索引存储在主分片中, 然后再将主分片中的索引复制到不同的副本中.(1) 副本的重要性:
① 解决单点问题, 提高可用性和容错性: 某个节点失败时服务不受影响, 可以从副本中恢复;
② 提高查询效率和查询时的吞吐量: 搜索可以在所有的副本上并行执行, 提高了服务的并发量.(2) 使用注意事项:
主分片在建立索引时设置, 后期不能修改;
主分片和副本分片不能存储在同一个节点中 —— 无法保证高可用.
5.x版本中, 默认主分片为5个, 默认副本分片数为1个, 即每个主分片各有1个副本分片(共5个副本分片); 可随时修改副本分片的数量.
默认情况下, 每个索引共有 5 primary shard + 5 * 1 replica shard = 10 shard.
集群中至少要有2个节点, 这是最少的高可用配置. -
4.8 river(数据源)
从其他存储方式 (如数据库) 中同步数据到ES的方法, 它是以插件方式存在的一个ES服务, 通过读取river中的数据并把它索引到ES中.
官方的river有CouchDB、RabbitMQ、Twitter、Wikipedia等. -
4.9 index(索引)
索引是具有相似结构的文档的集合, 等同于Solr中的集合, 比如可以有一个商品分类索引, 订单索引.
每个索引都要有唯一的名称, 名称要小写, 通过索引名称来执行索引、搜索、更新和删除等操作.
一个集群中可以有任意多个索引, 只要保证名称不同即可. -
4.10 type(类型)
type是index的逻辑分类, 在ES 6.x版本之前, 每个索引中可以定义一个或多个type, 而在6.X版本之后, 一个index中只能定义一个type.
一种type一般被定义为具有一组公共field的document, 比如对博客系统中的数据建立索引, 可以定义用户数据type, 博客数据type, 评论数据type, 也就是每个document都必须属于某一个具体的type, 也就是说每个document都有_type属性. -
4.11 document(文档)
文档是存储在ES中的一个个JSON格式的字符串, 是ES索引中的最小数据单元, 由field(字段)构成.
一个document可以是一条商品分类数据, 一条订单数据, 例如:
book document
{
"book_id": "1",
"book_name": "Thinking in Java(Java 编程思想)",
"book_desc": "Java学习者不得不看的经典书籍",
"book_price": 108.00,
"category_id": "5"
}
-
4.12 mapping(映射)
类似于关系数据库中的Table结构, 每个index都有一个映射: 定义索引中每个字段的类型.
所有文档在写进索引之前都会先进行分析, 如何对文本进行分词、哪些词条又会被过滤, 这类行为叫做映射(mapping).
映射可以提前定义, 也可以在第一次存储文档时自动识别. 一般由用户自己定义规则.
类似于Solr中schema.xml约束文件的作用. -
4.13 field(字段)
字段可以是一个简单的值(如字符串、数字、日期), 也可以是一个数组, 还可以嵌套一个对象或多个对象.
字段类似于关系数据库中表数据的列, 每个字段都对应一个类型.
可以指定如何分析某一字段的值, 即对field指定分词器.ES的索引中, 各概念的关系为: Field --> Document --> Type --> Index, 索引结构图如下:
ES的索引结构图
与关系型数据库的对比:Elasticsearch RDBMS
Index(索引) DataBase(数据库)
Type(类型) Table(表)
Document(文档) Row(行)
Field(字段) Column(列)
Mapping(映射) Schema(约束)
Everything is indexed(存储的都是索引) Index(索引)
Query DSL(ES独特的查询语言) SQL(结构化查询语言) -
4.14 recovery(数据恢复)
又叫数据重新分布: 当有节点加入或退出时, ES会根据机器的负载对索引分片进行重新分配, 挂掉的节点重新启动时也会进行数据恢复.
Kibana工具中通过 GET _cat/health?v, 就可以看到集群所处的状态.
(二)安装Elasticsearch单机服务 以及常见问题的解决
此部署过程以Elasticsearch-6.6.0版本为例, 后续的学习和演示也用此版本.
1 准备工作
-
1.1 安装JDK
学习使用ES的前提是成功安装JDK —— 很基础的一项步骤, 这里省略.此处学习演示所用的JDK版本为:
[root@localhost ~]# java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
-
1.2 下载安装包
(1) 根据自己的系统版本下载相应的安装包, 链接如下:
https://www.elastic.co/downloads/elasticsearch
这里下载MacOS/Linux系统下的版本elasticsearch-6.6.0.tar.gz.(2) 上传并解压:
# 工作路径
mkdir -p /data/elk-6.6.0
# 上传安装包后解压
tar -zxf elasticsearch-6.6.0.tar.gz
- 1.3 创建elastic用户
Elasticsearch的启动, 必须通过专用用户elastic启动, 否则将报错.
# 创建用户
useradd elastic -s /bin/bash
# 为该用户赋予相关操作权限
chown -R elastic:elastic /data/elk-6.6.0
# 修改安装目录名称
mv elasticsearch-6.6.0 es-node
2 启动ES服务
- 2.1 修改配置文件
修改${ES_HOME}/config/elasticsearch.yml文件中关于网络的配置:
# 大约17行, 修改集群名称, 同一个集群中此名称必须相同, 才能组成一个逻辑集群:
cluster.name: heal_es
# 大约23行, 修改节点名称, 可以设置为与主机名称相同:
node.name: es-1
# 大约55行, 指定可通过外部服务器访问本地ES服务:
network.host: 0.0.0.0
# 并指定访问的端口号, 默认是9200, 为了防止冲突, 这里修改为9301:
http.port: 9301
另外, 如果要考虑到后期的版本升级, 可以指定ES存储索引和日志文件的路径, 否则容易出现数据丢失的情况.
默认的路径是Elasticsearch解压包内的data和logs.
# Path to directory where to store the data (separate multiple locations by comma):
#path.data: /data/elk-6.6.0/data
#
# Path to log files:
#path.logs: /data/elk-6.6.0/logs
- 2.2 启动服务
# 切换用户
su elastic
# 启动服务, -d是指在后台启动, 若不用此参数, ES将阻塞当前终端的命令输入功能, 如果强制使用, 将导致ES服务终止
cd /data/elk-6.6.0/es-node
./bin/elasticsearch -d
- 注意事项:
Elasticsearch必须以 非root用户启动, 否则将抛出如下错误:
[2019-06-24T21:02:07,654][WARN ][o.e.b.ElasticsearchUncaughtExceptionHandler] [es-1] uncaught exception in thread [main]
org.elasticsearch.bootstrap.StartupException: java.lang.RuntimeException: can not run elasticsearch as root
at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:163) ~[elasticsearch-6.6.0.jar:6.6.0]
at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:150) ~[elasticsearch-6.6.0.jar:6.6.0]
at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86) ~[elasticsearch-6.6.0.jar:6.6.0]
at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124) ~[elasticsearch-cli-6.6.0.jar:6.6.0]
at org.elasticsearch.cli.Command.main(Command.java:90) ~[elasticsearch-cli-6.6.0.jar:6.6.0]
at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:116) ~[elasticsearch-6.6.0.jar:6.6.0]
at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:93) ~[elasticsearch-6.6.0.jar:6.6.0]
Caused by: java.lang.RuntimeException: can not run elasticsearch as root
at org.elasticsearch.bootstrap.Bootstrap.initializeNatives(Bootstrap.java:103) ~[elasticsearch-6.6.0.jar:6.6.0]
at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:170) ~[elasticsearch-6.6.0.jar:6.6.0]
at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:333) ~[elasticsearch-6.6.0.jar:6.6.0]
at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:159) ~[elasticsearch-6.6.0.jar:6.6.0]
... 6 more
3 验证ES服务是否可用
- 3.1通过jps命令查看当前系统中运行的所有Java进程, 前提是JDK的环境变量设置OK:
[elastic@localhost bin]$ jps
25810 Elasticsearch # Elasticsearch的进程号
25926 Jps
- 3.2通过ps -ef或ps aux命令查看 - 可以查看到启动ES时的JVM参数:
[elastic@gosearch-03 bin]$ ps aux | grep elasticsearch
elastic 24181 83.5 1.2 26269656 1701524 pts/0 Sl 13:51 2:03 /data/jdk1.8.0_151/bin/java
-Xms1g -Xmx1g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly
-Des.networkaddress.cache.ttl=60 -Des.networkaddress.cache.negative.ttl=10 -XX:+AlwaysPreTouch -Xss1m
-Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -XX:-OmitStackTraceInFastThrow -Dio.netty.noUnsafe=true
-Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false
-Dlog4j2.disable.jmx=true -Djava.io.tmpdir=/tmp/elasticsearch-8345885453572562051 -XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=data -XX:ErrorFile=logs/hs_err_pid%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
-XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -Xloggc:logs/gc.log -XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=32 -XX:GCLogFileSize=64m -Des.path.home=/data/elk-6.6.0/es-node -Des.path.conf=/data/elk-6.6.0/es-node/config
-Des.distribution.flavor=default -Des.distribution.type=tar -cp /data/elk-6.6.0/es-node/lib/* org.elasticsearch.bootstrap.Elasticsearch -d
elastic 28089 0.0 0.0 103248 844 pts/0 S+ 13:54 0:00 grep elasticsearch
- 3.3在MacOS/Linux的终端(或Windows的命令行)下运行命令:
# MacOS/Linux下:
curl http://localhost:9301/
# Windows下:
Invoke-RestMethod http://localhost:9301
若能出现类似下述浏览器中的信息, 说明ES启动成功.
- 3.4或在浏览器中访问 “http://localhost:9301/”, 若能出现下述信息, 说明ES服务启动成功:
启动界面参数解释:
{
"name" : "es-1",
"cluster_name" : "heal_es", # 当前集群的名称, 同一集群中要保证一致
"cluster_uuid" : "Rcgt8uy_T5uUAu4DsnXHdQ",
"version" : {
"number" : "6.6.0", # 当前运行的ES的版本号
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "a9861f4",
"build_date" : "2019-01-24T11:27:09.439740Z",
"build_snapshot" : false, # 当前运行的版本是不是从源代码构建而来
"lucene_version" : "7.6.0", # 当前ES底层的Lucene的版本号
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
4 关闭与重启服务
- 4.1 关闭服务
Elasticsearch没有直接关闭或重启服务的命令, 关闭只能通过kill命令杀掉进程的方式, 如下:
[elastic@localhost bin]$ ps aux | grep elasticsearch # 查看ES进程的id
[elastic@localhost bin]$ kill -8 25810 # 通过进程id来杀掉服务
- 4.2 重启服务
直接启动服务:
[elastic@localhost bin]$ sh elasticsearch -d
当然可以编写一个服务脚本, 来方便快捷地启动或关闭Elasticsearch服务.
5 常见问题及解决方法
说明: 下述问题部分是在Elasticsearch 5.x版本中遇到的, 部分是在6.6.0版本中遇到的, 本篇文章已于2019-06-24日更新, 仅供参考.
- 5.1 使用ES专属用户登录时出错
(1) 问题描述:
使用ES专属用户启动ES服务时, 终端抛出如下错误:
[elastic@localhost bin]$ 2018-11-05 04:26:38,466 main ERROR Could not register mbeans java.security.AccessControlException: access denied ("javax.management.MBeanTrustPermission" "register")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:585)
......
SettingsException[Failed to load settings from /data/elk-6.6.0/es-node/config/elasticsearch.yml]; nested: AccessDeniedException[/data/elk-6.6.0/es-node/config/elasticsearch.yml];
......
Caused by: java.nio.file.AccessDeniedException: /data/elk-6.6.0/es-node/config/elasticsearch.yml
......
(2) 问题分析:
错误信息说明: 当前用户无的访问被拒绝, 可知ES专属用户无法执行当前应用.
(3) 解决方法:
为ES创建专属用户后, 对其赋予相应的读写权限.
# 为该用户赋予相关操作权限
chown -R elastic:elastic /data/elk-6.6.0
- 5.2 syscall filter - 不能安装
(1) 问题描述:
启动ES时, 抛出如下错误信息:
[2018-11-06T03:12:35,812][WARN ][o.e.b.JNANatives ] unable to install syscall filter:
java.lang.UnsupportedOperationException: seccomp unavailable: requires kernel 3.5+ with CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER compiled in
at org.elasticsearch.bootstrap.SystemCallFilter.linuxImpl(SystemCallFilter.java:329) ~[elasticsearch-5.6.10.jar:5.6.10]
......
[2018-11-06T03:12:39,947][INFO ][o.e.n.Node ] initialized
[2018-11-06T03:12:39,947][INFO ][o.e.n.Node ] [jVSUBme] starting ...
[2018-11-06T03:12:40,131][INFO ][o.e.t.TransportService ] [jVSUBme] publish_address {10.0.20.50:9300}, bound_addresses {[::]:9300}
[2018-11-06T03:12:40,145][INFO ][o.e.b.BootstrapChecks ] [jVSUBme] bound or publishing to a non-loopback address, enforcing bootstrap checks
[2018-11-06T03:12:40,148][ERROR][o.e.b.Bootstrap ] [jVSUBme] node validation exception
[1] bootstrap checks failed
[1]: system call filters failed to install; check the logs and fix your configuration or disable system call filters at your own risk
[2018-11-06T03:12:40,150][INFO ][o.e.n.Node ] [jVSUBme] stopping ...
[2018-11-06T03:12:40,186][INFO ][o.e.n.Node ] [jVSUBme] stopped
[2018-11-06T03:12:40,186][INFO ][o.e.n.Node ] [jVSUBme] closing ...
[2018-11-06T03:12:40,199][INFO ][o.e.n.Node ] [jVSUBme] closed
(2) 问题解决:
Centos 6.5不支持SecComp, 而ES 5.x版本起 bootstrap.system_call_filter 的默认值是 true.
禁用: 在elasticsearch.yml中配置 bootstrap.system_call_filter=false, 注意要在Memory配置项的下面添加:
bootstrap.system_call_filter: false
- 5.3 memory is not locked - 内存没有锁定
(1) 问题描述:
启动Elasticsearch时, 抛出如下错误信息:
[2018-11-06T03:18:53,221][WARN ][o.e.b.JNANatives ] Unable to lock JVM Memory: error=12, reason=Cannot allocate memory
[2018-11-06T03:18:53,232][WARN ][o.e.b.JNANatives ] This can result in part of the JVM being swapped out.
[2018-11-06T03:18:53,232][WARN ][o.e.b.JNANatives ] Increase RLIMIT_MEMLOCK, soft limit: 65536, hard limit: 65536
[2018-11-06T03:18:53,233][WARN ][o.e.b.JNANatives ] These can be adjusted by modifying /etc/security/limits.conf, for example:
# allow user 'elastic' mlockall
elastic soft memlock unlimited
elastic hard memlock unlimited
[2018-11-06T03:18:53,233][WARN ][o.e.b.JNANatives ] If you are logged in interactively, you will have to re-login for the new limits to take effect.
......
[2018-11-06T03:18:57,644][ERROR][o.e.b.Bootstrap ] [jVSUBme] node validation exception
[1] bootstrap checks failed
[1]: memory locking requested for elasticsearch process but memory is not locked
[2018-11-06T03:18:57,646][INFO ][o.e.n.Node ] [jVSUBme] stopping ...
[2018-11-06T03:18:57,693][INFO ][o.e.n.Node ] [jVSUBme] stopped
[2018-11-06T03:18:57,693][INFO ][o.e.n.Node ] [jVSUBme] closing ...
[2018-11-06T03:18:57,707][INFO ][o.e.n.Node ] [jVSUBme] closed
(2) 问题分析:
Elasticsearch的配置文件中有如下选项: bootstrap.memory_lock: true, 意为在启动Elasticsearch服务时, 锁定JVM需要的内存, 避免OS层面的Swap交换 —— 降低ES服务性能.
此选项默认为false, 即不开启锁定.
(3) 问题解决:
① 在配置文件中用"#"注释掉bootstrap.memory_lock: true, 或修改其值为false;
② 或者修改系统/etc/security/limits.conf文件, 为ES专属用户elastic解除限制:
# 在文件最后添加下述配置, 允许用户'elastic'锁定内存
elastic soft memlock unlimited
elastic hard memlock unlimited
- 5.4 max virtual memory - 最大虚拟内存太小
(1) 问题描述:
启动Elasticsearch时, 抛出如下错误信息:
ERROR: [1] bootstrap checks failed
[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
[2019-06-24T21:05:12,355][INFO ][o.e.n.Node ] [es-1] stopping ...
[2019-06-24T21:05:12,425][INFO ][o.e.n.Node ] [es-1] stopped
[2019-06-24T21:05:12,426][INFO ][o.e.n.Node ] [es-1] closing ...
[2019-06-24T21:05:12,439][INFO ][o.e.n.Node ] [es-1] closed
[2019-06-24T21:05:12,442][INFO ][o.e.x.m.p.NativeController] [es-1] Native controller process has stopped - no new native processes can be started
(2) 问题分析:
Elasticsearch服务需要大量的虚拟内存支撑, 系统默认的最大虚拟内存是65530, 而ES至少需要262144.
(3) 问题解决:
① 切换到root用户下, 修改配置文件sysctl.conf:
vim /etc/sysctl.conf
# 修改下述配置, 如果没有就在文件末尾添加:
vm.max_map_count=655360
# 执行命令使修改生效:
sysctl -p
② 然后重新启动Elasticsearch, 即可启动成功.
- 5.5 max number of threads - 最大线程数太小
(1) 问题描述:
启动Elasticsearch时, 抛出如下错误信息:
ERROR: [2] bootstrap checks failed
[1]: max number of threads [1024] for user [elastic] is too low, increase to at least [4096]
[2]: system call filters failed to install; check the logs and fix your configuration or disable system call filters at your own risk
[2019-06-24T21:51:04,810][INFO ][o.e.n.Node ] [es-2] stopping ...
[2019-06-24T21:51:04,847][INFO ][o.e.n.Node ] [es-2] stopped
[2019-06-24T21:51:04,847][INFO ][o.e.n.Node ] [es-2] closing ...
[2019-06-24T21:51:04,864][INFO ][o.e.n.Node ] [es-2] closed
[2019-06-24T21:51:04,867][INFO ][o.e.x.m.p.NativeController] [es-2] Native controller process has stopped - no new native processes can be started
(2) 问题分析:
Elasticsearch服务需要用到多线程以提高执行效率, Cent OS 6.5默认的单个用户最大线程数是1024, 而ES用户至少需要4096.
(3) 问题解决:
① 切换到root用户下, 修改配置文件:
[elastic@localhost bin]$ su root
Password:
[root@localhost bin]# vim /etc/security/limits.d/90-nproc.conf
# 找到如下内容, 如果没有就创建:
* soft nproc 1024
# 修改为8192, 其中*表示所有用户:
* soft nproc 8192
② 保存, 退出, 然后重新登录(可以打开新的会话终端), 最后启动Elasticsearch, 即可启动成功.
- 5.6 max file descriptores - 最大可创建文件数太小
(1) 问题描述:
启动Elasticsearch时, 抛出如下错误信息:
ERROR: bootstrap checks failed
max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536]
[2019-06-24T22:06:04,810][INFO ][o.e.n.Node ] [es-2] stopping ...
[2019-06-24T22:06:04,847][INFO ][o.e.n.Node ] [es-2] stopped
[2019-06-24T22:06:04,847][INFO ][o.e.n.Node ] [es-2] closing ...
[2019-06-24T22:06:04,864][INFO ][o.e.n.Node ] [es-2] closed
[2019-06-24T22:06:04,867][INFO ][o.e.x.m.p.NativeController] [es-2] Native controller process has stopped - no new native processes can be started
(2) 问题分析:
Elasticsearch服务在运行期间需要创建大量的本地文件, Cent OS 6.5默认的单个用户最多可操作文件数为4096个, 而ES要求至少需要65536.
(3) 问题解决:
① 切换到root用户下, 修改配置文件:
[elastic@localhost bin]$ su root
Password:
[root@localhost bin]# vim /etc/security/limits.conf
# 找到如下内容, 如果没有就创建:
* soft nofile 4096
# 修改为65536, 其中*表示所有用户:
* soft nproc 65536
② 保存, 退出, 然后重新登录(可以打开新的会话终端), 最后启动Elasticsearch, 即可启动成功.
(三)初探Elasticsearch的主要配置文件(以6.6.0版本为例)
1 elasticsearch.yml(ES服务配置)
文件位置: ${ES_HOME}/config/elasticsearch.yml
# ======================== Elasticsearch Configuration =========================
#
# NOTE: Elasticsearch comes with reasonable defaults for most settings.
# Before you set out to tweak and tune the configuration, make sure you
# understand what are you trying to accomplish and the consequences.
#
# The primary way of configuring a node is via this file. This template lists
# the most important settings you may want to configure for a production cluster.
#
# Please consult the documentation for further information on configuration options:
# https://www.elastic.co/guide/en/elasticsearch/reference/index.html
- 1.1 Cluster集群配置
# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
# 集群名称, 具有相同名称的节点才能组成一个逻辑集群, 默认是"elasticsearch", 建议修改为与项目相关的名称.
cluster.name: heal_es
- 1.2 Node节点配置
# ------------------------------------ Node ------------------------------------
#
# Use a descriptive name for the node:
# 本节点的名称, 同一集群中各个node的名称不允许重复. 不配置则系统默认分配.
# node.name: node-1
#
# Add custom attributes to the node:
# 指定节点的部落属性 --- 一个比集群更大的范围, 即定义一些通用属性, 用于集群分配碎片时的过滤.
#node.attr.rack: r1
Elasticsearch 6.6.0版本中取消了下述配置:
# 指定本节点是否有资格被选举为主节点, 默认是true.
# ES集群中第一台机器被默认为master, 若此节点挂掉, 将重新选举master.
# node.master=true
#
# 指定本节点在集群中是否存储数据, 默认为true.
# node.data=true
#
# 配置文件中给出了三种配置高性能集群拓扑结构的模式, 如下:
# 1. 如果想让节点不被选举为主节点, 只用来存储数据, 可作为负载器:
# node.master: false
# node.data: true
# node.ingest: true # 默认为true
#
# 2. 如果想让节点成为主节点, 且不存储任何数据, 并保有空闲资源,可作为协调器:
# node.master: true
# node.data: false
# node.ingest: true
#
# 3. 如果想让节点既不作主节点, 又不作数据节点, 那么可将其作为搜索器, 从节点中获取数据, 生成搜索结果等:
# node.master: false
# node.data: false
# node.ingest: true
#
# 4. 仅作为协调器:
# node.master: false
# node.data: false
# node.ingest: false
- 1.3 Paths路径配置
# ----------------------------------- Paths ------------------------------------
# Path to directory where to store the data (separate multiple locations by comma):
# 如果不配置下述两项, ES将在其主目录下创建. 建议将程序与数据分离配置, 方便系统迁移与升级.
# 存放索引数据的目录
path.data: /data/elk-6.6.0/data
#
# Path to log files: 存放日志信息的目录
path.logs: /data/elk-6.6.0/logs
- 1.4 Memory内存配置
# ----------------------------------- Memory -----------------------------------
#
# Lock the memory on startup:
# 启动时是否锁定ES运行所需的内存, 默认为false.
# true: 锁定---防止ES使用Swap交换空间, 效率较高. 此时要确保当前用户具有memlock的权限.
# false: 将使用Swap交换空间.
# bootstrap.memory_lock: false
bootstrap.system_call_filter: false
#
# 确保ES_HEAP_SIZE参数的值设置为系统可用内存的一半左右, 不要超过, 因为Lucene底层索引和检索还需要一定的内存.
# Make sure that the heap size is set to about half the memory available
# on the system and that the owner of the process is allowed to use this
# limit.
#
# 当系统使用内存交换, ES的性能将变得很差
# Elasticsearch performs poorly when the system is swapping the memory.
- 1.5 Network网络配置
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
# 对外网关的IP, 默认为localhost, 此时只能通过localhost或127.0.0.1访问.
# 设置为0.0.0.0, 即可被外部所有网络访问, 如此但是不够安全, 可以指定某一个网段:
network.host: 0.0.0.0
#network.host: 192.168.0.1
#
# Set a custom port for HTTP:
# 对外提供的HTTP访问端口, 默认为9200. 为提高安全性, 建议设置为其他值.
# 可以指定一个值或一个区间, 如果是区间就会采取区间内第一个可用的端口.
#http.port: 9200
#
# For more information, consult the network module documentation.
Elasticsearch 6.6.0版本取消了transport.tcp.port的设置:
#
# 集群节点之间通信的TCP传输端口. 下述Discovery部分的设置、ES的Java API 也通过此端口传输数据. 默认为9300.
# 可以指定一个值或一个区间, 如果是区间就会采取区间内第一个可用的端口.
# transport.tcp.port: 9300
① 旧版本的Java API中客户端TransportClient 使用的是9300端口, 它执行的是序列化的Java请求, 性能较低, 在7.x版本中将过期, 8.x版本中将移除;
② 新版本推荐使用Java High Level REST Client客户端, 也就是RestHighLevelClient, 它执行HTTP请求, 使用的端口是http.port, 默认就是9200.
- 1.6 Discovery节点发现配置
# --------------------------------- Discovery ----------------------------------
#
# 启动新节点时, 通过IP列表进行节点发现, 组建集群
# Pass an initial list of hosts to perform discovery when new node is started:
# The default list of hosts is ["127.0.0.1", "[::1]"]
# '127.0.0.1'是ipv4的回环地址, '[::1]'是ipv6的回环地址
#
# 1.x中默认使用多播(multicast)协议: 自动发现同一网段中的ES节点并组建集群;
# 2.x中默认使用单播(unicast)协议: 要组建集群, 就需要在这里指定集群的节点信息 -- 安全高效, 但不够灵活.
#
# 默认已经关闭了自动发现节点的多播(组播)协议功能:
# discovery.zen.ping.multicast.enabled: false
#
# 指定单播模式的IP(或hostname)列表:
# 也可配置为: ["ip:port", "ip:port"]. 若port未设置, 将使用transport.tcp.port的值.
#discovery.zen.ping.unicast.hosts: ["host1", "host2"]
#
# Prevent the "split brain" by configuring the majority of nodes (total number of master-eligible nodes / 2 + 1):
# 配置此参数以防止集群出现"脑裂现象": 集群中出现2个及以上master节点, 将导致数据不一致.
# 官方推荐: 选举master的最少节点数 = (具有master资格的节点数 / 2) + 1
#discovery.zen.minimum_master_nodes:
#
# 设置集群中自动发现其他节点的连接超时时长, 默认是3秒.
# 在网络不佳时增加这个值, 会增加节点等待响应的时间, 可以减少误判.
# discover.zen.ping.timeout: 3s
#
# For more information, consult the zen discovery module documentation.
- 1.7 Gateway网关配置
# ---------------------------------- Gateway -----------------------------------
#
# Block initial recovery after a full cluster restart until N nodes are started:
# 配置集群中的N个节点启动后, 才允许进行数据恢复处理. 默认是1.
#gateway.recover_after_nodes: 3
#
# For more information, consult the gateway module documentation.
- 1.8 Various其他配置
# ---------------------------------- Various -----------------------------------
#
# 在一台服务器上禁止启动多个es服务
# Disable starting multiple nodes on a single system:
#
# node.max_local_storage_nodes: 1
#
# 设置是否可以通过正则或者_all删除或者关闭索引库,默认true表示必须需要显式指定索引库名称
#
# Require explicit names when deleting indices:
#action.destructive_requires_name: true
# 是否压缩TCP传输的数据, 默认是false:
# transport.tcp.compress: false
# 是否使用HTTP协议对外提供服务, 默认是true:
# http.cors.enabled: true
# http传输内容的最大容量, 默认是100MB:
# http.max_content_length: 100mb
注意:
在2.x版本的配置文件中, 存在 Index 配置项, 可配置包括分片数、副本分片数在内的配置.
在5.x版本中, 不支持在配置文件中设置此类配置项了, 请注意此区别.
修改完之后使用命令查看具体修改了哪些值:
# 查看某个文件上次修改的内容:
grep '^[a-z]' /data/elk-6.6.0/es-node/config/elasticsearch.yml
2 jvm.options(JVM参数配置)
文件位置: ${ES_HOME}/config/jvm.options
关于JVM常见参数的配置, 可参考博主文章:
对Tomcat 8.0进行JVM层面的优化(基于Oracle JDK 8)
关于JVM的垃圾回收(GC) 这可能是你想了解的
## JVM configuration
################################################################
## IMPORTANT: JVM heap size
################################################################
##
## You should always set the min and max JVM heap
## size to the same value. For example, to set
## the heap to 4 GB, set:
##
## -Xms4g
## -Xmx4g
##
## See https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html
## for more information
##
################################################################
# Xms represents the initial size of total heap space
# Xmx represents the maximum size of total heap space
# 下述配置最好不要超过节点物理内存的50%, 留出50%供Lucene底层索引与检索使用
-Xms1g
-Xmx1g
################################################################
## Expert settings
################################################################
##
## All settings below this section are considered
## expert settings. Don't tamper with them unless
## you understand what you are doing
##
################################################################
## GC configuration
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
## G1GC Configuration
# NOTE: G1GC is only supported on JDK version 10 or later.
# To use G1GC uncomment the lines below.
# 10-:-XX:-UseConcMarkSweepGC
# 10-:-XX:-UseCMSInitiatingOccupancyOnly
# 10-:-XX:+UseG1GC
# 10-:-XX:InitiatingHeapOccupancyPercent=75
## DNS cache policy
# cache ttl in seconds for positive DNS lookups noting that this overrides the
# JDK security property networkaddress.cache.ttl; set to -1 to cache forever
-Des.networkaddress.cache.ttl=60
# cache ttl in seconds for negative DNS lookups noting that this overrides the
# JDK security property networkaddress.cache.negative ttl; set to -1 to cache
# forever
-Des.networkaddress.cache.negative.ttl=10
## optimizations
# pre-touch memory pages used by the JVM during initialization
-XX:+AlwaysPreTouch
## basic
# explicitly set the stack size (reduce to 320k on 32-bit client JVMs)
-Xss1m
# set to headless, just in case
-Djava.awt.headless=true
# ensure UTF-8 encoding by default (e.g. filenames)
-Dfile.encoding=UTF-8
# use our provided JNA always versus the system one
-Djna.nosys=true
# turn off a JDK optimization that throws away stack traces for common
# exceptions because stack traces are important for debugging
-XX:-OmitStackTraceInFastThrow
# flags to configure Netty
-Dio.netty.noUnsafe=true
-Dio.netty.noKeySetOptimization=true
-Dio.netty.recycler.maxCapacityPerThread=0
# log4j 2
-Dlog4j.shutdownHookEnabled=false
-Dlog4j2.disable.jmx=true
-Dlog4j.skipJansi=true
## heap dumps
# generate a heap dump when an allocation from the Java heap fails
# heap dumps are created in the working directory of the JVM
-XX:+HeapDumpOnOutOfMemoryError
# specify an alternative path for heap dumps; ensure the directory exists and has sufficient space
# 生产环境中指定当发生OOM异常时, Heap的Dump Path, 默认是 -XX:HeapDumpPath=data
-XX:HeapDumpPath=/var/lib/elasticsearch
# specify an alternative path for JVM fatal error logs
-XX:ErrorFile=logs/hs_err_pid%p.log
## JDK 8 GC logging
8:-XX:+PrintGCDetails
8:-XX:+PrintGCDateStamps
8:-XX:+PrintTenuringDistribution
8:-XX:+PrintGCApplicationStoppedTime
8:-Xloggc:logs/gc.log
8:-XX:+UseGCLogFileRotation
8:-XX:NumberOfGCLogFiles=32
8:-XX:GCLogFileSize=64m
# JDK 9+ GC logging
9-:-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m
# due to internationalization enhancements in JDK 9 Elasticsearch need to set the provider to COMPAT otherwise
# time/date parsing will break in an incompatible way for some date patterns and locals
9-:-Djava.locale.providers=COMPAT
# temporary workaround for C2 bug with JDK 10 on hardware with AVX-512
10-:-XX:UseAVX=2
Elasticsearch 6.6.0中已经移除了下述优化配置:
# disable calls to System#gc
-XX:+DisableExplicitGC
# force the server VM (remove on 32-bit client JVMs)
-server
# use old-style file permissions on JDK9
-Djdk.io.permissionsUseCanonicalPath=true
其他说明:
-Xmx2g # 这种参数没有限制JVM版本
8: -Xmx2g # 限制JVM版本为8
8-: -Xmx2g # 限制JVM版本为8及8以上
8-10: -Xmx2g # 限制JVM版本在8-10之间
3 log4j2.properties(日志配置)
文件位置: ${ES_HOME}/config/log4j2.properties
一般使用默认日志配置即可.
(四)安装Kibana 6.6.0 以及常见问题的解决
-
1 Kibana是什么
Kibana是一个配合Elasticsearch使用的、开源的数据分析和可视化平台, 可以与Elasticsearch中存储的索引文档进行交互.
使用Kibana能执行高级的数据分析, 并通过图表、表格、地图等形式显示分析结果. -
2 安装并启动Kibana
在安装好单机版Elasticsearch的基础上, 安装Kibana插件, 使用其UI界面进行后续的学习操作.
前提: JDK和Elasticsearch单机服务已成功配置部署. -
2.1 准备安装包
(1) 下载安装包:
下载地址: https://www.elastic.co/downloads/past-releases.
本文演示使用的是kibana-6.6.0-linux-x86_64.tar.gz.
(2) 解压并重命名:
# 上传安装包至服务器的/data/elk-6.6.0下:
cd /data/elk-6.6.0
# 解压安装包:
tar -zxf kibana-6.6.0-linux-x86_64.tar.gz
# 重命名
mv kibana-6.6.0-linux-x86_64.tar.gz kibana
- 2.2 修改配置文件
文件位置: $kibana/config/kebana.yml:
# Kibana is served by a back end server. This setting specifies the port to use.
# Kibana是个后台服务, 需要指定服务的IP地址和端口号. 默认值为5601.
server.port: 5601
# Specifies the address to which the Kibana server will bind. IP addresses and host names are both valid values.
# The default is 'localhost', which usually means remote machines will not be able to connect.
# To allow connections from remote users, set this parameter to a non-loopback address.
# 指定Kibana服务需要绑定的IP地址, 默认值为本地回环地址"localhost". 若要允许远程访问, 就需要改为本地服务器的IP地址. (127.0.0.1的作用等同于localhost, 可用ifconfig命令查看本机的IPV4地址)
server.host: 172.16.22.133
# Enables you to specify a path to mount Kibana at if you are running behind a proxy. This only affects
# the URLs generated by Kibana, your proxy is expected to remove the basePath value before forwarding requests
# to Kibana. This setting cannot end in a slash.
#server.basePath: ""
# The maximum payload size in bytes for incoming server requests.
#server.maxPayloadBytes: 1048576
# The Kibana server's name. This is used for display purposes.
#server.name: "your-hostname"
# The URL of the Elasticsearch instance to use for all your queries.
# 需要监控的Elasticsearch服务的地址, 默认是本地回环地址+9200端口
#elasticsearch.url: "http://localhost:9200"
# When this setting's value is true Kibana uses the hostname specified in the server.host
# setting. When the value of this setting is false, Kibana uses the hostname of the host
# that connects to this Kibana instance.
#elasticsearch.preserveHost: true
# Kibana uses an index in Elasticsearch to store saved searches, visualizations and
# dashboards. Kibana creates a new index if the index doesn't already exist.
# Kibana在Elasticsearch中的索引
kibana.index: ".kibana"
- 2.3 启动Kibana并验证
(1) 启动Kibana:
cd /data/elk-6.6.0/kibana/bin
# 前台启动, 不能关闭终端, 即阻塞式启动, 不能执行其他操作. 此时可通过Ctrl + C终止服务.
./kibana
# 后台启动, 可退出终端, 若当前终端窗口关闭, 服务也将终止.
nohup ./kibana &
# 上述nohup命令将输出追加到了nohup.out中, 为了直接查看启动情况, 可省去nohup命令:
# ./kibana &
(2) 浏览器检查
在浏览器访问: http://172.16.22.133:5601, 出现如下界面, 说明启动成功:
- 2.4 关闭Kibana服务
# 查看node服务的进程id
[root@localhost bin]# ps aux | grep node
root 15653 3.7 1.0 1142400 88020 pts/0 Sl 04:21 0:07 ./../node/bin/node --no-warnings ./../src/cli
# kill掉 ./../src/cli 进程:
[root@localhost bin]# kill -9 15653
或者: 如果没有在后台启动Kibana, 即没有使用nohup命令启动, 则关闭当前会话(即终端窗口), Kibana服务也将终止.
- 3 Kibana功能测试
前往Dev Tools工具界面, 输入如下命令并点击 [绿色的向右的箭头➡] 发送请求:
GET _cluster/health
得到如下关于集群健康状况的JSON响应:
{
"cluster_name" : "heal_es", # 集群名称, 默认是elasticsearch
"status" : "yellow", # 集群健康状态
"timed_out" : false,
"number_of_nodes" : 1, # 集群中的节点数
"number_of_data_nodes" : 1, # 存储数据的节点数
"active_primary_shards" : 33, # 活跃的primary shard数
"active_shards" : 33, # 活跃的shard数, 包括primary shard和replica shard
"relocating_shards" : 0, # 当前正在从一个节点迁往其他节点的分片的数量, 通常是0. 当ES发现集群不太均衡(如添加或下线一个节点)时, 该值会上涨
"initializing_shards" : 0, # 刚创建的分片个数, 创建第一个索引时、节点重启时, 会短暂处于此状态(不应长期停留此状态)
"unassigned_shards" : 5, # 在集群中存在, 却又不能找到 -- 即未分配的副本
"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" : 86.8421052631579
}
-
3.1 关于集群的状态status
① green: 所有primary shard和replica shard都已成功分配, 集群是100%可用的;② yellow: 所有primary shard都已成功分配, 但至少有一个replica shard缺失. 此时集群所有功能都正常使用, 数据不会丢失, 搜索结果依然完整, 但集群的可用性减弱. —— 需要及时处理的警告.
③ red: 至少有一个primary shard(以及它的全部副本分片)缺失 —— 部分数据不能使用, 搜索只能返回部分数据, 而分配到这个分配上的写入请求会返回一个异常. 此时虽然可以运行部分功能, 但为了索引数据的完整性, 需要尽快修复集群.
-
3.2 关于集群中的节点数
使用GET _cat/nodes?v 查看当前节点数:
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
172.16.22.133 62 98 2 0.98 1.25 1.39 mdi * 1UlY804
-
3.3 关于未分配的分片
unassigned_shards: 已经在集群状态中存在、但在集群里又找不到的分片, 来源通常是未分配的副本. 比如: 一个有 5 分片和 1 副本的索引, 在单节点集群上就会有 5 个未分配副本分片.
如果集群状态是red, 也会长期存在未分配分片(因为缺少主分片). -
4 常见问题及解决
-
(1) 问题描述:
启动Kibana时抛出如下异常: (这里以Kibana 7.2.0为例进行说明)
[elastic@localhost bin]$ ./kibana
log [06:20:42.283] [fatal][root] Error: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by /data/elk-7.2.0/kibana-7.2.0/node_modules/@elastic/nodegit/build/Release/nodegit.node)
at Object.Module._extensions..node (internal/modules/cjs/loader.js:718:18)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Module.require (internal/modules/cjs/loader.js:637:17)
at require (internal/modules/cjs/helpers.js:22:18)
at Object.<anonymous> (/data/elk-7.2.0/kibana-7.2.0/node_modules/@elastic/nodegit/dist/nodegit.js:12:12)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Module._compile (/data/elk-7.2.0/kibana-7.2.0/node_modules/pirates/lib/index.js:99:24)
at Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Object.newLoader [as .js] (/data/elk-7.2.0/kibana-7.2.0/node_modules/pirates/lib/index.js:104:7)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Module.require (internal/modules/cjs/loader.js:637:17)
at require (internal/modules/cjs/helpers.js:22:18)
at Object.require (/data/elk-7.2.0/kibana-7.2.0/x-pack/plugins/code/server/git_operations.js:10:19)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Module._compile (/data/elk-7.2.0/kibana-7.2.0/node_modules/pirates/lib/index.js:99:24)
at Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Object.newLoader [as .js] (/data/elk-7.2.0/kibana-7.2.0/node_modules/pirates/lib/index.js:104:7)
at Module.load (internal/modules/cjs/loader.js:599:32)
FATAL Error: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by /data/elk-7.2.0/kibana-7.2.0/node_modules/@elastic/nodegit/build/Release/nodegit.node)
- (2) 解决方法:
由出错内容可知, 系统glibc库版本过低, 需要升级到2.17:
# 退出ES专用用户, 通过root用户下载安装包, 并解压:
wget http://ftp.gnu.org/gnu/glibc/glibc-2.17.tar.gz
tar glibc-2.17.tar.gz
# 进入解压后的目录, 然后创建编译目录:
cd glibc-2.17
mkdir build && cd build
# 检查(预编译):
../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin
# 如果没有出现问题的化, 就可以正式编译了. 这里开启8个线程编译:
make -j 8
# 等待编译完成后, 安装:
make install
# 查看已安装的GLIBC库版本:
[root@localhost date]# strings /lib64/libc.so.6 | grep GLIBC
GLIBC_2.2.5
GLIBC_2.2.6
GLIBC_2.3
GLIBC_2.3.2
GLIBC_2.3.3
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.5
GLIBC_2.6
GLIBC_2.7
GLIBC_2.8
GLIBC_2.9
GLIBC_2.10
GLIBC_2.11
GLIBC_2.12
GLIBC_2.13
GLIBC_2.14
GLIBC_2.15
GLIBC_2.16
GLIBC_2.17
GLIBC_PRIVATE
# 也可通过下述命令查看:
[root@localhost data]# ldd --version
ldd (GNU libc) 2.17
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
- (3) 再次启动Kibana, 该问题得以解决👌:
(五)ES通过Kibana查看Elasticsearch集群的健康状态、节点和索引个数
- 1 检查集群的健康状况
ES提供了一套_cat API, 可以查看ES中的各类数据.
查询API:
GET _cat/health?v
响应信息如下(一行显示不全, 分作两行):
epoch timestamp cluster status node.total node.data shards pri relo
1552979327 07:08:47 heal_es yellow 1 1 33 33 0
init unassign pending_tasks max_task_wait_time active_shards_percent
0 5 0 - 86.8%
-
(1) 如何快速了解集群的健康状况? 通过查看status选项的值:
① green: 所有primary shard和replica shard都已成功分配, 集群是100%可用的;
② yellow: 所有primary shard都已成功分配, 但至少有一个replica shard缺失. 此时集群所有功能都正常使用, 数据不会丢失, 搜索结果依然完整, 但集群的可用性减弱. —— 需要及时处理的警告.
③ red: 至少有一个primary shard(以及它的全部副本分片)缺失 —— 部分数据不能使用, 搜索只能返回部分数据, 而分配到这个分配上的写入请求会返回一个异常. 此时虽然可以运行部分功能, 但为了索引数据的完整性, 需要尽快修复集群. -
(2) 集群状态为什么是yellow?
① 当前只有一个Elasticsearch节点, 而且此时ES中只有一个Kibana内建的索引数据.
② ES为每个index默认分配5个primary shard和5个replica shard, 为了保证高可用, 它还要求primary shard和replica shard不能在同一个node上.
③ 当前服务中, Kibana内建的index是1个primary shard和1个replica shard, 由于只有1个node, 所以只有primary shard被分配和启动了, 而replica shard没有被成功分配(没有其他node可用). -
2 查看集群中的节点个数
查询API:
GET _cat/nodes?v
响应信息如下(一行显示不全, 分作两行):
ip heap.percent ram.percent cpu load_1m load_5m load_15m
172.16.22.133 49 98 3 0.56 0.74 1.02
node.role master name
mdi * 1UlY804
- 3 查看集群中的索引
查询API:
GET _cat/indices?v
响应信息如下(一行显示不全, 分作两行):
health status index uuid pri rep
green open .kibana_1 4q7ELvdcTVilW3UwtMWqeg 1 0
docs.count docs.deleted store.size pri.store.size
18 0 78.5kb 78.5kb
(六)通过Kibana插件操作ES中的索引文档 (CRUD操作)
- 1 创建、删除索引
- 1.1 创建索引
# 创建索引API:
PUT test_index?pretty
# 响应信息如下:
#! Deprecation: the default number of shards will change from [5] to [1] in 7.0.0;
# if you wish to continue using the default of [5] shards, you must manage this on the create index request or with an index template
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "test_index"
}
# 查看集群中的索引:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open test_index hMeJ-M9pSHSXl0t39OedYw 5 1 0 0 1.2kb 1.2kb
yellow open .kibana_1 4q7ELvdcTVilW3UwtMWqeg 1 0 18 0 78.5kb 78.5kb
过时说明:
在创建索引时, Elasticsearch提出过时警告: 从7.0.0版本开始, 默认的Shard个数将从[5]变为[1].
如果要继续使用默认的[5]个分片(Shard), 就需要在创建Index时指定, 或者通过索引模板创建Index.
关于创建Index时指定分片个数的方法, 参见后续的博文.
- 1.2 删除索引
# 删除索引API:
DELETE test_index?pretty
# 响应信息如下:
{
"acknowledged": true
}
- 2 document的结构
ES是一款面向文档的数据搜索、分析引擎. document结构说明:
(1) 基于面向对象的开发思想, 应用系统中的数据结构都是很复杂的: 对象中嵌套对象, 如CRM系统中的客户对象中, 还会嵌入客户相关的企业对象.
(2) 对象数据存储到数据库中, 需要分解, 将嵌套对象分解为扁平的多张表数据, 每次操作时需要还原回对象格式, 过程繁琐.
(3) ES存储的是JSON格式的文档, 基于此, ES可以提供复杂的索引, 全文检索, 分析聚合等功能.
(4) document格式示例:
{
"id": "5220",
"name": "张三",
"sex": "男",
"age": 25,
"phone": 13312345678,
"email": "zhangsan@163.com",
"company": {
"name": "Alibaba",
"location": "杭州"
},
"join_date": "2018/11/01"
}
接下来以电商系统中的搜索子系统为例, 演示对文档的操作方法.
- 3 添加文档
(1)添加API:
PUT index/type/id
{
"JSON格式的文档数据"
}
说明: ES会自动创建PUT API中指定的index和type, 不需要提前创建;
而且ES默认对document的每个field都建立倒排索引, 保证它们都可以被检索.
(2) 添加示例:
PUT book_shop/it_book/1
{
"name": "Java编程思想",
"author": "[美] Bruce Eckel",
"category": "编程语言",
"desc": "Java学习必读经典,殿堂级著作!",
"price": 109.0,
"publisher": "机械工业出版社",
"date": "2007-06-01",
"tags": [ "Java", "编程语言" ]
}
(3) 添加之后的响应信息:
{
"_index" : "book_shop",
"_type" : "it_book",
"_id" : "1",
"_version" : 1,
"result" : "created", # 操作结果: created(创建)了索引
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
(4) 再添加如下数据:
PUT book_shop/it_book/2
{
"name": "深入理解Java虚拟机:JVM高级特性与最佳实践",
"author": "周志明",
"category": "编程语言",
"desc": "Java图书领域公认的经典著作",
"price": 79.0,
"date": "2013-10-01",
"publisher": "机械工业出版社",
"tags": [ "Java", "虚拟机", "最佳实践" ]
}
PUT book_shop/it_book/3
{
"name": "Java并发编程的艺术",
"author": "方腾飞,魏鹏,程晓明",
"category": "编程语言",
"desc": "阿里系工程师的并发编程实践",
"price": 59.0,
"date": "2015-07-10",
"publisher": "机械工业出版社",
"tags": [ "Java", "并发编程" ]
}
- 4 查询文档
(1) 检索API:
GET index/type/id
(2) 检索示例:
GET book_shop/it_book/2
(3) 检索的结果:
{
"_index" : "book_shop1",
"_type" : "it_book",
"_id" : "2",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "深入理解Java虚拟机:JVM高级特性与最佳实践",
"author" : "周志明",
"category" : "编程语言",
"desc" : "Java图书领域公认的经典著作",
"price" : 79.0,
"date" : "2013-10-01",
"publisher" : "机械工业出版社",
"tags" : [
"Java",
"虚拟机",
"最佳实践"
]
}
}
- 5 修改文档
5.1 替换文档
(1) 替换API - 与添加API相同, 只不过文档id要存在, 此时系统将判定为修改操作:
—— 无论文档数据是否存在修改, 对同一id的文档执行1次以上的PUT操作, 都是修改操作.
PUT index/type/id
{
"JSON格式的文档数据"
}
注意: 替换方式的不便之处: 必须填写要修改文档的所有field, 如果缺少, 修改后的文档中将丢失相关field.
(2) 替换示例 - 为name添加了"(第4版)"
PUT book_shop/it_book/1
{
"name": "Java编程思想(第4版)",
"author": "[美] Bruce Eckel",
"category": "编程语言",
"desc": "Java学习必读经典,殿堂级著作!",
"price": 109.0,
"date": "2007-06-01",
"publisher": "机械工业出版社",
"tags": [ "Java", "编程语言" ]
}
(3) 替换的结果信息:
{
"_index" : "book_shop",
"_type" : "it_book",
"_id" : "1",
"_version" : 2,
"result" : "updated", // 操作结果: updated(修改)了索引
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
5.2 更新文档
(1) 更新API:
—— 通过POST和_update, 更新文档中的特定字段(field), 其他的字段不会改动.
POST index/type/id/_update
{
"doc": {
"field u want to update": "new value of ur update's field"
}
}
注意: 与替换方式相比, 更新方式的好处: 可以更新指定文档的指定field, 未指定的field也不会丢失.
(2) 更新示例 - 将name改为英文:
POST book_shop/it_book/1/_update
{
"doc": {
"name": "Thinking in Java(4th Edition) "
}
}
(3) 更新的结果信息:
{
"_index" : "book_shop",
"_type" : "it_book",
"_id" : "1",
"_version" : 3,
"result" : "updated", // 操作结果: updated(修改)了索引
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}
此时查看该文档 GET book_shop/it_book/1, 可以发现更新成功.
- 6 删除文档
(1) 删除API:
DELETE index/type/id
(2) 删除示例:
DELETE book_shop/it_book/1
(3) 删除的结果信息:
{
"_index" : "book_shop",
"_type" : "it_book",
"_id" : "1",
"_version" : 4,
"result" : "deleted", // 操作结果: deleted(删除)了索引
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 3,
"_primary_term" : 1
}
(4) 删除确认: 再次查看删除的文档:
GET book_shop/it_book/1
(5) 确认的信息:
{
"_index" : "book_shop",
"_type" : "it_book",
"_id" : "1",
"found" : false // 没有查找到相关文档
}
为了后期演示的方便, 再次将该文档添加至索引中:
PUT book_shop/it_book/1
{
"name": "Java编程思想(第4版)",
"author": "[美] Bruce Eckel",
"category": "编程语言",
"desc": "Java学习必读经典,殿堂级著作!",
"price": 109.0,
"date": "2007-06-01",
"publisher": "机械工业出版社",
"tags": [ "Java", "编程语言" ]
}
(七) Elasticsearch查询索引文档的6种方法
本文的六种查询方法, 只是一个简单的入门, 详细使用方法会在后续的学习中逐一演示.
- Coordinating Node(协调节点): 客户端随机选择一个Node用来发送操作请求, 这个节点就称为协调节点.
由于每个Node都能计算出Document的存储位置, 所以由哪个Node担任协调节点都是可以的——这对客户端来说是透明的.
① 客户端通过协调节点发送 查询请求.
② 协调节点对客户端提交的文档进行路由, 明确存储相关文档的Primary Shard(主分片), 然后使用Round-Robin算法(随机轮训算法), 将查询请求转发到 该Primary Shard及这个主分片对应的任意一个Replica Shard(副本分片) —— 读请求的负载均衡.
③ 接收到查询请求的Shard执行该请求, 然后将查询结果响应给协调节点.
④ 协调节点将查询结果响应给客户端.
- 1 Query String Search(查询串检索)
生产环境中很少使用, 因为请求参数都封装到Query String中, 难以构建复杂的查询.
# 检索name中包含Java的文档, 并按价格降序排序:
curl -XGET 'http://localhost:9301/book_shop/it_book/_search?q=name:Java&sort=price:desc'
(1) 查询全部商品:
直接在浏览器的URL地址栏内输入搜索参数:
http://172.16.22.133:9301/book_shop/it_book/_search?q=name:Java
(2) 查询的结果:
{
"took": 8,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 1,
"hits": [
{
"_index": "book_shop",
"_type": "it_book",
"_id": "2",
"_score": 1,
"_source": {
"name": "深入理解Java虚拟机:JVM高级特性与最佳实践",
"author": "周志明",
"category": "编程语言",
"desc": "Java图书领域公认的经典著作",
"price": 79,
"date": "2013-10-01",
"publisher": "机械工业出版社",
"tags": [
"Java",
"虚拟机",
"最佳实践"
]
}
},
// 省略另外两条记录
]
}
}
(3) 查询结果中的各个参数的含义:
① took: 此次检索耗费的时间, 单位是毫秒;
② timed_out: 是否超出规定的检索时间, 这里没有设置, 后续会讲解此参数;
③ _shards: 被查询的index被分散成多个分片, 所以搜索请求会分发到所有的primary shard(或primary shard对应的某个replica shard)上, 这里显示各个分片是否查询成功的信息;
④ hits: 命中的文档情况, 有如下参数:
total: 符合条件的文档总数, 即hit(命中)数;
max_score: Lucene底层对检索到的文档的相关度的评分, 相关度越高, 说明越匹配, score的值也就越高.
hits: 命中的所有document的详细数据.
- 2 Query DSL(ES特定语法检索)
DSL: Domain Specified Language, 特定领域的语言, 一般需要Kibana等工具配合操作.
这种方式把查询参数构建成JSON格式的数据, 并封装到HTTP请求的Request Body(请求体)中, 可以构建各类复杂的查询语法, 功能要比Query String Search强大很多.
(1) 查询全部商品:
GET book_shop/it_book/_search
{
"query": { "match_all": {} }
}
(2) 查询name中包含Java的商品, 并按price降序排序:
GET book_shop/it_book/_search
{
"query": {
"match": {
"name": "Java"
}
},
"sort": [
{ "price": "desc" }
]
}
(3) 分页查询商品 - 每页显示1条, 显示第3页:
GET book_shop/it_book/_search
{
"query": { "match_all": {} },
"from": 2,
"size": 1
}
(4) 只查询商品的名称和价格:
GET book_shop/it_book/_search
{
"query": {"match_all": {}},
"_source": ["name", "price"]
}
—— 上述各类语法可以组合使用, 具体使用方法后续会陆续介绍.
- 3 Query Filter(过滤检索)
过滤查询, 比如: 查询name中包含Java, 且price不大于80元的商品:
GET book_shop/it_book/_search
{
"query": {
"bool": {
"must": {
"match": {"name": "Java"} // name中含有Java
},
"filter": {
"range": {
"price": {"lte": 80.0} // 价格不大于80.0
}
}
}
}
}
- 4 Full Text Search(全文检索)
(1) 查询描述信息desc中包含"Java图书"的文档, 只显示name和desc的值:
GET book_shop/it_book/_search
{
"query": {
"match": {"desc": "Java图书"}
},
"_source": ["name", "desc"]
}
(2) 查询结果中有2条数据符合要求:
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 0.8630463,
"hits" : [
{
"_index" : "book_shop",
"_type" : "it_book",
"_id" : "2",
"_score" : 0.8630463,
"_source" : {
"name" : "深入理解Java虚拟机:JVM高级特性与最佳实践",
"desc" : "Java图书领域公认的经典著作" // desc中有"Java"和"图书"
}
},
{
"_index" : "book_shop",
"_type" : "it_book",
"_id" : "1",
"_score" : 0.2876821,
"_source" : {
"name" : "Java编程思想(第4版)",
"desc" : "Java学习必读经典,殿堂级著作!" // desc中有"Java"
}
}
]
}
}
(3) 全文检索的过程 —— 对查询结果的说明:
Elasticsearch会对字段"desc"的内容进行分词, 并建立倒排索引.
也就是说, 这里会把 “Java图书” 分词为 “Java”、“图”、“书” 3个, 检索时将匹配desc中含有 “Java”、“图”、“书” 中任意一个分词的文档.
—— 对于中文分词, 可以通过IK分词器, 把"Java图书"分解为"Java"、“图书” 2个词, 参考博主的文章:ES XX - Elasticsearch中使用IK中文分词器.
- 5 Phrase Search(短语检索)
Full Text Search会对检索文本作分词处理, 然后从倒排索引中作匹配查询, 如果一个文档的对应field中存在任意一个分解后的词, 那么这个文档就算匹配检索条件.
Phrase Search不会对检索串进行分词处理, 只有一个文档的对应field中包含与检索文本完全一致的内容, 该文档才算匹配检索条件, 也才能作为结果返回 —— 可以理解为全文检索场景下的部分精确匹配.
(1) 精确查询desc中包含"Java图书"的文档:
GET book_shop/it_book/_search
{
"query": {
"match_phrase": {
"desc": "Java图书"
}
},
"_source": ["name", "desc"]
}
(2) 查询结果只有一条数据符合要求了:
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 0.8630463,
"hits" : [
{
"_index" : "book_shop",
"_type" : "it_book",
"_id" : "2",
"_score" : 0.8630463,
"_source" : {
"name" : "深入理解Java虚拟机:JVM高级特性与最佳实践",
"desc" : "Java图书领域公认的经典著作" // desc中精确含有"Java图书"
}
}
]
}
}
- 6 Highlight Search(高亮检索)
(1) 分页查询desc中包含"Java图书"的文档, 页大小为1, 显示第1页, 并对搜索条件高亮处理:
GET book_shop/it_book/_search
{
"query": {
"match": {"desc": "Java图书"}
},
"from": 0,
"size": 1,
"highlight": {
"fields": {"desc": {}}
},
"_source": ["name", "desc"]
}
(2) 查询结果:
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 0.8630463,
"hits" : [
{
"_index" : "book_shop1",
"_type" : "it_book",
"_id" : "2",
"_score" : 0.8630463,
"_source" : {
"name" : "深入理解Java虚拟机:JVM高级特性与最佳实践",
"desc" : "Java图书领域公认的经典著作"
},
"highlight" : { // 高亮显示, 默认添加<em>标签
"desc" : [
"<em>Java</em><em>图</em><em>书</em>领域公认的经典著作"
]
}
}
]
}
}
从上述结果的"Java图书也可以看出, ES底层对desc字段的值"Java图书"进行了分词处理:
(八)增删改查、打开、关闭Elasticsearch的索引
Elasticsearch中的index相当于RDBMS(关系型数据库, 比如MySQL)中的DataBase.
本篇文章通过Kibana插件, 演示了ES的基础语法: 对ES中的index进行CRUD(增删改查)以及关闭、开启操作.
- 1 创建index(配置mapping[映射])
(1) 创建语法:
PUT index
{
"settings": { ... some settings ... },
"mappings": {
// ES 6.x开始不再支持1个index中同时存在多个type
"type1": { ... some mappings ... },
"type2": { ... some mappings ... },
...
}
}
如果不指定settings和mappings, 直接插入数据时, ES会根据要插入数据的类型, 自动创建相关配置 —— 功能强大, 但扩展性不够, 后续若有其他原因需要修改mappings, 会很困难.
—— 所以创建index时, 推荐手动指定settings和mappings的相关配置. 可以参考文章: ES XX - ES的mapping的设置.
(2) 创建示例:
PUT address // 关于地址(address)的索引
{
"settings": {
"number_of_shards": 1, // 默认分片数为5
"number_of_replicas": 0 // 默认副本数为1
},
"mappings": {
"province": { // type是province(省份)
"properties": {
"name": {
"type": "text" // 存储类型是text
},
"area": {
"type": "float"
}
}
}
}
}
(3) 创建结果:
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "address"
}
- 2 查看index
(1) 查看示例:
GET address
// 也可同时查看多个索引, 类似于删除操作:
GET *
GET _all
GET *index*
GET address,shop
// 在6.0之前的版本中, 还可以指定返回某些指定项的结果:
GET address/_settings,_mappings
- 弃用提示:
查看索引时, 若通过","分隔要返回的结果, Elasticsearch将抛出如下警告:
#! Deprecation: Requesting comma-separated features is deprecated and will be removed in 6.0+, retrieve all features instead.
意为: Elasticsearch不推荐使用逗号分隔功能, 将在6.0+中删除. 建议不要使用",", 而是直接检索全部数据, 或检索某一项的结果.
在ES 6.6.0中将直接出现:
{
"error": "Incorrect HTTP method for uri [/address/_settings,_mappings?pretty] and method [GET], allowed: [POST]",
"status": 405
}
换做POST请求时, 必须携带请求体, 仍然不支持.
(2) 查看的结果:
{
"address": {
"aliases": {},
"mappings": {
"province": {
"properties": {
"area" : {
"type" : "float"
},
"name" : {
"type" : "text"
}
}
}
},
"settings": {
"index": {
"creation_date": "1542108754899",
"number_of_shards": "1",
"number_of_replicas": "0",
"uuid": "MMpLNHzZR8K1k48rJplWVw",
"version": {
"created": "6060099"
},
"provided_name": "address"
}
}
}
}
- 3 修改index
修改索引的示例:
PUT address/_settings
{
"number_of_replicas": 1 // 修改副本数为1
}
说明: Elasticsearch中的分片数(number_of_shards)只能在创建索引时设置, 无论是否添加过数据, 都不支持修改.
这与文档的路由有关, 而Solr的SPLITSHARD可以算作动态修改分片的另一种思路: 只对某一路由范围内的进行拆分, 可以参考 管理SolrCloud集群 (创建集合、切割分片、更新配置) 第5节的内容.
关于修改ES的分片数, 应该有其他思路, 后期了解到再作研究整理.
- 4 删除index
删除索引需要指明索引名称、别名或通配符.
Elasticsearch支持同时删除多个索引, 或使用_all或通配符*删除全部索引.
删除示例:
DELETE address // 删除指定索引
DELETE index1,index2 // 删除多个索引
DELETE index_* // 按通配符删除以'index_'开头的索引
DELETE _all // 删除全部索引
为避免_all操作误删除全部索引, 可在配置文件elasticsearch.yml中作如下配置:
# 要求操作索引时必须指定索引的名称
action.destructive_requires_name: true
- 5 打开/关闭index
(1) 操作说明:
① 可以打开一个已经打开/关闭的索引, 以最后一次操作为准;
② 可以关闭一个已经关闭/打开的索引, 以最后一次操作为准;
③ 关闭的索引只能查看index的配置信息, 不能对内部的索引数据进行读写操作.
(2) 操作示例:
// 可以使用_all打开或关闭全部索引, 也可使用通配符(*)配合操作
POST address/_close
POST address/_open
说明事项:
① 使用_all或通配符操作索引, 都会受到配置文件中action.destructive_requires_name=true的限制.
② 关闭的索引会继续占用磁盘空间, 却又不能使用 —— 造成磁盘空间的浪费.
③ 可以在配置文件中禁止使用关闭索引的功能: settingscluster.indices.close.enable=false, 默认为true(开启).
- 6 常见问题及解决方法
(1) 查看不存在的索引时, 将抛出如下错误信息:
如果要查看的索引不存在, 比如GET addre, 就会抛出类似下面的异常信息:
{
"error" : {
"root_cause" : [
{
"type" : "index_not_found_exception",
"reason" : "no such index",
"resource.type" : "index_or_alias",
"resource.id" : "addre",
"index_uuid" : "_na_",
"index" : "addre"
}
],
"type" : "index_not_found_exception",
"reason" : "no such index",
"resource.type" : "index_or_alias",
"resource.id" : "addre",
"index_uuid" : "_na_",
"index" : "addre"
},
"status" : 404
}
(2) 在6.0之前的版本中, 如果修改已经关闭了的索引, 会抛出类似于下面的错误:
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "Can't update [index.number_of_replicas] on closed indices [[address/MMpLNHzZR8K1k48rJplWVw]] - can leave index in an unopenable state"
}
],
"type": "illegal_argument_exception",
"reason": "Can't update [index.number_of_replicas] on closed indices [[address/MMpLNHzZR8K1k48rJplWVw]] - can leave index in an unopenable state"
},
"status": 400
}
在本篇博客演示所用的Elasticsearch 6.6.0版本中, 并不存在此异常信息.
(九)Elasticsearch如何定制分词器 (自定义分词策略)
Elasticsearch 分词器
Redis集群——SpringBoot连接Redis集群(带密码)
-
1 索引的分析
索引分析: 就是把输入的文本块按照一定的策略进行分解, 并建立倒排索引的过程. 在Lucene的架构中, 这个过程由分析器(analyzer)完成.- 1.1 分析器的组成
- 字符过滤器(character filter): 比如去除HTML标签、把&替换为and等.
- 分词器(tokenizer): 按照某种规律, 如根据空格、逗号等, 将文本块进行分解.
- 标记过滤器(token filter): 所有被分词器分解的词都将经过token filters的处理, 它可以修改词(如小写化处理)、去掉词(根据某一规则去掉无 意义的词, 如"a", “the”, “的"等), 增加词(如同义词"jump”、"leap"等).
- 1.1 分析器的组成
注意: 人们一般将分析器通称为分词器, 并不是相等的关系, 而是包含的关系.
- 1.2 倒排索引的核心原理-normalization
建立倒排索引时, 会执行normalization(正常化)操作 —— 将拆分的各个单词进行处理, 以提高搜索时命中关联的文档的概率.
normalization的方式有: 时态转换, 单复数转换, 同义词转换, 大小写转换等.
比如文档中包含His mom likes small dogs:
- 在建立索引的时候normalization会对文档进行时态、单复数、同义词等方面的处理;
- 然后用户通过近似的mother liked little dog, 也能搜索到相关的文档.
- 2 ES的默认分词器
(1) ES中的默认分词器: standard tokenizer, 是标准分词器, 它以单词为边界进行分词. 具有如下功能:
- standard token filter: 去掉无意义的标签, 如<>, &, - 等.
- lowercase token filter: 将所有字母转换为小写字母.
- stop token filer(默认被禁用): 移除停用词, 比如"a"、"the"等.
(2) 测试默认分词器:
GET _analyze // ES引擎中已有standard分词器, 所以可以不指定index
{
"analyzer": "standard",
"text": "There-is & a DOG<br/> in house"
}
可以发现, Elasticsearch对text文本进行了分析处理, 结果如下:
{
"tokens" : [
{
"token" : "there", // 分词
"start_offset" : 0, // 起始偏移量
"end_offset" : 5, // 结束偏移量
"type" : "<ALPHANUM>", // 分词的类型
"position" : 0 // 该分词在文本中的位置
},
{
"token" : "is",
"start_offset" : 6,
"end_offset" : 8,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "a",
"start_offset" : 11,
"end_offset" : 12,
"type" : "<ALPHANUM>",
"position" : 2
},
// 省略其他4项
]
}
- 3 修改分词器
(1) 创建索引后可以添加新的分词器:
说明: 必须先关闭索引, 添加完成后, 再及时打开索引进行搜索等操作, 否则将出现错误.
// 关闭索引:
POST address/_close
// 启用English停用词token filter
PUT address/_settings
{
"analysis": {
"analyzer": {
"my_token_filter": { // 自定义的分词器名称
"type": "standard",
"stopwords": "_english_"
}
}
}
}
// 打开索引:
POST address/_open
// 关闭索引:
POST address/_close
// 启用English停用词token filter
PUT address/_settings
{
"analysis": {
"analyzer": {
"my_token_filter": { // 自定义的分词器名称
"tokenizer": "standard",
"filter": [
"lowercase"
]
}
}
}
}
// 打开索引:
POST address/_open
// 关闭索引:
POST address/_close
// 启用English停用词token filter
PUT address
{
"settings":{
"analysis": {
"analyzer": {
"std_english": {
"type": "standard",
"stopwords": "_english_"
}
}
}
},
"mappings": {
"_doc": {
"properties": {
"my_text": {
"type": "text",
"analyzer": "standard",
"fields": {
"english": {
"type": "text",
"analyzer": "std_english"
}
}
}
}
}
}
}
// 打开索引:
POST address/_open
(2) 使用具有停词功能的分词器进行分词:
GET address/_analyze // 指定索引
{
"analyzer": "my_token_filter", // 指定要使用的分词器
"text": "There-is & a DOG<br/> in house"
}
(3) 返回结果减少了停用词there, is, &, a, in等:
{
"tokens" : [
{
"token" : "dog",
"start_offset" : 13,
"end_offset" : 16,
"type" : "<ALPHANUM>",
"position" : 3
},
{
"token" : "br",
"start_offset" : 17,
"end_offset" : 19,
"type" : "<ALPHANUM>",
"position" : 4
},
{
"token" : "house",
"start_offset" : 25,
"end_offset" : 30,
"type" : "<ALPHANUM>",
"position" : 6
}
]
}
- 4 定制分词器
4.1 向索引中添加自定义的分词器
同样的, 在添加新的分词器之前, 必须先关闭索引, 添加完成后, 再打开索引进行搜索等操作.
PUT address/_settings
{
"analysis": {
"char_filter": {
"&_to_and": {
"type": "mapping",
"mappings": ["& => and"]
}
},
"filter": {
"my_stopwords": {
"type": "stop",
"stopwords": ["the", "a"]
}
},
"analyzer": {
"my_analyzer": { // 自定义的分析器名称
"type": "custom",
"char_filter": ["html_strip", "&_to_and"], // 跳过HTML标签, 将&符号转换为"and"
"tokenizer": "standard",
"filter": ["lowercase", "my_stopwords"] // 转换为小写
}
}
}
}
4.2 测试自定义分析器
GET address/_analyze
{
"analyzer": "my_analyzer", // 上面定义的分析器名称
"text": "There-is & a DOG<br/> in house"
}
可以发现, 返回的分析结果中已经对大写单词、HTML标签, 以及"&"做了处理.
{
"tokens" : [
// there和is
{
"token" : "and", // &被处理成了and
"start_offset" : 9,
"end_offset" : 10,
"type" : "<ALPHANUM>",
"position" : 2
},
// dog、in和house
]
}
4.3 向映射中添加自定义的分词器
PUT address/_mapping/province
{
"properties": {
"content": {
"type": "text",
"analyzer": "my_analyzer"
}
}
}
此时查看mapping信息:
GET address/_mapping
发现自定义的分析器已经配置到province上了:
{
"address": {
"mappings": {
"province": {
"properties": {
"area" : {
"type" : "float"
},
"content" : {
"type" : "text",
"analyzer" : "my_analyzer"
},
"name" : {
"type" : "text"
}
}
}
}
}
}
- 5 常见问题
在修改索引之前, 没有关闭索引, 修改时发生如下错误:
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "Can't update non dynamic settings [[index.analysis.analyzer.my_token_filter.type, index.analysis.analyzer.my_token_filter.stopwords]] for open indices [[address/Ci6MJV4sTyuoF4r9aLvVZg]]"
}
],
"type": "illegal_argument_exception",
"reason": "Can't update non dynamic settings [[index.analysis.analyzer.my_token_filter.type, index.analysis.analyzer.my_token_filter.stopwords]] for open indices [[address/Ci6MJV4sTyuoF4r9aLvVZg]]"
},
"status": 400
}
(十)如何使用Elasticsearch的索引模板(index template)
- 1 什么是索引模板
索引模板: 就是把已经创建好的某个索引的参数设置(settings)和索引映射(mapping)保存下来作为模板, 在创建新索引时, 指定要使用的模板名, 就可以直接重用已经定义好的模板中的设置和映射.
1.1 索引模板中的内容
-
settings: 指定index的配置信息, 比如分片数、副本数, tranlog同步条件、refresh策略等信息;
-
mappings: 指定index的内部构建信息, 主要有:
① _all: All Field字段, 如果开启, _all字段就会把所有字段的内容都包含进来,检索的时候可以不用指定字段查询 —— 会检索多个字段, 设置方式: “_all”: {“enabled”: true};
在ES 6.0开始, _all字段被禁用了, 作为替换, 可以通过copy_to自定义实现all字段的功能.
② _source: Source Field字段, ES为每个文档都保存一份源数据, 如果不开启, 也就是"_source": {“enabled”: false}, 查询的时候就只会返回文档的ID, 其他的文档内容需要通过Fields字段到索引中再次获取, 效率很低. 但若开启, 索引的体积会更大, 此时就可以通过Compress进行压缩, 并通过inclueds、excludes等方式在field上进行限制 —— 指定义允许哪些字段存储到_source中, 哪些不存储;
③ properties: 最重要的配置, 是对索引结构和文档字段的设置.
-
1.2 索引模板的用途
-
索引模板一般用在时间序列相关的索引中.
也就是说, 如果你需要每间隔一定的时间就建立一次索引, 你只需要配置好索引模板, 以后就可以直接使用这个模板中的设置, 不用每次都设置settings和mappings.
索引模板一般与索引别名一起使用. 关于索引别名, 后续研究之后再做补充.
- 2 创建索引模板
创建一个商品的索引模板的示例:
(1) ES 6.0之前的版本:
PUT _template/shop_template
{
"template": "shop*", // 可以通过"shop*"来适配
"order": 0, // 模板的权重, 多个模板的时候优先匹配用, 值越大, 权重越高
"settings": {
"number_of_shards": 1 // 分片数量, 可以定义其他配置项
},
"aliases": {
"alias_1": {} // 索引对应的别名
},
"mappings": {
"_default": { // 默认的配置, ES 6.0开始不再支持
"_source": { "enabled": false }, // 是否保存字段的原始值
"_all": { "enabled": false }, // 禁用_all字段
"dynamic": "strict" // 只用定义的字段, 关闭默认的自动类型推断
},
"type1": { // 默认的文档类型设置为type1, ES 6.0开始只支持一种type, 所以这里不需要指出
*/
"_source": {"enabled": false},
"properties": { // 字段的映射
"@timestamp": { // 具体的字段映射
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"@version": {
"doc_values": true,
"index": "not_analyzed", // 不索引
"type": "string" // string类型
},
"logLevel": {
"type": "long"
}
}
}
}
}
(2) ES 6.0之后的版本:
PUT _template/shop_template
{
"index_patterns": ["shop*", "bar*"], // 可以通过"shop*"和"bar*"来适配, template字段已过期
"order": 0, // 模板的权重, 多个模板的时候优先匹配用, 值越大, 权重越高
"settings": {
"number_of_shards": 1 // 分片数量, 可以定义其他配置项
},
"aliases": {
"alias_1": {} // 索引对应的别名
},
"mappings": {
// ES 6.0开始只支持一种type, 名称为“_doc”
"_doc": {
"_source": { // 是否保存字段的原始值
"enabled": false
},
"properties": { // 字段的映射
"@timestamp": { // 具体的字段映射
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"@version": {
"doc_values": true,
"index": "false", // 设置为false, 不索引
"type": "text" // text类型
},
"logLevel": {
"type": "long"
}
}
}
}
}
说明:
直接修改mapping的优先级 > 索引模板中的设置;
索引匹配了多个template, 当属性等配置出现不一致时, 以模板的权重(order属性的值)为准, 值越大越优先, order的默认值是0.
ES 6.0之后的版本API变化较大, 请重点关注.
- 3 查看索引模板
(1) 查看示例:
GET _template // 查看所有模板
GET _template/temp* // 查看与通配符相匹配的模板
GET _template/temp1,temp2 // 查看多个模板
GET _template/shop_template // 查看指定模板
(2) 判断模板是否存在:
判断示例:
HEAD _template/shop_tem
结果说明:
a) 如果存在, 响应结果是: 200 - OK
b) 如果不存在, 响应结果是: 404 - Not Found
- 4 删除索引模板
删除示例:
DELETE _template/shop_template // 删除上述创建的模板
如果模板不存在, 将抛出如下错误:
{
"error" : {
"root_cause" : [
{
"type" : "index_template_missing_exception",
"reason" : "index_template [shop_temp] missing"
}
],
"type" : "index_template_missing_exception",
"reason" : "index_template [shop_temp] missing"
},
"status" : 404
}
-
5 模板的使用建议
-
5.1 一个index中不能有多个type
—— Elasticsearch 6.X版本中已经不支持在同一个index下创建多个type.
6.X之前的版本中, 父子文档的实现是一个索引中定义多个type, 6.X中实现方式改变为join方式.
如果在同一个index下创建多个type, 会报出如下错误信息:
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "Rejecting mapping update to [book_shop] as the final mapping would have more than 1 type: [books, novel_book]"
}
],
"type": "illegal_argument_exception",
"reason": "Rejecting mapping update to [book_shop] as the final mapping would have more than 1 type: [books, novel_book]"
},
"status": 400
}
错误信息指出: 拒绝对[book_shop]的mapping信息进行修改, 因为它作为final mapping(终态的mapping), 将会有超过2个type.
- 5.2 设置_source = false
如果我们只关心查询的评分结果, 而不用查看原始文档的内容, 就设置"_source": {“enabled”: false}.
—— 这能节省磁盘空间并减少磁盘IO上的开销.
我们可以把原始的数据存储在MySQL、HBase等数据库, 从ES中得到文档的ID之后, 再到相应的数据库中获取数据.
- 5.3 设置_all = false
如果能够确切地知道要对哪个field做查询操作, 就设置"_all": {“enabled”: false}.
—— 这能实现性能提升, 并节省存储空间.
而在6.X版本开始, _all字段也不再支持了, ES官方建议我们通过copy_to自定义我们自己的all字段.
- 5.4 设置dynamic = strict
如果我们的数据是结构化数据, 就设置"dynamic": “strict”.
—— 把动态类型判断设置为严格, 也就是不允许ES为插入的数据进行动态类型设置, 避免注入脏数据.
- 5.5 使用keyword类型
如果我们只关心精确匹配, 就设置test_field: {“type”: “keyword”}.
—— keyword类型要比text类型的性能更高, 并且还能节省磁盘的存储空间.
(十一)ES配置Elasticsearch的映射 (mapping) 目录
-
1 映射的相关概念
-
1.1 什么是映射
(1) 映射(mapping): 定义index的元数据, 指定要索引并存储的文档的字段类型.
也就是说映射决定了Elasticsearch在建立倒排索引、进行检索时对文档采取的相关策略, 如数字类型、日期类型、文本类型等等.
需要注意的是: 检索时用到的分析策略, 要和建立索引时的分析策略相同, 否则将导致数据不准确.
(2) ES对不同的类型有不同的存储和检索策略
① 比如: 对full text型的数据类型(如text), 在索引时, 会经过各类处理 (包括分词、normalization(时态转换、同义词转换、大小写转换)等处理), 才会建立到索引数据中.
② 再比如: 对exact value(如date), 在索引的分词阶段, 会将整个value作为一个关键词建立到倒排索引中.
- 1.2 映射的组成
每个index都有一 (至多) 个type, 每个type对应一个mapping.
在Elasticsearch 6.X版本开始, 1个index只能有1个type.
每个mapping都由下述部分组成:
① 元字段: _index、_type、_id 和 _source.
② field/properties(字段或属性): 同一index中, 同名的field的映射配置必须相同
a) 因为index是根据_type元字段来区分type的, 也就是存储的每个文档中都有_type等元字段, 如果相同名称的field的映射(_type字段的值)不同, Elasticsearch在解析时就会出现冲突.
b) 这些参数可以例外: copy_to、dynamic、enabled、ignore_above、include_in_all.
关于type的处理方法, 可以参考博客: ES XX - Elasticsearch对type的处理(type的底层结构).
- 1.3 元字段
每个文档都有与之关联的元数据 —— ES内部为所有的文档配备的field, 都是以下划线_开头的内置字段.
具体的内容请参考博文 ES XX - Elasticsearch的元字段 中详细讲解.
-
1.4 字段的类型
Elasticsearch中每个field都对应一至多个数据类型.
详细的内容请参考博文 ES XX - Elasticsearch中字段的类型 中详细讲解. -
2 如何配置mapping
-
2.1 创建mapping
(1) 必读说明:
① 创建mapping时, 可以指定每个field是否需要:
索 引: “index”: true —— 默认配置
不索引: “index”: false
② mapping root object:
每个type对应的mapping的JSON串, 包括properties, metadata(_id, _source, _type) , settings(analyzer) , 其他settings(如include_in_all)
(2) 创建mapping的示例:
需求: 创建名为website的索引, 包含一个user类型. user类型中禁用元字段_all.
PUT website
{
"mappings": {
"user": { // 这就是一个root object
"_all": { "enabled": false }, // 禁用_all字段
"properties": {
"user_id": { "type": "text" },
"name": {
"type": "text",
"analyzer": "english"
},
"age": { "type": "integer" },
"sex": { "type": "keyword" },
"birthday": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"address": {
"type": "text",
"index": false // 不分词
}
}
}
}
}
(3) 过期提示说明 —— 这里使用的是Elasticsearch 6.6.0版本:
① 是否索引的API已经做了修改, 若使用"analyzed" | “not_analyzed” | “yes” | "no"等, 将抛出如下警告:
#! Deprecation: Expected a boolean [true/false] for property [index] but got [not_analyzed]
#! Deprecation: Expected a boolean [true/false] for property [index] but got [no]
② _all元字段也将在7.0版本中移除, 它建议我们使用copy_to定制自己的all field:
#! Deprecation: [_all] is deprecated in 6.0+ and will be removed in 7.0. As a replacement, you can use [copy_to] on mapping fields to create your own catch all field.
- 2.2 更新mapping
(1) 必读说明:
映射一旦创建完成, 就不允许修改:
—— Elasticsearch对文档的分析、存储、检索等过程, 都是严格按照mapping中的配置进行的. 如果允许后期修改mapping, 在检索时对索引的处理将存在不一致的情况, 导致数据检索行为不准确.
只能在创建index的时候手动配置mapping, 或者新增field mapping, 但是不能update field mapping.
(2) 更新mapping出现异常:
修改已经创建好的mapping
PUT website
{
"mappings": {
"user": {
"properties": {
"author_id": { "type": "text" }
}
}
}
}
抛出如下错误 —— 索引已经存在的异常:
{
"error": {
"root_cause": [
{
"type": "resource_already_exists_exception",
"reason": "index [website/mVYk4-a7RMOZbkcCp2avfw] already exists",
"index_uuid": "mVYk4-a7RMOZbkcCp2avfw",
"index": "website"
}
],
"type": "resource_already_exists_exception",
"reason": "index [website/mVYk4-a7RMOZbkcCp2avfw] already exists",
"index_uuid": "mVYk4-a7RMOZbkcCp2avfw",
"index": "website"
},
"status": 400
}
(3) 向已有mapping中添加字段及其映射信息:
PUT website/_mapping/user // 修改user类型的_mapping, 注意API的顺序
{
"properties": {
"new_field": {
"type": "text",
"index": false
}
}
}
- 2.3 查看mapping
(1) 查看mapping的API:
GET website/_mapping
(2) 查看的结果信息如下:
{
"website" : {
"mappings" : {
"user" : {
"_all" : {
"enabled" : false // 禁用元字段_all
},
"properties" : {
"address" : {
"type" : "text",
"index" : false // 不索引
},
"age" : {
"type" : "integer"
},
"birthday" : {
"type" : "date"
},
"name" : {
"type" : "text",
"analyzer" : "english"
},
"new_field" : { // 后期添加的新字段
"type" : "text",
"index" : false // 不索引
},
"sex" : {
"type" : "keyword"
},
"user_id" : {
"type" : "text"
}
}
}
}
}
}
(十二)如何配置使用Elasticsearch的动态映射 (dynamic mapping)
-
1 动态映射(dynamic mapping)
-
1.1 什么是动态映射
动态映射时Elasticsearch的一个重要特性: 不需要提前创建iindex、定义mapping信息和type类型, 你可以 直接向ES中插入文档数据时, ES会根据每个新field可能的数据类型, 自动为其配置type等mapping信息, 这个过程就是动态映射(dynamic mapping).
Elasticsearch动态映射的示例:
字段内容(field) 映射的字段类型(type)
true | false boolean
1234 long
123.4 float
2018-10-10 date
"hello world" text
说明: 动态映射虽然方便, 可并不直观, 为了个性化自定义相关设置, 可以在添加文档之前, 先创建index和type, 并配置type对应的mapping, 以取代动态映射.
mapping的配置可参考博文: ES 11 - 如何配置Elasticsearch的映射 (mapping).
- 1.2 体验动态映射
(1) 插入如下数据:
如果已有website索引, 先删除DELETE blog , 再执行下面的插入操作: .
PUT blog/_doc/1
{
"blog_id": 10001,
"author_id": 5520,
"post_date": "2018-01-01",
"title": "my first blog",
"content": "my first blog in the website"
}
PUT blog/_doc/2
{
"blog_id": 10002,
"author_id": 5520,
"post_date": "2018-01-02",
"title": "my second blog",
"content": "my second blog in the website"
}
PUT blog/_doc/3
{
"blog_id": 10003,
"author_id": 5520,
"post_date": "2018-01-03",
"title": "my third blog",
"content": "my third blog in the website"
}
(2) 进行如下检索测试:
注意这里结果数是3的情况.
GET blog/_search?q=2018 // 1条结果, 5.6之前的版本是3条结果
GET blog/_search?q=2018-01-01 // 1条结果, 5.6之前的版本是3条结果
GET blog/_search?q=post_date:2018 // 1条结果
GET blog/_search?q=post_date:2018-01-01 // 1条结果
(3) 查看ES自动建立的mapping:
GET blog/_mapping
// 结果如下:
{
"blog" : { // index是blog
"mappings" : {
"_doc" : { // type是_doc - 方便后期升级版本
"properties" : {
"author_id" : {
"type" : "long"
},
"blog_id" : {
"type" : "long"
},
"content" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"post_date" : {
"type" : "date" // 日期"2018-01-01"被自动识别为date类型
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}
- 2 开启dynamic mapping动态映射策略
- 2.1 约束策略
策略 功能说明
true 开启 —— 遇到陌生字段时, 进行动态映射
false 关闭 —— 忽略遇到的陌生字段
strict 遇到陌生字段时, 作报错处理
- 2.2 策略示例
(1) 使用不同的约束策略:
PUT blog_user
{
"mappings": {
"_doc": {
"dynamic": "strict", // 严格控制策略
"properties": {
"name": { "type": "text" },
"address": {
"type": "object",
"dynamic": "true" // 开启动态映射策略
}
}
}
}
}
(2) 插入数据演示:
// 插入数据时多添加一个字段
PUT blog_user/1
{
"name": "shou feng",
"content": "this is my blog", // 多添加的字段
"address": {
"province": "guangdong",
"city": "guangzhou"
}
}
将抛出如下错误信息:
{
"error": {
"root_cause": [
{
"type": "strict_dynamic_mapping_exception",
// 错误原因: 不允许动态添加field
"reason": "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed"
}
],
"type": "strict_dynamic_mapping_exception",
"reason": "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed"
},
"status": 400
}
添加符合约束的数据, 操作就能成功:
PUT blog_user/_doc/1
{
"name": "shou feng",
"address": {
"province": "guangdong",
"city": "guangzhou"
}
}
(3) 查看映射信息:
GET user/_mapping
// 映射信息如下:
{
"blog_user" : {
"mappings" : {
"_doc" : {
"dynamic" : "strict", // 严格约束条件
"properties" : {
"address" : {
"dynamic" : "true", // 开启动态映射策略
"properties" : {
"city" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"province" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"name" : {
"type" : "text"
}
}
}
}
}
}
- 3 定制dynamic mapping策略
- 3.1 date_detection - 日期识别策略
对于date类型的数据, Elasticsearch有其默认的识别策略, 比如"yyyy-MM-dd". 存在这种情况:
① 第一次添加文档时, 某个field是类似"2018-01-01"的值, 其类型就动态映射成date;
② 后期再次添加文档, 该field是类似"hello world"的值, ES就会因为类型不匹配而报错.
为解决这一问题, 可以手动关闭某个type的date_detection; 如果不需要关闭, 建议手动指定这个field为date类型. 示例如下:
PUT blog_user/_mapping/_doc
{
"date_detection": false
}
- 3.2 在type中自定义动态映射模板
(1) 在type中定义动态映射模板(dynamic mapping template) —— 把所有的String类型映射成text和keyword类型:
先删除已有的blog_user索引: DELETE blog_user, 再执行下述命令:
PUT blog_user
{
"mappings": {
"_doc": {
"dynamic_templates": [
{
"en": { // 动态模板的名称
"match": "*_en", // 匹配名为"*_en"的field
"match_mapping_type": "string",
"mapping": {
"type": "text", // 把所有的string类型, 映射成text类型
"analyzer": "english", // 使用english分词器
"fields": {
"raw": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
]
}
}
}
(2) 添加数据:
PUT blog_user/_doc/1
{
"name": "the first register user"
}
PUT blog_user/_doc/2
{
"name_en": "the second register user"
}
(3) 检索数据:
// 有结果: "name"没有匹配到任何动态模板, 默认使用standard分词器
GET blog_user/_search
{
"query": {
"match": {"name": "the"}
}
}
// 无结果: "name_en"匹配到了动态模板, 使用english分词器, the是停用词, 被过滤掉了
GET blog_user/_search
{
"query": {
"match": {"name_en": "the"}
}
}
说明:
这里的match_mapping_type的类型支持 [object, string, long, double, boolean, date, binary], 若使用text将抛出如下错误信息:
{
"error": {
"root_cause": [
{
"type": "mapper_parsing_exception",
"reason": "Failed to parse mapping [_doc]: No field type matched on [text], possible values are [object, string, long, double, boolean, date, binary]"
}
],
"type": "mapper_parsing_exception",
"reason": "Failed to parse mapping [_doc]: No field type matched on [text], possible values are [object, string, long, double, boolean, date, binary]",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "No field type matched on [text], possible values are [object, string, long, double, boolean, date, binary]"
}
},
"status": 400
}
在6.0之前的版本, 将抛出如下过期的警告信息:
Deprecation: match_mapping_type [text] is invalid and will be ignored:
No field type matched on [text], possible values are [object, string, long, double, boolean, date, binary]
## 3.3 [过期]在index中自定义默认映射模板
_default mapping - 默认映射模板是类似于全局变量的存在, 对当前配置的索引起作用.
默认映射模板在Elasticsearch 6.x版本中已经不再支持, 因为6.0版本开始, 每个索引只能有一个类型, 因此默认模板没有存在的意义了.
下面的演示过程, 是在6.0之前的版本上的测试.
(1) 在index中定义默认映射模板(default mapping template):
先删除已有的blog_user索引: DELETE blog_user, 再执行下述命令:
PUT blog_user
{
"mappings": {
"_default_": {
"_all": { "enabled": false }
},
"_doc": {
"_all": { "enabled": true }
}
}
}
(2) 动态映射可以和索引模板(Index Templates)配合使用.
无论是自动创建Index, 还是手动显式创建Index, 索引模板都可以用来配置新索引的默认mappings(映射)、settings(配置项)和aliases(别名). 具体使用方法请参考博客: ES 10 - 如何使用Elasticsearch的索引模板(index template)
(十三)ES的数据类型 (text、keyword、date、object、geo等)
- 1 核心数据类型
- 1.1 字符串类型 - string(不再支持)
(1) 使用示例:
PUT website
{
"mappings": {
"blog": {
"properties": {
"title": {"type": "string"}, // 全文本
"tags": {"type": "string", "index": "not_analyzed"} // 关键字, 不分词
}
}
}
}
(2) ES 5.6.10中的响应信息:
#! Deprecation: The [string] field is deprecated, please use [text] or [keyword] instead on [tags]
#! Deprecation: The [string] field is deprecated, please use [text] or [keyword] instead on [title]
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "website"
}
(3) ES 6.6.0中的响应信息:
{
"error": {
"root_cause": [
{
"type": "mapper_parsing_exception",
"reason": "No handler for type [string] declared on field [title]"
}
],
"type": "mapper_parsing_exception",
"reason": "Failed to parse mapping [blog]: No handler for type [string] declared on field [title]",
"caused_by": {
"type": "mapper_parsing_exception",
"reason": "No handler for type [string] declared on field [title]"
}
},
"status": 400
}
可知string类型的field已经被移除了, 我们需要用text或keyword类型来代替string.
- 1.1.1 文本类型 - text
在Elasticsearch 5.4 版本开始, text取代了需要分词的string.
—— 当一个字段需要用于全文搜索(会被分词), 比如产品名称、产品描述信息, 就应该使用text类型.
text的内容会被分词, 可以设置是否需要存储: “index”: “true|false”.
text类型的字段不能用于排序, 也很少用于聚合.
使用示例:
PUT website
{
"mappings": {
"blog": {
"properties": {
"summary": {"type": "text", "index": "true"}
}
}
}
}
- 1.1.2 关键字类型 - keyword
在Elasticsearch 5.4 版本开始, keyword取代了不需要分词的string.
—— 当一个字段需要按照精确值进行过滤、排序、聚合等操作时, 就应该使用keyword类型.
keyword的内容不会被分词, 可以设置是否需要存储: “index”: “true|false”.
使用示例:
PUT website
{
"mappings": {
"blog": {
"properties": {
"tags": {"type": "keyword", "index": "true"}
}
}
}
}
- 1.2 数字类型 - 8种
数字类型有如下分类:
类型 | 说明 |
---|---|
byte | 有符号的8位整数, 范围: [-128 ~ 127] |
short | 有符号的16位整数, 范围: [-32768 ~ 32767] |
integer | 有符号的32位整数, 范围: [−231 ~ 231-1] |
long | 有符号的64位整数, 范围: [−263 ~ 263-1] |
float | 32位单精度浮点数 |
double | 64位双精度浮点数 |
half_float | 16位半精度IEEE 754浮点类型 |
scaled_float | 缩放类型的的浮点数, 比如price字段只需精确到分, 57.34缩放因子为100, 存储结果为5734 |
使用注意事项:
尽可能选择范围小的数据类型, 字段的长度越短, 索引和搜索的效率越高;
优先考虑使用带缩放因子的浮点类型.
使用示例:
PUT shop
{
"mappings": {
"book": {
"properties": {
"name": {"type": "text"},
"quantity": {"type": "integer"}, // integer类型
"price": {
"type": "scaled_float", // scaled_float类型
"scaling_factor": 100
}
}
}
}
}
- 1.3 日期类型 - date
JSON没有日期数据类型, 所以在ES中, 日期可以是:
包含格式化日期的字符串, “2018-10-01”, 或"2018/10/01 12:10:30".
代表时间毫秒数的长整型数字.
代表时间秒数的整数.
如果时区未指定, 日期将被转换为UTC格式, 但存储的却是长整型的毫秒值.
可以自定义日期格式, 若未指定, 则使用默认格式: strict_date_optional_time||epoch_millis
(1) 使用日期格式示例:
// 添加映射
PUT website
{
"mappings": {
"blog": {
"properties": {
"pub_date": {"type": "date"} // 日期类型
}
}
}
}
// 添加数据
PUT website/blog/11
{ "pub_date": "2018-10-10" }
PUT website/blog/12
{ "pub_date": "2018-10-10T12:00:00Z" } // Solr中默认使用的日期格式
PUT website/blog/13
{ "pub_date": "1589584930103" } // 时间的毫秒值
(2) 多种日期格式:
多个格式使用双竖线||分隔, 每个格式都会被依次尝试, 直到找到匹配的.
第一个格式用于将时间毫秒值转换为对应格式的字符串.
使用示例:
// 添加映射
PUT website
{
"mappings": {
"blog": {
"properties": {
"date": {
"type": "date", // 可以接受如下类型的格式
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
}
- 1.4 布尔类型 - boolean
可以接受表示真、假的字符串或数字:
真值: true, “true”, “on”, “yes”, “1”…
假值: false, “false”, “off”, “no”, “0”, “”(空字符串), 0.0, 0
- 1.5 二进制型 - binary
二进制类型是Base64编码字符串的二进制值, 不以默认的方式存储, 且不能被搜索. 有2个设置项:
(1) doc_values: 该字段是否需要存储到磁盘上, 方便以后用来排序、聚合或脚本查询. 接受true和false(默认);
(2) store: 该字段的值是否要和_source分开存储、检索, 意思是除了_source中, 是否要单独再存储一份. 接受true或false(默认).
使用示例:
// 添加映射
PUT website
{
"mappings": {
"blog": {
"properties": {
"blob": {"type": "binary"} // 二进制
}
}
}
}
// 添加数据
PUT website/blog/1
{
"title": "Some binary blog",
"blob": "hED903KSrA084fRiD5JLgY=="
}
注意: Base64编码的二进制值不能嵌入换行符\n, 逗号(0x2c)等符号.
- 1.6 范围类型 - range
(1) 添加映射:
PUT company
{
"mappings": {
"department": {
"properties": {
"expected_number": { // 预期员工数
"type": "integer_range"
},
"time_frame": { // 发展时间线
"type": "date_range",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"ip_whitelist": { // ip白名单
"type": "ip_range"
}
}
}
}
}
(2) 添加数据:
PUT company/department/1
{
"expected_number" : {
"gte" : 10,
"lte" : 20
},
"time_frame" : {
"gte" : "2018-10-01 12:00:00",
"lte" : "2018-11-01"
},
"ip_whitelist": "192.168.0.0/16"
}
(3) 查询数据:
GET company/department/_search
{
"query": {
"term": {
"expected_number": {
"value": 12
}
}
}
}
GET company/department/_search
{
"query": {
"range": {
"time_frame": {
"gte": "208-08-01",
"lte": "2018-12-01",
"relation": "within"
}
}
}
}
查询结果:
{
"took": 26,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1.0,
"hits": [
{
"_index": "company",
"_type": "department",
"_id": "1",
"_score": 1.0,
"_source": {
"expected_number": {
"gte": 10,
"lte": 20
},
"time_frame": {
"gte": "2018-10-01 12:00:00",
"lte": "2018-11-01"
},
"ip_whitelist" : "192.168.0.0/16"
}
}
]
}
}
- 2 复杂数据类型
- 2.1 数组类型 - array
ES中没有专门的数组类型, 直接使用[]定义即可;
数组中所有的值必须是同一种数据类型, 不支持混合数据类型的数组:
① 字符串数组: [“one”, “two”];
② 整数数组: [1, 2];
③ 由数组组成的数组: [1, [2, 3]], 等价于[1, 2, 3];
④ 对象数组: [{“name”: “Tom”, “age”: 20}, {“name”: “Jerry”, “age”: 18}].
注意:
- 动态添加数据时, 数组中第一个值的类型决定整个数组的类型;
- 不支持混合数组类型, 比如[1, “abc”];
- 数组可以包含null值, 空数组[]会被当做missing field —— 没有值的字段.
- 2.2 对象类型 - object
JSON文档是分层的: 文档可以包含内部对象, 内部对象也可以包含内部对象.
(1) 添加示例:
PUT employee/developer/1
{
"name": "ma_shoufeng",
"address": {
"region": "China",
"location": {"province": "GuangDong", "city": "GuangZhou"}
}
}
(2) 存储方式:
{
"name": "ma_shoufeng",
"address.region": "China",
"address.location.province": "GuangDong",
"address.location.city": "GuangZhou"
}
(3) 文档的映射结构类似为:
PUT employee
{
"mappings": {
"developer": {
"properties": {
"name": { "type": "text", "index": "true" },
"address": {
"properties": {
"region": { "type": "keyword", "index": "true" },
"location": {
"properties": {
"province": { "type": "keyword", "index": "true" },
"city": { "type": "keyword", "index": "true" }
}
}
}
}
}
}
}
}
- 2.3 嵌套类型 - nested
嵌套类型是对象数据类型的一个特例, 可以让array类型的对象被独立索引和搜索. - 2.3.1 对象数组是如何存储的
① 添加数据:
PUT game_of_thrones/role/1
{
"group": "stark",
"performer": [
{"first": "John", "last": "Snow"},
{"first": "Sansa", "last": "Stark"}
]
}
② 内部存储结构:
{
"group": "stark",
"performer.first": [ "john", "sansa" ],
"performer.last": [ "snow", "stark" ]
}
③ 存储分析:
可以看出, user.first和user.last会被平铺为多值字段, 这样一来, John和Snow之间的关联性就丢失了.
在查询时, 可能出现John Stark的结果.
- 2.3.2 用nested类型解决object类型的不足
如果需要对以最对象进行索引, 且保留数组中每个对象的独立性, 就应该使用嵌套数据类型.
—— 嵌套对象实质是将每个对象分离出来, 作为隐藏文档进行索引.
① 创建映射:
PUT game_of_thrones
{
"mappings": {
"role": {
"properties": {
"performer": {"type": "nested" }
}
}
}
}
② 添加数据:
PUT game_of_thrones/role/1
{
"group" : "stark",
"performer" : [
{"first": "John", "last": "Snow"},
{"first": "Sansa", "last": "Stark"}
]
}
③ 检索数据:
GET game_of_thrones/_search
{
"query": {
"nested": {
"path": "performer",
"query": {
"bool": {
"must": [
{ "match": { "performer.first": "John" }},
{ "match": { "performer.last": "Snow" }}
]
}
},
"inner_hits": {
"highlight": {
"fields": {"performer.first": {}}
}
}
}
}
}
- 3 地理数据类型
地理点类型用于存储地理位置的经纬度对, 可用于:
- 查找一定范围内的地理点;
- 通过地理位置或相对某个中心点的距离聚合文档;
- 将距离整合到文档的相关性评分中;
- 通过距离对文档进行排序
- 3.1 地理点类型 - geo point
(1) 添加映射:
PUT employee
{
"mappings": {
"developer": {
"properties": {
"location": {"type": "geo_point"}
}
}
}
}
(2) 存储地理位置:
// 方式一: 纬度 + 经度键值对
PUT employee/developer/1
{
"text": "小蛮腰-键值对地理点参数",
"location": {
"lat": 23.11, "lon": 113.33 // 纬度: latitude, 经度: longitude
}
}
// 方式二: "纬度, 经度"的字符串参数
PUT employee/developer/2
{
"text": "小蛮腰-字符串地理点参数",
"location": "23.11, 113.33" // 纬度, 经度
}
// 方式三: ["经度, 纬度"] 数组地理点参数
PUT employee/developer/3
{
"text": "小蛮腰-数组参数",
"location": [ 113.33, 23.11 ] // 经度, 纬度
}
(3) 查询示例:
GET employee/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": { "lat": 24, "lon": 113 }, // 地理盒子模型的上-左边
"bottom_right": { "lat": 22, "lon": 114 } // 地理盒子模型的下-右边
}
}
}
}
- 3.2 地理形状类型 - geo_shape
是多边形的复杂形状. 使用较少, 这里省略.
可以参考这篇文章: Elasticsearch地理位置总结
- 4 专门数据类型
- 4.1 IP类型
IP类型的字段用于存储IPv4或IPv6的地址, 本质上是一个长整型字段.
(1) 添加映射:
PUT employee
{
"mappings": {
"customer": {
"properties": {
"ip_addr": { "type": "ip" }
}
}
}
}
(2) 添加数据:
PUT employee/customer/1
{ "ip_addr": "192.168.1.1" }
(3) 查询数据:
GET employee/customer/_search
{
"query": {
"term": { "ip_addr": "192.168.0.0/16" }
}
}
- 4.2 计数数据类型 - token_counttoken_count类型用于统计字符串中的单词数量.
本质上是一个整数型字段, 接受并分析字符串值, 然后索引字符串中单词的个数.
(1) 添加映射:
PUT employee
{
"mappings": {
"customer": {
"properties": {
"name": {
"type": "text",
"fields": {
"length": {
"type": "token_count",
"analyzer": "standard"
}
}
}
}
}
}
}
(2) 添加数据:
PUT employee/customer/1
{ "name": "John Snow" }
PUT employee/customer/2
{ "name": "Tyrion Lannister" }
(3) 查询数据:
GET employee/customer/_search
{
"query": {
"term": { "name.length": 2 }
}
}
1 创建document
1.1 创建时手动指定id
(1) 适用情景:
从其他系统中导入数据到ES时, 会采取这种方式: 使用原有系统中数据已有的唯一标识, 作为ES中document的id.
而如果数据一生产出来就存储到了ES中, 一般是不适合手动指定id的.
(2) 使用语法:
put index/type/id
(3) 使用示例:
PUT employee/developer/1
{
“name”: “shoufeng”,
“e_id”: 5220
}
(4) 添加成功后的响应信息:
{
“_index” : “employee”,
“_type” : “developer”,
“_id” : “1”, // 指定了id, 控制底层的_id元字段
“_version” : 1, // 当前版本号, 基于此字段进行并发控制
“result” : “created”,
“_shards” : {
“total” : 2, // 参与创建的分片数, 包括Primary和Replica
“successful” : 1, // 成功创建索引的分片数量
“failed” : 0 // 创建索引失败的分片数量
},
“_seq_no” : 0,
“_primary_term” : 1
}
1.2 创建时自动生成id
(1) 使用情景:
ES作为数据存储服务器, 应用程序中的数据直接对接到ES中, 这种场景适合自动生成id.
在多节点并发生成大量数据的场景下, 自动生成id更具安全性.
(2) 使用语法:
POST index/type
(3) 使用示例:
POST employee/developer
{
“name”: “shoufeng”,
“sex”: “male”,
“age”: 20
}
(4) 添加成功后的响应结果:
{
“_index” : “employee”,
“_type” : “developer”,
“_id” : “vMxcFWoBfKUnm9s_Uxen”, // 没有指定id, 就会自动生成id, 长度为20个字符
“_version” : 1,
“result” : “created”,
“_shards” : {
“total” : 2,
“successful” : 1,
“failed” : 0
},
“_seq_no” : 0,
“_primary_term” : 1
}
官方文档中指出:
Elasticsearch自动生成的id, 长度为20个字符, 是URL安全的, 它是Base64编码的GUID字符串, 多节点(分布式系统)并行生成id时不会发生冲突.
2 查看document
2.1 根据id查询文档
查询时可以不指定type, 即下述的developer, 而用_all代替.
// 查询语法:
GET employee/developer/1
// 结果如下:
{
“_index” : “employee”,
“_type” : “developer”,
“_id” : “1”,
“_version” : 1,
“_seq_no” : 0,
“_primary_term” : 1,
“found” : true,
“_source” : { // 文档的元数据
“name” : “shoufeng”,
“e_id” : 5220
}
}
2.2 通过_source字段控制查询结果
(1) 只获取指定id的文档的_source内容:
GET employee/developer/1/_source
// 结果是:
{
“name” : “shoufeng”,
“e_id” : 5220
}
(2) 禁用指定id的文档的_source字段:
GET employee/developer/1?_source=false
// 结果是:
{
“_index” : “employee”,
“_type” : “developer”,
“_id” : “1”,
“_version” : 1,
“_seq_no” : 0,
“_primary_term” : 1,
“found” : true
}
(3) 过滤_source中的某些field:
// _source_includes和_source_excludes可以匹配通配符*
GET employee/developer/1?_source_includes=name,age&_source_excludes=sex
GET employee/developer/_search?_source_includes=name,age&_source_excludes=sex
(4) 通过stored_fields API过滤文档中已存储的字段:
在Elasticsearch 6.0之后, 不再支持fields, 需要使用stored_fieldsAPI替换.
GET employee/developer/1?stored_fields=name,age // 指定id
GET employee/developer/_search?stored_fields=name,age // 不指定id, 将查询所有文档
其他查询操作, 将在后续的文章中详细演示.
3 修改document
3.1 全量替换document
全量替换是基于指定文档id的修改:
// 语法与创建语法相同:
PUT employee/developer/1
{
“name”: “shoufeng001”, // 修改姓名
“age”: 20, // 添加年龄
“sex”: “male”, // 添加性别
“e_id”: 5220
}
操作过程说明:
① 如果指定的document id不存在, 就是创建操作;
② 如果指定的document id已经存在, 就是全量替换操作 —— 替换旧文档的JSON串内容;
③ Lucene中倒排索引一旦被创建就是不可变的, 要修改文档内容, 可以采取全量替换的方式 —— 对文档重新建立索引, 替换旧文档的所有内容;
④ ES会将旧文档标记为deleted, 然后根据我们提交的请求创建一个新文档, 当标记为deleted的文档数达到一定量时, ES会在自动删除这些旧文档.
3.2 强制创建document
(1) 存在这样的场景:
我们不知道索引中是否已经存在某个文档 —— 可能有其他用户在并发添加文档;
为了防止创建操作被执行为全量替换操作, 从而导致数据的丢失, 我们可以使用强制创建的方式, 来避免这种失误.
(2) 强制创建示例:
PUT employee/developer/1?op_type=create
{
“name”: “shoufeng”,
“age”: 20
}
// 或者使用:
PUT employee/developer/1/_create
{
“name”: “shoufeng”,
“age”: 20
}
// 响应结果中出现冲突:
{
“error”: {
“root_cause”: [
{ // 由于文档已经存在, 发生版本冲突, 导致创建失败
“type”: “version_conflict_engine_exception”,
“reason”: “[developer][1]: version conflict, document already exists (current version [2])”,
“index_uuid”: “OYu6J2x_S2S5v-R74aq6NQ”,
“shard”: “3”,
“index”: “employee”
}
],
“type”: “version_conflict_engine_exception”,
“reason”: “[developer][1]: version conflict, document already exists (current version [2])”,
“index_uuid”: “OYu6J2x_S2S5v-R74aq6NQ”,
“shard”: “3”,
“index”: “employee”
},
“status”: 409
}
出现冲突的原因:
① Elasticsearch通过乐观锁控制每个文档的_version信息, 强制创建语法会对当前操作的文档的_version信息进行初始化;
② 添加索引时, 发现已经存在对应id的文档, 而且其版本号与正在强制创建的文档的版本信息不匹配, 所以报错.
出现冲突后, 我们就能知道索引中已存在该文档了, 就可以根据自己的应用需求, 采取更改id后重新添加, 或者更改已有的文档等操作.
4 删除document
(1) 删除语法:
DELETE index/type/id
(2) 删除示例:
DELETE employee/developer/1
// 再次查看id为1的文档, 发现"found": false
(3) Elasticsearch删除文档采取的是懒删除机制:
不会立即物理删除, 而是将其标记为deleted, 当被删除的文档数量达到一定级别后, ES会在后台自动删除这些文档.
(十四)Elasticsearch写入索引数据的过程 以及优化写入过程 (底层原理)
- Coordinating Node(协调节点): 客户端随机选择一个Node用来发送操作请求, 这个节点就称为协调节点.
由于每个Node都能计算出Document的存储位置, 所以由哪个Node担任协调节点都是可以的——这对客户端来说是透明的.
① 客户端通过协调节点发送 查询请求.
② 协调节点对客户端提交的文档进行路由, 明确存储相关文档的Primary Shard(主分片), 然后使用Round-Robin算法(随机轮训算法), 将查询请求转发到 该Primary Shard及这个主分片对应的任意一个Replica Shard(副本分片) —— 读请求的负载均衡.
③ 接收到查询请求的Shard执行该请求, 然后将查询结果响应给协调节点.
④ 协调节点将查询结果响应给客户端.
-
1.Lucene操作document的流程
Lucene将index数据分为segment(段)进行存储和管理.
Lucene中, 倒排索引一旦被创建就不可改变, 要添加或修改文档, 就需要重建整个倒排索引, 这就对一个index所能包含的数据量, 或index可以被更新的频率造成了很大的限制.
为了在保留不变性的前提下实现倒排索引的更新, Lucene引入了一个新思路: 使用更多的索引, 也就是通过增加新的补充索引来反映最新的修改, 而不是直接重写整个倒排索引.
—— 这样就能确保, 从最早的版本开始, 每一个倒排索引都会被查询到, 查询完之后再对结果进行合并.
-
1.1 添加document的流程
① 将数据写入buffer(内存缓冲区);② 执行commit操作: buffer空间被占满, 其中的数据将作为新的 index segment 被commit到文件系统的cache(缓存)中;
③ cache中的index segment通过fsync强制flush到系统的磁盘上;
④ 写入磁盘的所有segment将被记录到commit point(提交点)中, 并写入磁盘;
④ 新的index segment被打开, 以备外部检索使用;
⑤ 清空当前buffer缓冲区, 等待接收新的文档.
说明:
(a) fsync是一个Unix系统调用函数, 用来将内存缓冲区buffer中的数据存储到文件系统. 这里作了优化, 是指将文件缓存cache中的所有segment刷新到磁盘的操作.
(b) 每个Shard都有一个提交点(commit point), 其中保存了当前Shard成功写入磁盘的所有segment.
-
1.2 删除document的流程
① 提交删除操作, 先查询要删除的文档所属的segment;② commit point中包含一个.del文件, 记录哪些segment中的哪些document被标记为deleted了;
③ 当.del文件中存储的文档足够多时, ES将执行物理删除操作, 彻底清除这些文档.
在删除过程中进行搜索操作:
依次查询所有的segment, 取得结果后, 再根据.del文件, 过滤掉标记为deleted的文档, 然后返回搜索结果. —— 也就是被标记为delete的文档, 依然可以被查询到.
在删除过程中进行更新操作:
将旧文档标记为deleted, 然后将新的文档写入新的index segment中. 执行查询请求时, 可能会匹配到旧版本的文档, 但由于.del文件的存在, 不恰当的文档将被过滤掉.
-
-
2 优化写入流程 - 实现近实时搜索
-
2.1 流程的改进思路
(1) 现有流程的问题:插入的新文档必须等待fsync操作将segment强制写入磁盘后, 才可以提供搜索.而 fsync操作的代价很大, 使得搜索不够实时.
(2) 改进写入流程:
① 将数据写入buffer(内存缓冲区);
② 不等buffer空间被占满, 而是每隔一定时间(默认1s), 其中的数据就作为新的index segment被commit到文件系统的cache(缓存)中;
③ index segment 一旦被写入cache(缓存), 就立即打开该segment供搜索使用;
④ 清空当前buffer缓冲区, 等待接收新的文档.
—— 这里移除了fsync操作, 便于后续流程的优化.
优化的地方: 过程②和过程③:
segment进入操作系统的缓存中就可以提供搜索, 这个写入和打开新segment的轻量过程被称为refresh.
-
-
2.2 设置refresh的间隔
Elasticsearch中, 每个Shard每秒都会自动refresh一次, 所以ES是近实时的, 数据插入到可以被搜索的间隔默认是1秒.(1) 手动refresh —— 测试时使用, 正式生产中请减少使用:
刷新所有索引:
POST _refresh
刷新某一个索引:
POST employee/_refresh
(2) 手动设置refresh间隔 —— 若要优化索引速度, 而不注重实时性, 可以降低刷新频率:
创建索引时设置, 间隔1分钟:
PUT employee { "settings": { "refresh_interval": "1m" } }
在已有索引中设置, 间隔10秒:
PUT employee/_settings { "refresh_interval": "10s" }
(3) 当你在生产环境中建立一个大的新索引时, 可以先关闭自动刷新, 要开始使用该索引时再改回来:
关闭自动刷新:
PUT employee/_settings { "refresh_interval": -1 }
开启每秒刷新:
PUT employee/_settings { "refresh_interval": "1s" }
-
3 优化写入流程 - 实现持久化变更
Elasticsearch通过事务日志(translog)来防止数据的丢失 —— durability持久化.
-
3.1 文档持久化到磁盘的流程
① 索引数据在写入内存buffer(缓冲区)的同时, 也写入到translog日志文件中;② 每隔refresh_interval的时间就执行一次refresh:
(a) 将buffer中的数据作为新的 index segment, 刷到文件系统的cache(缓存)中; (b) index segment一旦被写入文件cache(缓存), 就立即打开该segment供搜索使用;
③ 清空当前内存buffer(缓冲区), 等待接收新的文档;
④ 重复①~③, translog文件中的数据不断增加;
⑤ 每隔一定时间(默认30分钟), 或者当translog文件达到一定大小时, 发生flush操作, 并执行一次全量提交:
(a) 将此时内存buffer(缓冲区)中的所有数据写入一个新的segment, 并commit到文件系统的cache中; (b) 打开这个新的segment, 供搜索使用; (c) 清空当前的内存buffer(缓冲区); (d) 将translog文件中的所有segment通过fsync强制刷到磁盘上; (e) 将此次写入磁盘的所有segment记录到commit point中, 并写入磁盘; (f) 删除当前translog, 创建新的translog接收下一波创建请求.
扩展: translog也可以被用来提供实时CRUD.
当通过id查询、更新、删除一个文档时, 从segment中检索之前, 先检查translog中的最新变化 —— ES总是能够实时地获取到文档的最新版本.
-
3.2 基于translog和commit point的数据恢复
-
(1) 关于translog的配置:
flush操作 = 将translog中的记录刷到磁盘上 + 更新commit point信息 + 清空translog文件.
1.es的各个shard会每个30分钟进行一次flush操作。
2.当translog的数据达到某个上限的时候会进行一次flush操作。
相关配置为:
发生多少次操作(累计多少条数据)后进行一次flush, 默认是unlimited:
index.translog.flush_threshold_ops
当translog的大小达到此预设值时, 执行一次flush操作, 默认是512MB:
index.translog.flush_threshold_size
每隔多长时间执行一次flush操作, 默认是30min:
index.translog.flush_threshold_period
检查translog、并执行一次flush操作的间隔. 默认是5s: ES会在5-10s之间进行一次操作:
index.translog.interval
-
(2) 数据的故障恢复:
① 增删改操作成功的标志: segment被成功刷新到Primary Shard和其对应的Replica Shard的磁盘上, 对应的操作才算成功.
② translog文件中存储了上一次flush(即上一个commit point)到当前时间的所有数据的变更记录. —— 即translog中存储的是还没有被刷到磁盘的所有最新变更记录.
③ ES发生故障, 或重启ES时, 将根据磁盘中的commit point去加载已经写入磁盘的segment, 并重做translog文件中的所有操作, 从而保证数据的一致性.
(3) 异步刷新translog:
为了保证不丢失数据, 就要保护translog文件的安全:
Elasticsearch 2.0之后, 每次写请求(如index、delete、update、bulk等)完成时, 都会触发fsync将translog中的segment刷到磁盘, 然后才会返回200 OK的响应;
或者: 默认每隔5s就将translog中的数据通过fsync强制刷新到磁盘.
—— 提高数据安全性的同时, 降低了一点性能.
==> 频繁地执行fsync操作, 可能会产生阻塞导致部分操作耗时较久. 如果允许部分数据丢失, 可设置异步刷新translog来提高效率.
PUT employee/_settings { "index.translog.durability": "async", "index.translog.sync_interval": "5s" }
-
-
-
4 优化写入流程 - 实现海量segment文件的归并
-
4.1 存在的问题
由上述近实时性搜索的描述, 可知ES默认每秒都会产生一个新的segment文件, 而每次搜索时都要遍历所有的segment, 这非常影响搜索性能.为解决这一问题, ES会对这些零散的segment进行merge(归并)操作, 尽量让索引中只保有少量的、体积较大的segment文件.
这个过程由独立的merge线程负责, 不会影响新segment的产生.
同时, 在merge段文件(segment)的过程中, 被标记为deleted的document也会被彻底物理删除.
-
4.2 merge操作的流程
① 选择一些有相似大小的segment, merge成一个大的segment;
② 将新的segment刷新到磁盘上;
③ 更新commit文件: 写一个新的commit point, 包括了新的segment, 并删除旧的segment;
④ 打开新的segment, 完成搜索请求的转移;
⑤ 删除旧的小segment. -
4.3 优化merge的配置项
segment的归并是一个非常消耗系统CPU和磁盘IO资源的任务, 所以ES对归并线程提供了限速机制, 确保这个任务不会过分影响到其他任务.(1) 归并线程的速度限制:
限速配置 ,的默认值是20MB, 这对写入量较大、磁盘转速较高的服务器来说明显过低.
indices.store.throttle.max_bytes_per_sec
对ELK Stack应用, 建议将其调大到100MB或更高. 可以通过API设置, 也可以写在配置文件中:
PUT _cluster/settings { "persistent" : { "indices.store.throttle.max_bytes_per_sec" : "100mb" } }
// 响应结果如下: { "acknowledged": true, "persistent": { "indices": { "store": { "throttle": { "max_bytes_per_sec": "100mb" } } } }, "transient": {} }
(2) 归并线程的数目:
推荐设置为CPU核心数的一半, 如果磁盘性能较差, 可以适当降低配置, 避免发生磁盘IO堵塞:
PUT employee/_settings { "index.merge.scheduler.max_thread_count" : 8 }
(3) 其他策略:
优先归并小于此值的segment, 默认是2MB:
index.merge.policy.floor_segment
一次最多归并多少个segment, 默认是10个:
index.merge.policy.max_merge_at_once
一次直接归并多少个segment, 默认是30个
index.merge.policy.max_merge_at_once_explicit
大于此值的segment不参与归并, 默认是5GB. optimize操作不受影响
index.merge.policy.max_merged_segment
-
4.4 optimize接口的使用
segment的默认大小是5GB, 在非常庞大的索引中, 仍然会存在很多segment, 这对文件句柄、内存等资源都是很大的浪费.但由于归并任务非常消耗资源, 所以一般不会选择加大 index.merge.policy.max_merged_segment 配置, 而是在负载较低的时间段, 通过optimize接口强制归并segment:
强制将segment归并为1个大的segment:
POST employee/_optimize?max_num_segments=1在终端中的操作方法:
curl -XPOST http://ip:5601/employee/_optimize?max_num_segments=1
optimize线程不会受到任何资源上的限制, 所以不建议对还在写入数据的热索引(动态索引)执行这个操作.实战建议: 对一些很少发生变化的老索引, 如日志信息, 可以将每个Shard下的segment合并为一个单独的segment, 节约资源, 还能提高搜索效率.
-
(十五)查询Elasticsearch中的数据 (基于DSL的查询, 包括validate、match、bool)
-
1 什么是DSL
DSL: Domain Specific Language, 领域特定语言, 指的是专注于某个应用程序领域的、具有高度针对性的计算机语言.Query String 与 Query DSL之间的区别:
Query String: 在请求的URL后直接拼接查询条件;
Query DSL: 在请求的Request Body中携带查询条件.DSL功能强大, 可以构建复杂的查询、过滤、聚合条件, 所以这种查询方式的用途最广.
-
2 _validate - 校验查询语句是否合法
对于复杂的查询, 很有必要在查询前使用validate API进行验证, 保证DSL语句的正确有效:// 要查询name中包含"java"的文档: GET shop/it_book/_validate/query?explain { "query": { "math": { // 错误的查询名称, 应该是match "name": "java" } } }
// 校验结果: { "valid": false, "error": "org.elasticsearch.common.ParsingException: no [query] registered for [math]" } // 修改math为match后, 校验结果为: { "valid": true, "_shards": { "total": 1, "successful": 1, "failed": 0 }, "explanations": [ { "index": "shop", "valid": true, // 校验通过, DSL有效 "explanation": "+name:java #_type:it_book" // 查询条件, +表示必须存在 } ] }
-
3 match query - 匹配查询
-
3.1 简单功能示例
- 3.1.1 查询所有文档
GET shop/it_book/_search { "query": { "match_all": {} } }
- 3.1.2 查询满足一定条件的文档
查询name中包含"java"的文档, 同时按照价格升序排序:
GET shop/it_book/_search { "query": { "match": { "name": "java" } }, "sort": [ { "price": {"order": "asc"} } ] }
- 3.1.3 分页查询文档
GET shop/it_book/_search { "query": { "match_all": {} }, "from": 0, // 开始记录数, 起始数为0 "size": 1 // 页大小, 即每页显示的记录数 }
- 3.1.4 指定返回的结果中包含的字段
GET shop/it_book/_search { "query": { "match_all": {} }, "_source": [ "name", // 显示商品名称 "price" // 显示商品价格 ] }
-
3.2 精确查询 - match_phrase
不同的数据类型在建立倒排索引时, 有的会作为full text处理, 有的作为exact value处理.
对查询串分词时, 使用的分析器(analyzer)必须和创建index时使用的相同, 否则将检索不到准确的数据.-
3.2.1 精确匹配 - exact value
常见的exact value类型有date - 日期类型.
ES检索时, 不会对String进行分词, 而是完全根据String的值去精确匹配, 查找相应的文档.
在DSL中, 通过match_phrase短语匹配达到精确匹配的目的 —— 不会对查询串进行分词, 而是直接精确匹配查找.
示例: 查询name中包含"thinking in java"的文档, 不会对查询串进行分词:GET shop/_search { "query": { "match_phrase": { "name": "thinking in java" } } }
-
3.2.2 全文搜索 - full text
常见的full text类型有: text - 文本串.
ES检索时, 会对检索串进行分词, 包括缩写、时态、同义词等转换手段, 然后根据分词结果与倒排索引进行匹配, 查找相应的文档.索引中只要有任意一个相关field的分词 匹配拆分后的词, 这个文档就可以出现在结果中, 只是匹配度越高的排名越靠前.
示例: 查询name中包含"thinking in java"的文档, 会将查询串拆分为"think", “in”, "java"三个词:
GET shop/_search { "query": { "match": { "name": "thinking in java" } } }
-
-
-
3.3 控制匹配规则 - operator
operator 操作符, 用来指定ES对分词后的词项如何进行检索过滤. 选项有:
and, 作用 == match_phrase, 即全部匹配;
or, 作用 == match, 即部分匹配.
使用示例:GET shop/_search { "query": { "match": { "name": { // 要查询的field "query": "编程思想", "operator": "or" // 操作符 } } } }
-
3.4 指定命中的百分比 - minimum_should_match
minimum_should_match 用来指定最少要匹配多少比例的分词, 才算符合条件并返回结果.
示例: 搜索name中包含"并发编程的艺术", 被拆分成"并发", “编程”, "艺术"等词, 现在要求至少匹配50%的分词, 可以这样:GET shop/_search { "query": { "match": { "name": { "query": "并发编程的艺术", "minimum_should_match": "50%" } } } }
当然这种需求也可以用 must、must_not、should 匹配同一个字段的方式进行组合查询.
-
3.5 多字段的匹配 - multi_match
multi_match 用来对多个字段同时进行匹配: 任意一个字段中存在相应的分词, 就可作为结果返回.示例 ① : 查询 name 或 desc 字段中包含 “面试经典” 的文档 —— 会对查询串进行分词:
GET shop/_search { "query": { "multi_match": { "query": "面试经典", "fields": [ "name", "desc" ] } } }
示例 ② : 查询 name 或 desc 字段中同时包含 “面试经典” 的文档 —— 不对查询串进行分词:
GET shop/_search { "query": { "multi_match": { "query": "面试经典", "type": "cross_fields", // 还有best_fields、most_fields、phrase、phrase_prefix选项 "operator": "and", // 全部匹配, or是部分匹配 "fields": [ "name", "desc" ] } } }
-
4 bool query - 布尔查询(真假查询)
bool query, 顾名思义, 就是 真假/有无 查询. 包括4个子查询:① must - 必须匹配, 类似于SQL中的 = ;
② must_not - 必须不匹配, 类似于SQL中的 != ;
③ should - 不强制匹配, 类似于SQL中的 or ;
④ filter - 过滤, 将满足一定条件的文档筛选出来.除filter之外, 每个子查询都会根据自己的条件计算出每个文档的相关度分数, 然后bool综合所有分数, 合并为一个.
- 4.1 简单功能示例
GET shop/_search { "query": { "bool": { "must":[ { "match": { "name": "Java" } } ], "must_not": [ { "match": { "desc": "编程" } } ], "should": [ { "match": { "publisher": "机械工业" } } ], "filter": { "bool": { "must": [ { "range": { "date": { "gte": "2010-01-01" }}}, { "range": { "price": { "lte": 99.00 }}} ] } } } } }
- 4.2 嵌套使用bool query
GET shop/_search { "query": { "bool": { "should": [ { "term": { "name.keyword": "Java编程思想" } }, { "bool": { "must": [ { "term": { "product_desc": "刷头" } } ] } } ] } } }
- 4.3 直接filter操作 - 使用constant_score
如果不指定query条件而直接filter, 将抛出no [query] registered for [filter], 此时通过constant_score即可实现直接filter.
GET shop/_search { "query": { "constant_score": { "filter": { "range": { "price": { "gte": 80 } } } } } }
- 4.4 指定should的匹配个数 - minimum_should_match
如果组合查询中没有must, 就会至少匹配一个should.
可以通过 minimum_should_match 指定匹配的should的个数.
GET shop/_search { "query": { "bool": { "should": [ { "match": { "name": "java" } }, { "match": { "desc": "编程"} }, { "match": { "price": 109 } } ], "minimum_should_match": 2 } } }