Elasticsearch学习总结

(一)初识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)
# 工作路径
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.2 倒排索引的核心原理-normalization
    建立倒排索引时, 会执行normalization(正常化)操作 —— 将拆分的各个单词进行处理, 以提高搜索时命中关联的文档的概率.
    normalization的方式有: 时态转换, 单复数转换, 同义词转换, 大小写转换等.

比如文档中包含His mom likes small dogs:

  1. 在建立索引的时候normalization会对文档进行时态、单复数、同义词等方面的处理;
  2. 然后用户通过近似的mother liked little dog, 也能搜索到相关的文档.
  • 2 ES的默认分词器

(1) ES中的默认分词器: standard tokenizer, 是标准分词器, 它以单词为边界进行分词. 具有如下功能:

  1. standard token filter: 去掉无意义的标签, 如<>, &, - 等.
  2. lowercase token filter: 将所有字母转换为小写字母.
  3. 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 索引模板中的内容

  1. settings: 指定index的配置信息, 比如分片数、副本数, tranlog同步条件、refresh策略等信息;

  2. 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]
float32位单精度浮点数
double64位双精度浮点数
half_float16位半精度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
            }
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值