快速入门ElasticSearch
写在前面
ElasticSearch是一个分布式、可扩展、实时的搜索与数据分析引擎,它能从项目一开始就赋予你的数据以搜索、分析和探索的能力,在日常工作和学习中扮演着非常重要的角色,鉴于此本篇将从ElasticSearch的安装、基础概念、基本用法、高级查询等角度来进行介绍。
ElasticSearch简介
ElasticSearch是一款基于Apache Lucene构建的开源搜索引擎,采用Java编写,提供简单易用的RESTful API,开发者可以通过它轻松实现简单明了的搜索功能。ElasticSearch轻松的横向扩展能力,支持PB级别的结构化和非结构化数据处理。其实就是说当机器的磁盘容量不满足需求的时候,可以通过不断的横向添加节点(机器)来解决容量问题,通过这种方式可以使我们的存储容量从GB到TB甚至PB级别的转化。
接下来学习ElasticSearch的应用场景:
(1)海量数据分析引擎。当你需要对应用日志、系统日志等进行分析时,可以使用ElasticSearch的聚合搜索功能来实现;
(2)站内搜索引擎。当你需要快速搭建一个站内搜索的时候,使用ElasticSearch就能完成这个任务;
(3)数据仓库。开发者可以使用ElasticSearch强大的分布式搜索能力,直接将其作为数据仓库产品来使用,可以存储PB级别的结构化或者非结构化数据,这样可以为上层应用提供强大的数据存储能力。
当然上面介绍的应用场景只是一些较为通用的场景,而实际上一些大型公司会将ElasticSearch用在其他的地方,如英国卫报公司则使用ElasticSearch来实时搜集用户日志和社交网络数据以便于实时分析公众对文章的响应程度。而维基百科和GitHub尽管都使用ElasticSearch进行站内实时的数据搜索,但是维基百科则使用ElasticSearch提供全文搜索,并高亮关键字;GitHub则使用ElasticSearch来检索1000多亿代码;百度则使用ElasticSearch搭建其实时日志监控平台。
ElasticSearch安装
ElasticSearch的版本非常特殊,它的迭代顺序是这样的:1.x–>2.x–>5.x–>6.x–>7.x。为什么会出现这个问题呢?那是因为ElasticSearch属于Elastic技术栈,但是Elastic技术栈中其他中间件的版本更新迭代不同,版本号也出现了混乱,举个例子ElasticSearch用2.x版本,而要求Kibana使用4.x版本,这势必会提高小白的学习门槛,要知道在学习任何软件的第一部分就是选择合适版本,鉴于此Elastic在16年就正式统一了所有Elastic技术栈中所有产品的版本号。综合考虑目前情况,笔者选择使用ElasticSearch6.x版本,当然在后续文章中则会使用最新版的7.x系列来进行学习。
ElasticSearch单实例安装
第一步,下载安装java8及以上版本,之后进行环境变量的设置:
(1)JAVA_HOME
值为G:\Application\java1.8
;(2)在系统变量的Path处设置两个值,分别是:%JAVA_HOME%\bin
和%JAVA_HOME%\jre\bin
。(3)使用java -version
命令来检验是否安装成功,输出版本信息则表明Java安装成功:
第二步,新建ElasticSearch文件,并将后续下载的文件存放与此,同时便于后续管理。
第三步,下载ElasticSearch安装包,点击 ElasticSearch镜像地址,选择合适的64位版本后进行下载。
第四步,启动ElasticSearch。开发者可以进入到ElasticSearch的bin目录,然后直接执行start /min elasticsearch
命令即可在后台启动ElasticSearch。之后直接打开浏览器,输入http://127.0.0.1:9200
后显示下图所示信息,也表明elasticsearch已成功启动:
这样关于ElasticSearch单实例的安装就完成了,但是由于ElasticSearch返回的是JSON格式信息,对开发者并不是非常友好,因此需要安装Head插件,因为它提供了Web界面,帮我们解决了无界面这一问题,同时也可以提供基本信息查看,REST请求模拟,以及基本数据的检索等功能。点击 这里 获取插件,然后download,选择下载ZIp文件即可:
请注意该插件要求nodejs版本大于6,可以使用node -v
命令来查看当前系统nodejs版本的信息,可以看到笔者为v12.13.1
,满足要求,其实这就是一个node项目,因此需要进入到elasticsearch-head-master包内,然后执行npm install
命令来安装相关依赖包,之后执行npm run start
,当然也可以使用cnpm命令来代替之前的npm命令,结果如下所示:
可以看到它的Web服务运行在http://localhost:9100
,因此可以打开浏览器去访问这个地址:
但是目前Web页面显示集群未连接,但是此时ElasticSearch和Head插件都已经启动了,所以问题在于两者还未完成通信(存在跨域问题),因此需要停掉正在运行的ElasticSearch,同时进入到其config包内,修改其elasticsearch.yml配置文件,在其末尾添加如下两行代码:
1 | http.cors.enabled: true |
最后保存退出,重启ElasticSearch和Head插件,注意顺序不能搞错,然后再去浏览器访问http://localhost:9100
地址,此时页面如下所示:
ElasticSearch多实例安装
接下来将搭建一个包含3个节点的集群,其中一个master,两个slave节点,为了便于操作将之前搭建的单节点实例作为master节点。首先进入到之前搭建的单节点实例中,修改其elasticsearch.yml配置文件,如下所示:
1 | cluster.name: envythink |
注意各个配置信息所在的位置:
之后重启ElasticSearch这一master节点,然后访问浏览器确认集群名称和当前节点修改都已生效:
由于ElasticSearch默认启动使用的是elasticsearch.yml配置文件,且无法以其他名称文件启动,因此要想实现一台机器部署多个实例,就必须直接复制多个安装程序。
在ElasticSearch目录下新建slave-node目录,然后复制两份安装文件进入并修改名字为slave1和slave2,其中slave1中config目录下的elasticsearch.yml配置文件修改如下信息:
可以看到这里我们修改了节点的名称和端口号,以及配置发现集群的IP地址。同时将此的elasticsearch.yml配置文件复制一份到slave2中config目录下,替换之前的elasticsearch.yml配置文件,并将节点名称和端口号依次修改为slave2和9202,之后启动这两个salve节点,并重启Head插件,之后再去浏览器中访问Head页面,如下所示:(注意这个顺序可能会随着节点的顺序而发生变动)
如果出现下面的错误:
1 | failed to send join request to master [{master}{4f6DA5uJQ8iJokZ3T18gjg}{2dOtfXXbTrynhShWrzt3xQ}{127.0.0.1}{127.0.0.1:9300}{ml.machine_memory=8374956032, ml.max_open_jobs=20, xpack.installed=true, ml.enabled=true}], reason [RemoteTransportException[[master][127.0.0.1:9300] |
那是因为之前复制ElasticSearch文件时,将其data目录下的文件也一同复制了,因此需要清空data文件夹,然后再进行重试即可。
ElasticSearch基础概念
首先是集群和节点的概念,我们知道集群是由一个或多个节点组成的,如前面我们搭建的具有三个节点的集群,其默认名称为ElasticSearch,但是前面我们通过cluster.name参数将其修改为envythink。请注意对于任意一个节点来说,其集群的名字只能有一个,实际上所有的节点都是靠这个集群的名称来加入集群的。此外每个节点都有自己的名字,可以通过node.name来自定义,同时节点都是可以存储数据,参与集群索引数据,以及搜索数据的独立服务。其次是索引,你可以将其理解为是含有相同属性的文档集合。接着是类型,一般来说索引可以定义一个或者多个类型,但是文档必须是属于一个类型。那么问题来了,文档又是什么呢?文档就是可以被索引的基本数据单位,如用户的个人信息,它是ElasticSearch中最基本的存储单位。索引在ElasticSearch中是通过名字来识别的,且它必须是英文字母小写,且不含中划线,我们都是通过名字来对文档数据进行增删改查等操作。
关于索引、类型和文档这三者之间的关系,可以借鉴数据库的相关知识,将索引类比为数据库;类型类比为数据表;而文档就是一行SQL记录。再来举一个较为详细的系统,假设我们这里有一个信息查询系统,此处使用ElasticSearch来作存储,那么里面的数据就可以分为各种各样的索引,如图书索引,服装索引,电器索引等,而对于图书索引又可以细分为文学类、工具类、技术类等类型,而具体到每一本书籍则就是文档,也就是最小的存储单位。
和索引相关还有两个比较重要的概念就是分片和备份。每个索引都有多个分片,每个分片就是一个Lucene索引。而拷贝一份分片就完成了分片的备份。使用分片可以将索引进行拆分,可以分担每一个索引上的压力,同时分片还允许用户进行水平扩展和拆分,以及分布式的操作,可以提高搜索以及其他操作的效率。使用备份的好处就是当一个主分片出现问题时,备份的分片就可以代替工作,从而提高了ElasticSearch的可用性,同时备份的分片也支持搜索操作,可以减轻搜索的压力。ElasticSearch默认在创建索引时,会创建5个分片,一个用于备份,当然这个数据也是可以修改的。此外分片的数量只能在创建索引的时候指定,而不能在后期进行修改,但是备份却是可以动态修改的。
ElasticSearch基本用法
由于ElasticSearch使用的是RESTful风格的API,因此在学习ElasticSearch的基本用法之前,需要了解ElasticSearch中API的基本格式:http://<ip>:<port>/<索引>/<类型>/<文档id>
,注意上面的索引、类型和文档都是名词,而动作则使用HTTP对应的GET/POST/PUT/DELETE。
创建索引
接下来可以结合之前的Head插件来显式创建索引,点击左上角的索引–>创建索引–>填入数据–>点击确定(注意这里的movie是索引名称,必须是英文小写,且不能使用中划线):
之后回到首页,可以看到页面出现了10个绿底黑字的方框,这些都是ElasticSearch的分片,如下所示:
其实你还可以发现这些方框有的边框颜色粗,有的浅,那是因为粗的是主分片,浅的是分片的备份。其实上面这种是非结构化的创建,其实还有结构化的创建。那么如何确定某个索引是结构化还是非结构化的呢?可以借助于Head插件来完成,点击索引的信息按钮,然后点击索引信息按钮,如下所示:
然后会弹出一个页面,注意这里面的”mappings”字段,它是结构化的关键词,如果后面的内容是空的,则表示它是非结构化的索引。也就是说上面我们创建的movie其实是一个非结构化的索引:
那么问题来了,如何创建结构化的索引呢?同样可以借助于head插件,点击右上角的“复合查询”按钮,然后选择POST方式:
并修改接口为movie/action/_mappings
,然后在里面新增如下代码:
1 | { |
之后查看一下首页,可以发现之前的“mappings”字段里面已经显示了刚才添加的信息:
尽管使用Head插件可以结构化创建,但是对于JOSN的书写并不太友好,此时我们可以使用Postman这一工具来进行创建,但是需要开发者自己书写一些基础配置信息,如”settings”等,之后才能编写“mappings”关键字等信息,如下所示:
1 | { |
之后点击提交即可,结果如下所示,注意使用PUT方法用于新增数据(ES6.x系列要求一个index只能存储一种type):
之后刷新首页,可以看到右侧多出了一个book的索引,然后查看该索引的信息可以发现该索引中的”mappings”关键字中的信息就是之前我们通过Postman创建的:
数据插入
在学完了如何创建索引之后,接下来开始学习如何插入数据,在ElasticSearch中,插入分为两种:“指定文档id插入”和“自动产生文档id插入”。这里的文档id它是一个唯一索引值,指向文档数据。接下来学习如何使用Postman工具来插入数据,选择PUT方法,并输入接口为http://127.0.0.1:9200/book/novel/1
,请注意这里的book为索引,novel为类型,1是文档的id,这个文档id用于唯一标识文档,然后书写如下JSON信息:
然后点击确定,刷新首页并点击上方的数据预览,可以看到我们之前的数据就已经成功插入了:
同时可以看到对于book这一索引来说,其docs的数量为1,它表示book索引下所有的文档的数量:
在前面文档的id都是开发者自己来指定的,其实还可以让ES自己来生成,不过此时需要使用的是POST方法,相应的代码如下所示:
1 | { |
其中Postman对应的操作如下所示:
可以看到此时的文档id就是ES自动为我们所生成的字符串,这样关于数据的插入就先学习到这。
数据修改
在简单学完如何插入数据之后,接下来开始学习如何对数据进行修改。对数据修改有两种方式:直接修改文档和脚本修改文档。首先学习直接修改文档这种方式,在前面我们已经成功的往book这一索引中添加了两条记录,接下来就尝试将之前文档id为1的记录的《朝花夕拾》修改为《呐喊》,继续使用Postman测试工具同时使用POST方法,注意此时的API接口为http://127.0.0.1:9200/book/novel/1/_update
,后面必须添加_update
参数:
此时开发者填入的JSON信息必须包裹在doc字段中,这个doc字段用于表明这是直接修改文档方式:
1 | { |
之后点击确认,可以发现name属性的值的确发生了变化:
除了上面介绍的直接修改文档方式外,开发者还可以使用脚本修改文档这一方式。ES支持多种脚本语言,这里以内置的脚本语言painless为例进行说明,注意无论是直接修改文档还是通过脚本来修改文档,其对应的API接口是不变的,依旧为http://127.0.0.1:9200/book/novel/1/_update
,但是前面的doc需要修改为script:
1 | { |
请注意其中的ctx表示上下文,_scorce
则是获取资源,而后面则是得到了文档的属性。不过这样有一个问题:直接将参数写进了语句中,而无法动态注入,其实上面的写法可以采用动态属性注入的方式:
1 | { |
这样就可以实现属性的动态赋值,那么关于数据的简单修改就学习到这里。
数据删除
接下来开始学习如何删除数据,这里主要学习如何删除文档和索引。首先学习如何删除文档,可以借助于Postman测试工具,选择使用DELETE方法,然后输入API接口为http://127.0.0.1:9200/book/novel/1
,注意这里需要添加文档id,然后点击提交即可删除文档。
接下来是删除索引,注意索引除非是必要删除,否则不要轻易删除,因为删除它会删除它所包含的所有数据。同样删除索引也可以借助于Postman测试工具。选择使用DELETE方法,然后输入API接口为http://127.0.0.1:9200/book
,注意这里仅仅是需要添加索引名称,然后点击提交即可删除索引。其实删除索引还可以借助于Head插件来完成,点击movie索引的“动作”按钮,然后选择“删除”,之后弹出输入框,开发者输入“删除”后,点击确定即可完成删除:
然后首页会自动刷新,可以看到索引名称为movie的索引已经被删除了:
这样关于数据的删除就先学习到这里,后续开始学习如何查询数据。
数据查询
接下来开始学习较为重要的数据查询,数据查询包括简单查询、条件查询和聚合查询这三种,下面将仔细学习这三种查询方式。考虑到后续的需要,这里就新创建了一个book索引,其对应的JSON信息为:
1 | { |
之后查看一下首页发现book索引已经创建成功,如下所示:
接下来就是为这个book索引添加文档,这里提前准备了一些数据,如下所示:
首先来学习简单查询,使用Postman测试工具,选择GET方法,使用的接口为http://127.0.0.1:9200/book/novel/文档id
,然后查看结果:
接着再来学习条件查询,注意条件查询使用POST方法,使用的接口为http://127.0.0.1:9200/book/_search
,然后构建一个查询的JOSN信息,注意所有的信息都必须包含在query这个关键词内,match_all表示查询所有的结果信息:
1 | { |
请注意这里面的took表示查询所花费的时间,单位为毫秒;hits表示查询的全部结果数,可以看到有12条信息,但是此处只会显示10条,我们可以自己来指定返回的数量以及从从何处返回,只需在上述JSON格式信息中添加过滤条件即可,from表示从查询的第一个数据开始返回,size表示只返回一个:
1 | { |
运行结果如下所示:
上面的条件查询没设置过滤条件,接下来尝试查询所有title中包含Java的文档,此时对应的JSON格式信息如下:
1 | { |
然后点击查询,可以发现居然只查询到两条,而title中包含JavaScript的却没有查询到:
查询结果默认是根据_score
倒序排列的,开发者可以自定义排序字段,如使用出版时间publish_date,然后将其默认的升序排序修改为倒序:
1 | { |
最后再来学习聚合查询,所谓的聚合查询就是指将多个相同的数据进行统计查询,如根据字数对书籍进行聚合查询,相应的JSON格式信息为:
1 | { |
请注意这里的aggs表示聚合查询,group_by_word_count是自定义的聚合名称,terms表示聚合条件,field表示聚合字段为word_count。聚合结果如下所示:
前面都是查询的信息,后面则是聚合的结果,当然还可以多个聚合查询,如:
1 | { |
这样在查询结果的最后面会显示出两个聚合结果。其实还可以指定对某个值进行计算,如对字数word_count进行计算:
1 | { |
然后点击运行,运算结果如下所示,里面包含了最大值、最小值、平均值和总和:
stats表示对指定字段进行计算,里面包含5个值,如果只是需要单纯的某个值,可以将stats修改为min、max、avg、sum和count。
高级查询
前面学习的查询是最为基础的查询,接下来学习较为高级的查询知识,主要包括子条件查询和复合条件查询。
子条件查询Query context
子条件查询也称为叶子条件查询,它是以特定字段查询所指特定值;而复合条件查询则是以一定的逻辑组合子条件查询。子条件查询分为Query context和Filter context。
在查询过程中,Query context除了判断文档是否满足查询条件外,ElasticSearch还会计算一个_score
来标识匹配的程度,旨在判断目标文档和查询条件匹配的有多好。
Query context常用的查询有全文本查询和字段级别查询,其中全文本查询主要针对文本类型的数据;而字段级别查询则针对结构化的数据,如时间、日期等。
模糊匹配
全文本查询又分为模糊匹配,短语匹配和多个字段的查询,以及语法的查询,这里先学习模糊匹配。使用Postman测试工具来演示如何进行模糊查询,同样使用POST方法,相应的接口API为http://127.0.0.1:9200/book/_search
,相对应的JSON格式信息为:
1 | { |
可以看到查询结果为:
接下来再来尝试搜索title中包含Java入门的文档,相对应的JSON格式信息为:
1 | { |
神奇的事情发生了,居然把所有包含Java或者入门的文档都给查询出来了,其实这就是模糊查询的弊端,也就是说此时”Java入门”这一词语被拆分为”Java”和”入门”这两个词语,然后再进行模糊查询,而我们实际上想查询的则是”Java入门”这个整体。
短语匹配
那么如何解决上述问题呢?可以使用短语匹配,只需将其中的match关键字修改为match_phrase即可:
1 | { |
查询结果如下所示:
多个字段的模糊匹配查询
接下来学习多个字段的模糊匹配查询,它的关键字是multi_match。同样使用POST方法,相应的接口API为http://127.0.0.1:9200/book/_search
,相对应的JSON格式信息为:
1 | { |
以上的JSON信息表示作者或者标题中包含余思的文档就会被查询出来。
语法查询
接下来学习语法查询,它是根据一定的语法规则来进行查询,经常使用在Kibana中,用于支持数据搜索,且支持通配符,范围查询、布尔查询和正则表达式。语法查询的关键字是query_string,同样使用POST方法,相应的接口API为http://127.0.0.1:9200/book/_search
,相对应的JSON格式信息为:
1 | { |
查询结果如下所示:
现在假设需要查询包含Java或者C的文档记录,此时相对应的JSON格式信息为:
1 | { |
查询结果如下所示,可以看到一共有5条查询记录,包括C,C++,C#和Java:
当然开发者还可以指定查询的字段,如前面的例子,只搜索标题和作者中包含Java和C的文档记录,此时相应的JSON格式信息为:
1 | { |
字段级别查询
字段级别查询针对的是结构化的数据,如时间、日期等,接下来就开始学习如何对结构化的数据进行查询。字段级别查询的关键字是term(具体项),同样使用POST方法,相应的接口API为http://127.0.0.1:9200/book/_search
,相对应的JSON格式信息为:
1 | { |
这样就可以查询出单词数为5000的文档记录,结果如下所示:
当然它还支持范围查询,此时使用的关键字是range,如果想要查询单词数在5000-9000内的文档记录,相对应的JSON格式信息为:
1 | { |
注意其中的gte和lte,gte是greate than and equals的简写,也就是大于或者等于的意思。当然我们还可以对时间进行范围查询,如查询时间在2017-2018年之间的,即时间在2017-01-01至2018-12-31期间的文档记录,相对应的JSON格式信息为:
1 | { |
当然了,如果想查询自2017年1月1日到现在的文档记录,只需将后面lte中的时间修改为”now”即可。这样关于子条件查询Query context的学习就先到此,接下来开始学习子条件查询Filter context。
子条件查询Filter context
Filter context是指在查询过程中,只判断该文档是否满足条件,只有Yes或者No,而Query context除了会判断是否满足条件,还判断满足条件后的匹配程度。
Filter context的关键字是bool,同样使用POST方法,相应的接口API为http://127.0.0.1:9200/book/_search
,相对应的JSON格式信息为:
1 | { |
上面的例子就是过滤出单词数为5000的文档记录,注意ES会将filter的数据进行缓存,它的速度比query快一些,通常需要结合bool来使用。上面例子的过滤结果如下所示:
复合条件查询
在复合条件查询中,常用的两个查询分别是:固定分数查询和布尔查询。
固定分数查询
在学习固定分数查询之前,先使用全文搜索来搜索标题中包含入门的文档记录,全文搜索使用的关键字是query,同样使用POST方法,但是对应的接口API为http://127.0.0.1:9200/_search
,注意里面是不写索引的,此时相对应的JSON格式信息为:
1 | { |
可以看到查询结果是12条,也就是将之前的所有记录都查询出来,同时发现ES给予每个查询结果都有一个_score
它用于体现查询结果的匹配程度,也就是得分,得分越高表明越匹配查询条件。上例的查询结果如下所示:
所谓的固定分数查询就是只在查询的时候将所有匹配结果的_score
值都设置为相同的,如下所示:
1 | { |
此时查询结果如下所示:
假设开发者想指定固定的分数,此时可以将上述JSON格式的信息修改为:
1 | { |
此时查询结果如下所示:
由于此处使用了boost固定分数且使用了filter,因此ElasticSearch会对结果进行缓存。
现在尝试将外层的filter去掉,并将自定义的boost也去掉,此时JSON格式的信息变为:
1 | { |
此时查询结果如下所示,可以看到固定分数查询是不支持match匹配的,仅支持filter匹配。
布尔查询
在前面我们已经使用过布尔查询,布尔查询使用的关键字是bool,同样使用POST方法,但是对应的接口API为http://127.0.0.1:9200/_search
,注意里面是不写索引的,此时相对应的JSON格式信息为:
1 | { |
可以看到这里我们使用了bool关键字,且在里面又使用了should这一条件,表示应当,上面例子的意思是查询作者为张三或者标题中包含Java的文档记录,查询结果肯定有三条:
接下来将上面的should条件修改为must,其余保持不变则就将或的关系变成与的关系,此时查询结果肯定为空,因为不存在作者为张三且标题中包含Java的文档记录:
再来看一个例子,查询单词数为8000且标题中包含Java的文档记录,此时的JSON信息为:
1 | { |
通过分析可以知道查询结果只有一条,如下所示:
与和或都介绍完了,现在来学习非,非就是must_not,也就是一定不能满足的条件。举个例子,查询不是余思的书,此时对应的JOSN格式信息为:
1 | { |
显然一共有12条记录,其中只有一条记录的作者是余思,因此作者不是余思的记录肯定的有11条。
那这样关于ElasticSearch的快速入门就先学习到这,后续将正式进入Elastic Stack的学习。