Elasticsearch学习笔记-说明篇
共 7577字,需浏览 16分钟
·
2021-07-31 03:20
Elasticsearch是一款分布式、RESTful 风格的搜索和数据分析引擎,已经被越来越多的企业广泛使用。
随着ES的稳步发展,ES现如今支持的功能也越来越强大。最近这几天正好有时间好好学习一下ES,在这里整理几篇相关文章。一来为自己整理一下学习笔记,二来希望这几篇笔记能够帮助到刚入门学习ES的读者。
ES的学习笔记主要分为以下三篇:
说明篇:简单介绍一下Elasticsearch,搜索技术的基本知识以及ES相关架构设计。
安装篇:利用ES压缩包或docker-compose安装ES使用环境,几种比较常用的分词器,及kibana安装。
使用篇:利用kibana和Java中的RestClientHighLevel实现一些基础和高级的查询。例如:索引创建,删除,数据查询聚合等相关操作。
初步设想是读者可以根据这三篇学习笔记自己能够上手使用ES,包含简单的使用,查询,聚合等。未来自己在不断学习的过程中也会随时整理一些相关内容,希望大家多多关注。
好了其它的不多说,下面开始这篇笔记的正文,说明篇。
Elasticsearch简介
Elasticsearch(以下简称ES)是一个分布式,可扩展,近实时的高性能搜索和数据分析引擎。所以ES不仅能够存储,检索数据,还能对数据进行统计分析。
ES是基于Java编写,底层使用Lucene做索引与搜索。通过对Lucene的封装,屏蔽了Lucene的复杂性。并且ES支持简单的RESTful API,无论你使用哪种开发语言,都能让使用变得更简单。
ES的特点和优势:
分布式实时文件存储:ES可以将被索引文档中的每一个字段存入索引,以便字段可以被检索到。
实时分析的分布式搜索引擎:ES的索引被拆分成多个分片,每个分片可以有0或多个副本。而集群中的每个数据节点都可以承载一个或多个分片,并且协调处理各种操作,负载均衡和路由则会自动完成。
高可拓展性:ES不仅可以运行在单台PC机上,也可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。
可插拔插件支持:ES支持多种插件,例如:分词插件,同步插件,可视化插件等,这些都可以选择性安装。
Lucene简介
提到ES就不得不说下Lucene。Lucene是一个免费,开源,高性能,纯Java编写的全文检索引擎工具包。
值得一提的是,Lucene仅仅是一个工具包,并不是一个完整的全文搜索引擎。Lucene的初衷是为了给开发者提供一个好用的工具包,主要提供倒排索引的查询结构。
搜索技术基本知识
提到搜索,需要提及一下数据搜索方式,而数据主要分为结构化数据和非结构化数据两种。
结构化数据:一般会存储在关系型数据库中,较常用的就是MySQL,Oracle等,数据有固定的数据格式和有限个数的字段。
非结构化数据:一般会存储在MongoDB这类文档型数据库中,因为非结构化数据长度不定且无固定数据格式。而这类数据存储在关系型数据库中较为困难。
为此,在搜索方面也分为结构化数据搜索和非结构化数据搜索。
结构化数据搜索:由于结构化数据可以存储在关系型数据库中,所以一般来说,结构化数据搜索就借助关系型数据库,因为关系型数据库支持索引,索引查找起来会比较方便。常用方式就是借助like关键字,进行左匹配(like xxx%),右匹配(like %xxx),模糊匹配(like %xxx%)。
非结构化数据搜索:搜索主要有顺序扫描和全文检索两种方法。因为顺序扫描效率非常低下,所以全文检索技术应运而生,而搜索引擎主要的目的就是在做这样的事情。
下面简单介绍一下搜索引擎的工作原理。
搜索引擎工作分为两个阶段,即数据准备阶段,和搜索阶段。
数据准备阶段:通过网页数据爬取(一般使用网络爬虫)将网页信息存储在网页库中,再通过一些常见的预处理方式(例如:去除噪声内容,关键词处理,分词,网页链接计算等)将数据进行转化。转化之后,再将数据进行正向索引,倒排索引阶段处理,最终建立索引库,将数据保存。
搜索阶段:搜索阶段是指由用户主动发起的请求。通过用户输入的关键字进行预处理(主要包含去重,去空格,删除标点,拼写错误检查等)后,进行相关分词。分词后,搜索引擎向索引库发起搜索请求,索引库会将包含该分词结果的网页信息进行整理排序,最终返回给用户。
倒排索引
倒排索引的名词在ES中比较常见,都知道ES是基于倒排索引来进行数据搜索的,那么什么是倒排索引呢?
一般来说,在建立索引的过程中均涉及正排索引和倒排索引两种,下面简单介绍一下:
正排索引:顾名思义,正排索引就是将网页或文章映射关系作为Key、以分词后的列表为Value。(例如:一篇网页中包含中文词1,中文词2,和中文词3,那么正排索引的结果就是中文网页1 -> 中文词1 中文词2 中文词3)这就是最常见的正排索引。但是往往我们在实际搜索网页或文章时恰恰与此结构相反,所以倒排索引应运而生。
倒排索引:在正排索引基础上进行转化,转化为以分词结果为Key,以网页或文章列表为Value的格式。(例如:上述所说一篇网页包含中文词1,中文词2,中文词3,那么倒排索引的结构就如下所示:
中文词1 -> 中文网页1
中文词2 -> 中文网页1
中文词3 -> 中文网页1)
按照上述的文字说明,大家不难发现。有了倒排索引,能够大大提高文章或网页的检索效率。因为用户可以直接通过关键词定位到相关网页或文章,这就是倒排索引。
当然真正的倒排索引,不会只存储网页或文章内容,还会存储更多信息。例如词汇出现位置,频率等,这些都会在搜索时用到。除此之外,倒排索引还有词条(Term),词典(Term Dictionary),倒排表(Post List)三个名词。
词条:是索引中最小的存储和查询单元。一般是指分词后的一个词组。
词典:也叫字典,顾名思义是词条的集合,是出现所有词条的字符串集合。
倒排表:记录词条出现在哪些文档或网页中,记录出现的位置和频率等相关信息,而倒排表中的每条记录称为一个倒排项。
词典和倒排表是实现快速检索的基础。此外,词典和倒排表是分开存储的,词典存储在内存中,而倒排表存储在磁盘上。
分词器
分词器的目的就是将单词或词组进行拆分。一般来说,英文具有天然的优势,因为每个英文单词都是通过空格进行分词的,然而对于中文来说,分词可能就不会像英文这么简单了。例如:对于“中国人”这个词进行分词,不同分词器可能分词的结果就不相同,有的分词器可能会拆分成,中国,人。有的分词器可能会拆分成,中国,中国人,国人等等。
所以对于中文分词器来说,每种分词器的分词效果可能是不同的,但是无法说哪种分词器效果最好,只能说哪种分词器更适合自身的业务需求。而ES本身自带的分词器,对中文并不友好,只能将中文逐个拆分,在很多时候造成索引浪费。为此很多分词器插件就出现了,这些插件不仅对中文分词非常友好,而且支持很多复杂语境。下面简单介绍几种分词器。
IK分词器:IK分词器是一个开源的,基于java语言开发的轻量级的中文分词工具包。支持文本细粒度拆分(ik_max_word)和粗粒度拆分(ik_smart)。应该是如今比较好用的中文分词器之一。git项目地址(https://github.com/medcl/elasticsearch-analysis-ik)。
Ansj中文分词器:基于n-Gram+CRF+HMM算法实现,用Java实现。分词速度可以达到大约200万字/s,准确率达96%以上。适用于对分词效果要求较高的项目。目前支持ToAnalysis(精准分词),DicAnalysis(用户自定义词典优先策略分词),NlpAnalysis(带有新词发现功能的分词),IndexAnalysis(面向索引的分词),BaseAnalysis(最小颗粒度分词)几种。git项目地址(https://github.com/NLPchina/elasticsearch-analysis-ansj)。
Jcseg中文分词器:基于MMSEG算法的一个轻量级中文分词器,Java实现。目前支持简易模式,复杂模式,检测模式,最多模式,分隔符模式,NLP模式,n-gram模式。除此之外还支持多种分词功能特性。详情可参考git项目地址(https://github.com/lionsoul2014/jcseg)。
总的来说,分词器的目的就是能够让ES更好的对中文进行分词,所以在使用上读者只需选择一种最适合自身业务的分词器即可。
ES架构设计及核心概念
ES核心概念
ES中有很多核心概念,想要真正学好,用好ES,首先要了解其核心概念。ES的核心概念主要有:Node,Cluster,Shards,Replicas,Index,Type,Document,Settings,Mapping和Analyzer。其对应含义分别如下:
Node:节点。是组成ES集群的基本数据单元,集群中每个运行中的ES服务器都可以称之为节点,
Cluster:集群。ES的集群是由具有相同cluster.name的一个或者多个ES节点组成的。每个节点协同工作,共享相同数据。同一个集群,节点名字不能重复,但是集群名称一定要相同,否则无法加入同一集群。
在ES集群中,节点状态有Green,Yellow,Red三种,含义如下:
Green:绿色。表示节点运行状态健康,所有的主分片和副本分片都可以正常工作,集群100%可用。
Yellow:黄色。表示节点运行状态预警,所有的主分片都可以正常工作,但是至少有一个副本分片是不能正常工作的。此时集群仍然可用,但是高可用性在此状态下被弱化。
Red:红色。表示集群无法正常使用。集群中至少有一个分片的主分片和它的全部副本分片都不能正常工作。如果出现此状态则需要关注问题原因,及时修复,否则集群虽然还能正常查询,但是不能正常工作的分片无法返回和存储数据,导致数据丢失。
Shards:分片。当索引数据量太大时,由于单个节点的内存,磁盘空间等受限,节点需要将索引上的数据进行水平拆分。那么拆分出来的每个数据部分就称之为一个分片。一般来说,每个分片都会放在不同服务器上,使集群能够快速响应客户端请求。
分片又分为主分片和副本分片,其中主分片是必须有的,副本分片可以没有,默认情况下ES为一个索引创建5个主分片,并且为每个主分片创建一个副本分片。注意:副本分片一般来说不承担查询的任务。
Replicas:备份。也可以称之为副本。副本的含义就是上面提到的主分片的备份。ES中的副本是精确复制模式,即每个副本都包含主分片上的所有数据。当在索引中写入数据时,首先会在主分片上写入数据,然后数据会从主分片分发到备份分片上进行写入。当主分片不可用时,ES会在备份分片中选举出一个分片作为主分片。实现高可用。当然副本也是一把双刃剑,过多的副本数量会增加数据同步的开销。设置适合的副本数量即可。
Index:索引。ES中索引由一个或多个分片组成。使用时需要通过索引名称在集群内进行唯一标识。
Type:类别。指的是索引内的逻辑分区。通过Type的名字在索引内唯一标识查询数据,如果没有Type则认为是整个索引内查询。需要注意的是,ES在7.x之后已经不再推荐使用Type属性,未来的8.x版本可能会彻底废弃掉Type属性。
Document:文档。索引中每一条数据称作一个文档,可以理解为关系型数据库中的一条记录。一条文档可以通过_id和Type属性(7.x之后版本忽略)进行唯一标识。
Settings:配置信息。即对集群中索引的定义信息,例如索引的分片数,副本数等。
Mapping:表示索引中字段(Field)的存储类型,分词方式等配置,有点类似于关系型数据库中的表结构信息。
在ES中,Mapping如果没有特殊需求,是可以动态识别字段类型的,不需要手动创建Mapping。如果需要定义特殊属性时,例如:是否分词,使用其它分词器等,就需要手动设置Mapping。
Analyzer:字段分词方式定义。ES默认的标准Analyzer包含一个标准的Tokenizer和三个Filter,即Standard Token Filter,Lower Case Token Filter和Stop Token Filter。
ES架构设计
ES的设计架构大致如下所示:
由上图可以知道,ES架构自底向上分为五层,分别为:核心层,数据处理层,发现与脚本层,协议层,应用层。下面分别阐述相关内容。
核心层:从图中可以发现,ES的核心层就是Lucene,基于Lucene实现的。
数据处理层:主要是对数据的处理,常见的索引模块(对索引的新增,删除,更新等处理)、搜索模块(对数据常规查询,聚合等操作),映射模块(对数据字段映射等处理)
发现与脚本层:主要包含Discovery模块(对节点自动发现功能)。Script脚本模块,可以对数据进行脚本加工处理,目前ES支持Python和JavaScript等多种语言。第三方插件模块,例如上文提到的IK分词插件,SQL插件等,这些第三方插件都是在这个模块中处理的。
协议层:指ES的数据交互协议。目前ES支持Thrift,Memcache,HTTP三种,默认协议是HTTP。而JMX协议是指ES对Java的管理框架,用来管理ES应用。
应用层:指ES支持的API模式,ES的特色就是RESTful风格的API。
ES数据写入过程
最后,我们来说一下ES数据的写入过程。
首先ES的数据写入操作是在内存中执行的,数据会被分配到特定的分片和副本上,但最终数据需要存储到磁盘上进行持久化。
分段存储
索引数据在磁盘上是以分段形式存储的。
段其实是Lucene中的概念,在索引中,索引文件被拆分成多个子文件,每个子文件就称作段,每个段就是一个倒排索引的小单元。
段具有不可变性,即只要段写入磁盘后就不可以修改。
使用分段存储的好处
避免数据量过大导致索引过大,查询数据缓慢。如果全部索引都写在一个文件中,随着数据量增大,文件也会随之增大,大大降低了数据索引的速度。
避免在读写操作时使用锁,提升读写性能。提高并发。设想一下,采用分段存储的方式,能够保证用户没有缓存到磁盘上的段内容不被读取,此时只能写入,而一旦段写入磁盘后,就变为只读,不再允许使用,保证读写并发时数据不会出现错误,同时提高并发。
段存储过程
当分段被写入磁盘后,就会生成一个提交点,提交点意味着一个用来记录所有段信息记录的文件已经生成。由于段具有不可变性,所以一旦段生成提交点,就意味着只具有读权限,而无法再写入了。
当分段在内存中时,此时段具有只写权限,数据不断写入,此时由于段还没有生成提交点,所以还不具有读权限,所以此时用户索引是不会获取到内存中段记录的。
不得不说,ES这点设计真是精妙,即减少了锁的使用,还能大大提高并发。当然凡事都具有两面性,由于段的特殊性,所以在更新,删除索引数据时显得就不这么方便,下面会详细说明。
段数据新增
数据新增是最简单的,因为数据是最新的,所以只需要在当前索引新增一个段文件即可,无需考虑其它操作。
段数据删除
当删除数据时,由于段具有不可修改的特性,ES不会把文档从旧的段中删除,而是会创建一个.del的文件,在.del文件中会记录被删除文档的段信息。被标记删除的文档仍然可以被查询匹配到,但在数据返回前会根据.del文件中的内容进行过滤,将其从结果集中移除。
段数据更新
当更新数据时,由于段具有不可更新特性,所以数据更新无法对段进行操作。所以更新操作就变成两部操作结合,即先删除,再新增。同数据删除相同,ES会将旧的文档在.del文件中标记删除,然后将新版本文档缓存到新的段中,这样虽然旧文档和新文档都能被查询匹配到,但是在数据返回前旧文档就会被.del文件中标记删除的内容过滤掉,从结果集中移除。
综上所述,分段存储虽然可以不需要锁提升ES的读写性能,但是相对来说带来的代价就是存储空间的浪费,尤其在更新,删除数据比较频繁的情况下。此外,在查询出全部数据后,主节点还需要根据.del文件中的内容排除被标记删除的旧数据,随之带来的是对查询的负担。
延迟写策略
首先,在ES中索引写入磁盘的过程是异步的,因此,为了提升写的性能,ES并不是每新增一条记录就增加一个段到磁盘上,而是采用延迟写策略。
大体上延迟写策略分为以下几步:
新数据写入时,将其写入JVM内存中。
当达到默认时间或内存数据达到一定数量时触发刷新(Refresh)操作。
刷新操作将内存中数据生成到一个新的分段上并写入文件缓存系统。
最后刷新到磁盘中,生成提交点。
从上面几步来看,不难理解,当数据新增时,由于新增数据都是先存入JVM内存中,并不生成段,所以此时用户在查询数据时,这些记录是无法被查询匹配到的,只有当经历了步骤3之后,生成新的分段并写入文件缓存系统后,这些记录才能被查询匹配到。这也是ES能做到近实时查询的原因,默认情况下每个分片自动刷新时间是1s,这也是为什么有的时候,写入记录后前端并不是能够马上查询到,需要等待1s后才能匹配的原因。(相信很多读者在初期使用ES的时候都遇到过这样的问题。)
当然延迟写策略一定程度上提升了ES写的效率,但是面临的问题也随之而来,那就是数据丢失,设想当你的数据刚刚写入JVM内存后,还没有写入文件缓存系统并刷新到磁盘的时候,机器断电了。那么这部分数据很有可能面临丢失的风险,为此,ES提供了事务日志机制(Translog),事务日志用于记录所有还没有缓存到磁盘上的数据。所以在添加了事务日志机制后,每当有新数据写入JVM内存时,同时会追加到事务日志中一条记录,当内存中记录都刷新到磁盘后,这份事务日志也会随之清空。这样即使机器或机房突然断电,有了事务日志,记录也不会丢失。
段合并
最后来说说段合并,因为ES默认1s刷新一次,所以短时间内ES会生成大量的段文件,而段数量太多,带来的就是对资源的损耗,内存,CPU等都会受其影响,而且段越多,查询数据时需要索引的段文件就越多,大大降低了ES的搜索性能。
所以ES引入了段合并机制,段合并是在后台定期进行的,这些都是由ES控制将较小的段合并为大的段,大的段再合并为更大的段。值得一提的是,在段合并的过程中,已经删除的文档将会从段中删除,这也是上文提到的如果频繁更新,删除数据,短时间内会造成磁盘资源浪费的原因。段合并的过程计算量较大,并且对磁盘I/O消耗也较大,所以ES会对其进行资源限制,并不会影响查询操作。
以上就是Elasticsearch学习笔记第一篇-说明篇的全部内容,希望能对大家有所帮助。欢迎大家继续关注后续两篇学习笔记,共勉。