ElasticSearch实战与原理解析-阅读摘要

ElasticSearch前传

搜索技术发展史

正说搜索技术发展史

  搜索引擎发展经理了五个阶段和两大分类。

  五个阶段:FTP文件检索阶段、分类目录导航阶段、文本相关性检索阶段、网页链接分析阶段、用户意图识别阶段。

  两大分类:站外搜索、站内搜索。

ElasticSearch简介

  ElasticSearch是一个分布式、可扩展、近实时的高性能搜索与数据分析引擎。

  ElasticSearch提供了搜索、分析、存储数据三大功能,其主要特点有:分布式、零配置、易装易用、自动发现、索引自动分片、索引副本机制、RESTful风格接口、多数据源和自动搜索负载等。

  ElasticSearch基于Java编写,其内部使用Lucene做索引与搜索,通过进一步封装Lucene,向开发人员屏蔽了Lucene的复杂性。开发人员只需使用一套简单一致的RESTfulAPI即可。

  除此之外,ElasticSearch解决了检索相关数据、返回统计结果、响应速度等相关的问题。因此,ElasticSearch能做到分布式环境下的实时文档存储和实时分析搜索。实时存储的文档,每个字段都可以被索引与搜索。

  最令人惊喜的是,ElasticSearch能胜任上成百上千个服务节点的分布式扩展,支持PB级别的结构化或者非结构化海量数据的处理。

  2019年4月10日,ElasticSearch发布了7.0版本。该版本的重要特性包含引入内存断路器、引入ElasticSearch的全新集群协调层-Zen2、支持更快的前k个查询、引入Functionscore2.0等。

  其中内存断路器可以更精准的检测出无法处理的请求,并防止它们使单个节点不稳定;Zen2是ElasticSearch的全新集群协调层,提高了可靠性、性能和用户体验,使ElasticSearch变更更快、更安全,并更易于使用。

Lucene简介

  Lucene是一个免费、开源、高性能、纯Java编写的全文检索引擎。

  2005年,Lucene升级成为Apache顶级项目。

  Lucene包含大量相关项目,核心项目有Lucene Core、Solr和PyLucene。

  需要支持的是,Lucene仅仅是一个工具包,它并非一个完整的全文检索引擎,突出有点如下:

  • 索引文件格式独立于应用平台

  Lucene定义了一套以8位字节为基础的索引文件格式,能够兼容系统或者不同平台的应用能够共享创立的索引文件。

  • 索引速度快

  在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提升索引速度。然后通过与原有索引的合并,达到优化的目的。

  • 简单易学

  优秀的面向对象的系统架构,降低了Lucene扩展的学习难度,方便扩充新功能。

  • 跨语言

  设计了独立于语言和文件格式的文件分析接口,索引器通过接受token流完成索引文件的创立,用户扩展新的语言与文件格式,只需实现文本分析的接口即可。

  • 强大的查询引擎

  Lucene默认实现了一套强大的查询引擎,默认实现了布尔操作、模糊查询、分组查询等。

  Lucene主要模块有Analysis模块、Index模块、Store模块、QueryParser模块、Search模块和Similarity模块。

  ①   Analysis模块:主要负责词法分析及语言处理,也就是我们常说的分词,通过该模块可最终形成存储或者搜索的最小单位元Term。

  ②   Index模块:主要负责索引的创建工作。

  ③   Store模块:主要负责索引的读和写,主要是对文件的一些操作,其主要目的是抽象出和平台文件系统无关的存储。

  ④   QueryParser模块:主要负责语法分析,把查询语句生成Lucene底层可以识别的条件。

  ⑤   Search模块:主要负责对索引的搜索工作。

  ⑥   Similarity模块:主要负责相关性打分和排序的实现。

相关术语汇总如下:

  ①   Term:索引中最小的存储和查询单元。对于英文语境而言,一般是指一个单词;对于中文语境而言,一般是指一个分词后的词。

  ②   词典:是Term的集合。词典的数据结构有很多种,各有优缺点。如可以通过排序数据(通过二次查找来检索数据)、HashMap(哈希表,检索速度更快,属于空间换时间的模式)、FST(Finite-State Transducer,有很好的压缩率)等来实现。

  ③   倒排表:一篇文章通常由多个词组成,倒排表记录的是某个词在哪些文章中出现过。

  ④   正向信息:原始的文档信息,可以用来做排序、聚合、展示等。

  ⑤   段:索引中最小的独立存储单元。一个索引文件由一个或者多个段组成。在Lucene中,段有不变性,段一旦生成,在段上只能读取、不可写入。

知识关联点

  • REST定义

  REST(Representational State Transfer,表达性状态转移)是所有Wen应用都应该遵守的架构设计指导原则。

  • REST关键原则

  ①   Resource(资源)表示为所有事物定义ID。

  ②   Hypertext Driven(超文本驱动)表示将所有事物链接在一起。

  ③   Uniform Interface(统一接口)表示使用标准方法。

    •   7个HTTP方法:POST、GET、PUT、DELETE、PATCH、HEAD、OPTIONS。
    •   HTTP头信息(可自定义)。
    •   HTTP响应状态代码(可自定义)。
    •   一套标准的内容协商机制。
    •   一套标准的缓存机制。
    •   一套标准的客户端身份认证机制。

  ④   Representation(资源的表述)表示资源多重表达的方式。

  ⑤   State Transfer(状态转移)表示无状态的通信。

搜索技术基本知识

数据搜索方式

  数据有两种类型:即结构化数据与非结构化数据。

  与数据形态相对应的,数据的搜索分两种,即结构化数据搜索与非结构化数据搜索。

  结构化数据可以基于关系型数据库来存储,而关系数据库往往支持索引,因此结构化数据可以通过关系数据库来完成搜索与查找。

  对于非结构化数据,数据的搜索主要有顺寻扫描和全文见搜两种方法。顺序扫描时效率很低,因此全文检索技术应运而生。

  在实现全文检索的过程中,一般都需要提取非结构化数据中的有效信息,重新组织数据的承载结构形式。而搜索数据时,需要基于新结构化的数据展开,从而达到提供搜索速度的目的。显而易见,全文检索是一种空间换时间的做法——前期进行数据索引的创建,需要花费一定的时间和空间,但能显著提高后其搜索的效率。

搜索引擎工作原理

通用搜索引擎的工作原理如图:

 

 

 

         搜索引擎的工作原理分为两个阶段,即网页数据爬取和索引阶段、搜索阶段。其中网页数据爬取和索引阶段包含网络爬虫、数据预处理、数据索引三个主要动作,搜索阶段包含数据关键词、输入内容预处理、搜索关键词查询三个主要动作。

         常见的预处理动作有去除噪声内容、关键词处理(如中文分词、去除停止词)、网页间链接关系计算等。

         数据预处理后,要进行数据索引过程。索引过程先后经历正向索引和倒排索引阶段,最终建立索引库。

倒排索引

         正排索引的数据结构如图:

 

 

 

         在正排索引种,以网页或文档映射关系为key、已分词的列表value。而在实际搜索网页或文章时恰恰与此结构相反,即在搜索时是以查询语句的分词列表为key来进行搜索的。因此,为了提高搜索效率,我们需要对正排索引进行转化,将其转化为分词为key、以网页或文章列表为value的结构,而这个结构就是倒排索引。

         倒排索引的数据结构如图:

 

 

 

         在倒排索引中,有词条(Term)、词典(Term Dictionary)、倒排表(Post List)三个名词。

  • 词条时索引里面最小的存储和查询单元。一般来说,在英文语境中词条是一个单词,在中文语境中词条指的是分词后的一个词组。
  • 词典又称字段,是词条的集合。单词词典一般是由网页或文章集合中出现过的所有词构成的字符串集合。
  • 倒排表记录的是词出现在哪些文档里、出现的位置和频率等。

  在倒排表中,每条记录被称作一个倒排项。

  Elasticsearch是基于Lucene实现的。在Lucene中,词典和倒排表是实现快速检索的重要基石。另外,词典和倒排表是分两部分存储的,词典存储在内存中,倒排表存储在磁盘上。

结果排序

  搜索结果排序是搜索引擎查询服务的核心所在排序结果决定了搜索引擎体验的好与坏、用户的使用满意度和搜索引擎的口碑。

  搜索结果的排序算法也是不断迭代发展的,早期主要基于查询词出现的频率来排序,随后出现了PageRank和相关性等算法排序。

  当前,在目前的搜索引擎中,还都不约而同引入了用户行为分析、数据挖掘等技术,来提升搜索结果的质量。

文档处理过程解析

文档的索引过程

  写入磁盘的倒排索引是不变的。Elasticsearch主要基于一下几个考量:

  (1)      读写操作轻量级,不需要锁。如果Elasticsearch从来不需要更新一个索引,则就不必担心多个程序同时尝试修改索引的情况。

  (2)      一旦索引被读入文件系统的内存,它就会一直在那里,因为不会改变。此外,当文件系统内存足够大的空间时,大部分的索引读写操作是可以直接访问内存,而不是磁盘就能实现的,显然这有助于提升Elasticsearch的性能。

  (3)      当写入单个大的倒排索引时,Elasticsearch可以压缩数据,以减少磁盘I/O和需要存储索引的内存大小。

  倒排索引的不可变性也是一把“双刃剑”,不可变的索引也有它的缺点。首先就是它不可变,用户不能改变它。如果想要搜索一个新文档,则必须重建整个索引。这不仅严重限制了一个索引所能装下的数据,还限制了一个索引可以被更新的频率。

  那么Elasticsearch是如何在保持倒排索引不可变好处的同时又能更新倒排呢?答案是:使用多个索引。

  Elasticsearch不是重写整个倒排索引,而是增加额外的索引反映最近的变化。每个倒排索引都可以按顺序查询,从最“老旧”的索引开始查询,最后把结果聚合起来。

  Elasticsearch的底层依赖Lucene,Lucene中的索引其实是Elasticsearch中的分片,Elasticsearch中的索引是分片的集合。当Elasticsearch搜索索引时,它发送查询请求给该索引下的所有分片,然后过滤这些结果,最后聚合成全局的结果。

  为了避免混淆,Elasticsearch引入了per-segment search的概念。一个段(segment)就一个是有完整功能的倒排索引。Lucene中的索引指的是段的集合,再加上提交点(common point,包括所有段的文件)。新的文件在被写入磁盘的段之前,需要先写入内存区的索引。

  一个per-segment search的工作流程如下所示:

  (1)      新的文档首先被写入内存区的索引。

  (2)      内存中的索引不断被提交,新段不断产生。当新的提交点产生时就将这些新段的数据写入磁盘,包括新段的名称。写入磁盘是文件同步写入的,也就是说,所有的写操作都需要等待文件系统内存的数据同步到磁盘,确保它们可以被物理写入。

  (3)      新段被打开,于是它包含的文档就可以被检索到。

  (4)      内存被清除,等待接收新的文档。

  当一个请求被接收,所有段一次被查询时。所有段上的term统计信息会被聚合,确保每个term和文档的相关性被正确计算。通过这种方式,新的文档就能够以较小的代价加入索引。

  段是不可变的,那么Elasticsearch是如何删除和更新文档数据的呢?

  段的不可变特性,意味着文档既不能从旧的段中移除,旧的段中的文档也不能被更新。于是Elasticsearch在每一个提交点都引入一个.del文件,包含了段上已经被删除的文档。

  当一个文档被删除时,它实际上只是在.del文件中被标记为删除。在进行文档查询时,被删除的文档依然可以被匹配查询,但是在最终返回之前会从结果中删除。

  当一个文档被更新时,旧版本的文档会被标记为删除,新版本的文档在新的段中被索引。当对文档进行查询时,改文档的不同版本都会匹配一个查询请求,但是较旧的版本会从结果中被删除。

  被删除的文件越积累越多,每个段消耗的如文件句柄、内存、CPU等资源越来越大。如果每次搜索请求都需要依次检查每个段,则段越多,查询就越慢。这些势必会影响Elasticsearch的性能,那么Elasticsearch是如何处理的呢?Elasticsearch引入了段合并段。在段合并时,我们会展示被删除的文件是如何从文件系统中清除的。

  Elasticsearch通过后台合并段的方式解决了上述问题,在段合并过程中,小段被合并成大段,大段再合并成更大的段。再合并段时,被删除的文档不会被合并到大段中。

  在索引过程中,refresh会创建新的段,并打开它。合并过程是在后台选择一些小的段,把它们合并成大的段。在这个过程中不会中断索引和搜索。当新段合并后,即可打开供搜索;而旧段会被删除。

  需要指出的是,合并大的段会消耗很多I/O和CPU。为了不影响Elasticsearch的搜索性能,在默认情况下,Elasticsearch会限制合并过程,这样搜索就可以有足够的资源进行了。

  除自动完成段合并外,Elasticsearch还提供了optimize API,以便根据需要强制合并段。Optimize API强制分片合并段以达到指定max_num_segments参数,这样减少段的数量(通常为1),达到提高搜索性能的目的。

  需要指出的是,不要在动态的索引上使用optimize API。Optimize API的典型场景是记录日志。日志是按照每天、周、月存入索引的。旧的索引一般是只可读,不可修改的。在这种场景下,用户主动把每个索引的段降至1是有效的,因为搜索过程会用到更少的资源,性能更好。

Elasticsearch文档分片存储

  一个索引一般由多个分片构成,当用户执行添加、删除、修改文档操作时,Elasticsearch需要决定把这个文档存储在哪个分片上,这个过程就称为数据路由。

  当把文档路由到分片上时需要使用路由算法,Elasticsearch中的路由算法如下所示:

shard = hash(routing) % number_of_primary_shards

 

下面通过实例示例展示路由到分片的过程。

  假设某个索引由3个主分片组成,用户每次对文档进行增删改查时,都有一个routing值,默认是该文档的ID的值。随后对这个routing值使用Hash函数进行计算,计算出的值再和主分片个数取余数,余数的取值范围永远是(0~ number_of_primary_shards~1)之间,文档知道应该存储再哪个对应的分片上。

  需要指出的是,虽然routing值默认是文档ID的值,但Elasticsearch也支持用户手动指定一个值。手动指定对于负载均衡及提升批量读取的性能有一定的帮助。

  正是Elasticsearch的这种路由机制,主分片的个数在索引建立之后不能修改。因为修改索引主分片数目会直接导致路由规则出现严重问题,部分数据将无法被检索。

  那么读者每次对文档进行增删改查时,主分片和副本分片是如何工作的呢?下面进行说明。

  假设读者本机有三个节点的集群,该集群包含一个名叫niudong的索引,并拥有两个主分片,每个主分片有两个副本分片。

  在Elasticsearch中,出于数据安全和容灾等因素考虑,相同的分片不会放在同一个节点上,所以此时的集群如图:

 

 

 

 

 

 

 

  有一个标记为主节点的节点,也称之为请求节点。一般来说,用户能够发送请求给集群中的任意一个节点。每个节点都有能力处理用户提交的任意请求,每个节点都知道任意文档所在的节点,所以也可以将请求转发到需要的节点。

  当用户执行新建索引、更新和删除请求等写操作时,文档必须在主分片上成功完成请求,才能复制到相关的副本分片上,分片数据同步过程如下图:

 

 

 

 

客户端发送了一个索引或者删除的请求给主节点1。此时,主节点会进行如下处理:

(1)      主节点1通过请求中文档的ID值判断出文档应该被存储在哪个分片上,如现在已经判断出需要分片编号为0的分片在节点3中,则主节点1会把这个请求转发到节点3处。

(2)      节点3在分片编号为0的主分片上执行请求。如果请求执行成功,则节点3将并行的将该请求发给分片编号为0的所有副本上,即上图位于主节点1和节点2中的副本分片。

如果所有的副本分片都成功地执行了请求,那么将向节点3回复一个请求执行成功的确认消息。当节点3收到所有副本节点的确认信息后,会向客户端返回一个成功的响应信息。

(3)      当客户端收到成功的响应信息时,文档的操作就已经被应用于主分片和所有的副本分片上,此时操作就会生效。

Elasticsearch的数据分区

  一般来说,搜索引擎有两种数据分区方式,即基于文档的分区方式和基于词条的分区方式。Elasticsearch使用的是基于文档的分区方式。

  基于文档的分区(Document Based Partitioning)指的是每个文档只存一个分区,每个分区持有整个文档集的一个子集。这里说的分区是指一个功能完整的倒排索引。

  基于文档的分区的优点汇总如下:

  (1)      每个分区都可以独立地处理查询。

  (2)      可以非常方便地添加以文档为单位的索引信息。

  (3)      在搜索过程中网络开销很小,每个节点可以分别独立地执行搜索,执行完之后只需返回文档的ID和评分信息即可。而呈现给用户的结果集是在执行分布式搜索的节点上执行合并操作实现的。

  基于文档的分区的缺点也是很明显,如果查询需要在所有的分区上执行,则它将执行O(K×N)次磁盘操作(K是词条term的数量,N是分区的数量)。

  从实用性角度来看,基于文档的分区方式已经被证明是一个构建大型的分布式信息检索系统的行之有效的方法。因此Elasticsearch使用的是基于文档的分区方式。

  基于词条的分区(Term Based Partitioning)指的是每个分区拥有一部分词条,词条里面包含了与该词条相关的整个index的文档数据。

  目前也有一些基于词条分区的搜索引擎系统,如Riak Search有、Lucandra和Solandra。

  基于词条的分区的优点汇总如下:

  (1)      部分分区执行查询。一般来说,用户只需要在很小的部分分区上执行查询就可以了。举个例子,假如用户有3个term词条的查询,则Elasticsearch在搜索时将至多命中3个分区。如果足够幸运,这3个term词条都保存同一个分区中,那么用户只需访问一个分区即可。在搜索的背后,用户无需知道Elasticsearch中实际的分区数量。

  (2)      时间复杂度低。一般而言,当对应K个term词条的查询时,用户只需执行O(K)次磁盘查询即可。

基于词条的分区的缺点汇总如下:

  (1)      最主要的问题是Lucene Segment概念里面有很多固有的结果都将失去。

对于比较复杂的查询,搜索过程中的网络开销将变得非常高,并且可能使得系统可用性大大降低,特别是注入前置搜索或模糊搜索的场景。

  (2)      获取每个文档的信息将会变得非常困难。由于是按词条分区存储的,如果用户想获取文档的一部分数据做进一步的控制,或获取每个文档的这些数据,都将变得非常困难,因为这种分区方式使得文档的数据被分割到了不同的地方,所以实现注入评分、自定义评分等都将变得难以实现。

搜索过程解析

对已知文档的搜索

如果被搜索的文档(不论是单个文档,还是批量文档)能够从主分片或任意一个副本分片中被检索到,则与索引文档过程相同,对已知文档的搜索也会用到路由算法,elasticsearch中的路由算法如下所示:

shard = hash(routing) % number_of_primary_shards

下图为例,展示在主分片上搜索一个文档的必要步骤。

 

 

 

(1)      客户端给主节点1发送文档的Get请求,此时主节点1就成为协同节点。主节点使用路由算法算出文档所在的主分片;随后协同节点将请求转发给主分片所在的节点2,当然,也可以基于轮询算法转发给副本分片。

(2)      主节点1根据文档的ID确认文档属于分片R0.分片R0对应的副本分片在三个节点上都有。此时,主节点1转发请求到节点2。

(3)      节点2在本地分片进行搜索,并将目标文档信息作为结果返给主节点1。

对于读请求,为了在各节点间负载均衡,请求节点一般会为每个请求选择不同的分片,一般采用轮询算法循环在所有副本分片中进行请求。

对未知文档的搜索

除对已知文档的搜索外,大部分请求实际上是不知道查询条件会命中哪些文档的。这些被查询条件命中的文档可能位于elasticsearch集群中的任意位置上。因此,搜索请求的执行不得不去询问每个索引的每一个分片。

在elasticsearch中,搜索过程分为查询阶段(query phase)和获取阶段(fetch phase)。

在查询阶段,查询请求会广播到索引中的每一个主分片和备份中,每一个分片都会在本地执行搜索,并在本地各建立一个优先级队列(priority queue)。该优先级队列是一份根据文档相关度指标进行排序的列表,列表的长度由from和size两个分页参数决定。

查询阶段可以再细分成3个小的子阶段:

(1)      客户端发送一个检索请求给某节点A,此时节点A会创建一个空的优先级队列,并配置号分页参数from和size。

(2)      节点A将搜索请求发送给该索引中的每一个分片,每个分片在本地执行搜索,并将结果添加到本地优先级队列中。

(3)      每个分片返回本地优先级序列中所记录的ID与sort值,并发送给节点A。节点A将这些值合并到自己的优先级队列中,并做出全局的排序。

在获取阶段,主要是基于上一阶段找到所要搜索文档数据的具体位置,将文档数据内容取回并返回给客户端。

在elasticsearch中,默认的搜索类型就是上面介绍的Query then Fetch。上述描述运作方式就是Query then Fetch。Query then Fetch有可能会出现打分偏离的情形,幸好,elastic search还提供了一个称为“DFS Query then Fetch”的搜索方式,它和Query then Fetch基本相同,但是它会执行一个预查询来计算整体文档的frequency。其处理过程如下所示:

(1)      预查询每个分片,询问Term和Document Frequency等信息。

(2)      发送查询请求到每个分片。

(3)      找到各个分片中所有匹配的文档,并使用全局的Term/Document Frequency信息进行打分。在执行过程中依然需要对结果构建一个优先队列,如排序等。

(4)      返回关于结果的元数据到请求节点。需要指出的是,此时实际文档还没有发送到请求节点,发送的只是分数。

(5)      请求节点将来自所有分片的分数合并起来,并在请求节点上进行排序,文档被按照查询要求进行选择。最终,实际文档从它们各自所在的独立的分片上被检索出来,结果被返回给读者。

对词条的搜索

  回顾elasticsearch分别为每个文档的字段建立了一个倒排索引

  当要搜索中文词1时,通过倒排索引可以获悉,与待搜索中文词1的文档是中文网页1、中文网页2和中文网页3.

  当词条数据较少时,可以顺利遍历词条获取结果,但如果词条由成千上万个,elasticsearch为了能快速找到某个词条,它对所有的词条都进行了排序,随后使用二分法查找词条,其查找效率为log(N)。这个过程就像查找字典一样,因此排序词条的集合也称为Term Dictionary。

  为了提高查询性能,Elasticsearch直接通过内存查找词条,而非从磁盘中读取。但当词条太多时,显然Term Dictionary也会很大,此时全部放在内存有些不现实,于是引入了Term Index。

  Term Index就像字典中的索引页,其中的内容如字母A开头的有哪些词条,这些词条分别在哪页。通过Term Index,Elastinsearch可以快速地定位到Term Dictionary地某个OffSet(位置偏移),然后从这个位置再往后顺序查找。

  前面提及了单个词条地搜索方法,而在实际应用中,更常见地往往是多个词条拼接成地“联合查询”,那么Elasticsearch是如何基于倒排索引实现快速查询地呢?

  核心思想是利用跳表快速做“与”计算,还有一种方法是利用BitSet(位图)按位“与”运算。

  先来看跳表快速做“与”运算地方式,首先介绍跳表,如下图所示:

 

 

 

  跳表由多级链表组成,上一级是下一级元素地子集,因此上一级往往数据较少。在查找时,数据从上级向下级逐级查找/如查找数据36,从第三级中找到元素11,从第二级中到数据32,从第三级中的数据32之后查找到36,总共用了3次查找。

  那么跳表是如何用在多词条索引中地呢?

  通过倒排索引,每个词条都会有一个命中地文档ID列表(如果能命中地话),此时,我们可以找到这些文档ID列表中最短地那一个。接下来,用最短的文档ID列表中的ID逐个在其他文档ID列表中进行查找,都能找到的ID即为多个词条的交集结果。

  再来看BitSet按位“与”运算的方式。

  BitSet中的每一位只有两个可能取值,即0或1。如果数据存在,则对应的标记置为1,否则置为0。

  因此,将词条的文档ID列表转化为位图后,将多个词条对应的位图取“与”运算,即可得到交集结果。

索引原理解析

近实时搜索的实现

文档被索引动作和搜索该文档动作之间是有延迟的,因此,新的文档需要在几分钟后方可被搜索到,但这依然不够快,其根本原因在于磁盘。

在elasticsearch中,当提交一个新的段到磁盘时需要执行fsync操作,以确保段被物理地写入磁盘,即使断电数据也不会丢失。不过,fsync很消耗资源,因为它不能在每个文档被索引时都触发。

位于Elasticsearch和磁盘间地时文件系统缓存。内存索引缓存中的文档被写入新段的过程的资源消耗很低;之后文档会被同步到磁盘,这个操作的资源消耗很高。而一个文件一旦被缓存,它就可以被打开和读取。因此,elasticsearch利用了这一特性。

Lucene允许新段在写入后被打开,以便让段中包含的文档可被搜索,而不用执行一次全量提交。这是一种比提交更轻量的过程,可以经常操作,且不会影响性能。

在elasticsearch中,这种写入打开一个新段的轻量级过程,叫做refresh。在默认清空下,每个分片每秒自动刷新一次。这就是认为Elasticsearch是近实时搜索,但不是实时搜索的原因,即文档的改动不会立即被搜索,但是会在一秒内可见。

倒排索引的压缩

Elasticsearch为每个文档中的字段分别建立了一个倒排索引。

随着文档的不断增加,倒排索引中的词条和词条对应的文档ID列表会不断增大,从而影响Elasticsearch的性能。

Elasticsearch对词条采用了Term Dictionary和Term Index的方式来简化词条的存储和查找;同事,Elasticsearch对词条对应的文档ID列表进行了必要的处理。

Elasticsearch是如何处理这些文档ID列表的呢?答案很简单,即通过增量编码压缩,将大数变小数。按字节压缩。

为了有效进行压缩,词条对应的文档ID列表是有序排序的。所谓增量编码,就是将原来的大数变小数,仅存储增量值,示例如下:

假如原有的文档ID列表为:10 23 34 66 100 178

增量编码后文档ID列表为:10 13 11 32 34  78

所谓按字节存储,即查看增量编码后的数字可以用几位字节存储,而非全部用int(4字节)存储,从而达到压缩和节省内存的目的。当然,为了节省更多的内存,还可以对增量编码后的文档ID列表进行分组,再分别计算每一个存储需要的字节数。

数据刷新方式

在Elasticsearch中,有两种数据刷新操作:Refresh和Flush。

Refresh

当我们索引文档是,文档是存储在内存中的,默认1s后会进入文件系统缓存。Refresh操作本质上是对Lucense Index Reader调用了ReOPen操作,即对此时索引中的数据进行更新,使文档可以被用户搜索到。

不过,此时文档还未存储到磁盘上,如果Elasticsearch的服务器宕机了,那么这部分数据就会丢失。如果要对这部分数据进行持久化,则需要调用消耗较大的Luncene Commit操作。因此,Elasticsearch采用可以频繁调用轻量级的ReOpen操作来达到近实时搜索的效果。

Flush

虽然Elasticsearch采用了可以频繁调用轻量级的ReOpen操作来达到近实时搜索的效果,但数据终究是要持久化的。

Elasticsearch在写入文档时,会写一份translog日志,基于translog日志可以恢复那些丢失的文档,在出现程序故障或磁盘异常时,保障数据的安全。

Flush可高效地触发Lucene Commit,同时清空translog日志,使数据在Lucene层面持久化。

posted @ 2021-10-07 14:51  A点点圈圈A  阅读(152)  评论(0)    收藏  举报