elasticsearch document的索引过程分析

elasticsearch专栏:https://www.cnblogs.com/hello-shf/category/1550315.htmlhtml

 

1、预备知识

1.一、索引不可变

看到这篇文章相信你们都知道es是倒排索引,不了解也不要紧,在个人另外一篇博文中详细分析了es的倒排索引机制。在es的索引过程当中为了知足一下特色,落盘的es索引是不可变的。java

1 不须要锁。若是历来不须要更新一个索引,就没必要担忧多个程序同时尝试修改。 
2 一旦索引被读入文件系统的缓存(译者:在内存),它就一直在那儿,由于不会改变。只要文件系统缓存有足够的空间,大部分的读会直接访问内存而不是磁盘。这有助于性能提高。
3 在索引的声明周期内,全部的其余缓存均可用。它们不须要在每次数据变化了都重建,使文本能够被搜索由于数据不会变。 写入单个大的倒排索引,能够压缩数据,较少磁盘IO和须要缓存索引的内存大小。 

固然,不可变的索引有它的缺点,首先是它不可变!你不能改变它。若是想要搜索一个新文 档,必须重建整个索引。这不只严重限制了一个索引所能装下的数据,还有一个索引能够被更新的频次。因此es引入了动态索引。数组

 

1.二、动态索引

下一个须要解决的问题是如何在保持不可变好处的同时更新倒排索引。答案是,使用多个索引。不是重写整个倒排索引,而是增长额外的索引反映最近的变化。每一个倒排索引均可以按顺序查询,从最老的开始,最后把结果聚合。 Elasticsearch底层依赖的Lucene,引入了 per-segment search 的概念。一个段(segment)是有完整功能的倒排索引,可是如今Lucene中的索引指的是段的集合,再加上提交点(commit point,包括全部段的文件),如图1所示。新的文档,在被写入磁盘的段以前,首先写入内存区的索引缓存。而后再经过fsync将缓存中的段刷新到磁盘上,该段将被打开即段落盘以后开始能被检索。缓存

 

 看到这里若是对分段仍是有点迷惑,不要紧,假如你熟悉java语言,ArrayList这个集合咱们都知道是一个动态数组,他的底层数据结构其实就是数组,咱们都知道数组是不可变的,ArrayList是动过扩容实现的动态数组。在这里咱们就能够将commit point理解成ArrayList,segment就是一个个小的数组。而后将其组合成ArrayList。假如你知道Java1.8的ConcurrentHashMap的分段锁相信你理解这个分段就很容易了。安全

 

1.三、几个容易混淆的概念

在es中“索引”是分片(shard)的集合,在lucene中“索引”从宏观上来讲就是es中的一个分片,从微观上来讲就是segment的集合。性能优化

“document的索引过程”这句话中的这个“索引”,咱们能够理解成es为document简历索引的过程。数据结构

 

2、document索引过程

 

文档被索引的过程如上面所示,大体能够分为 内存缓冲区buffer、translog、filesystem cache、系统磁盘这几个部分,接下来咱们梳理一下这个过程。elasticsearch

阶段1:性能

这个阶段很简单,一个document文档第一步会同时被写进内存缓冲区buffer和translog。优化

阶段2:

refresh:内存缓冲区的documents每隔一秒会被refresh(刷新)到filesystem cache中的一个新的segment中,segment就是索引的最小单位,此时segment将会被打开供检索。也就是说一旦文档被刷新到文件系统缓存中,其就能被检索使用了。这也是es近实时性(NRT)的关键。后面会详细介绍。

阶段3:

merge:每秒都会有新的segment生成,这将意味着用不了多久segment的数量就会爆炸,每一个段都将十分消耗文件句柄、内存、和cpu资源。这将是系统没法忍受的,因此这时,咱们急需将零散的segment进行合并。ES经过后台合并段解决这个问题。小段被合并成大段,再合并成更大的段。而后将新的segment打开供搜索,旧的segment删除。

阶段4:

flush:通过阶段3合并后新生成的更大的segment将会被flush到系统磁盘上。这样整个过程就完成了。可是这里留一个包袱就是flush的时机。在后面介绍translog的时候会介绍。

不要着急,接下来咱们将以上步骤拆分开来详细分析一下。

 

2.一、近实时化搜索(NRT)

在早起的lucene中,只有当segement被写入到磁盘,该segment才会被打开供搜索,和咱们上面所说的当doc被刷新到filesystem cache中生成新的segment就将会被打开。

由于 per-segment search 机制,索引和搜索一个文档之间是有延迟的。新的文档会在几分钟内能够搜索,可是这依然不够快。磁盘是瓶颈。提交一个新的段到磁盘须要 fsync 操做,确保段被物理地写入磁盘,即时电源失效也不会丢失数据。可是 fsync 是昂贵的,它不能在每一个文档被索引的时就触发。

因此须要一种更轻量级的方式使新的文档能够被搜索,这意味这移除 fsync 。

位于Elasticsearch和磁盘间的是文件系统缓存。如前所说,在内存索引缓存中的文档被写入新的段,可是新的段首先写入文件系统缓存,这代价很低,以后会被同步到磁盘,这个代价很大。可是一旦一个文件被缓存,它也能够被打开和读取,就像其余文件同样。

在es中每隔一秒写入内存缓冲区的文档就会被刷新到filesystem cache中的新的segment,也就意味着能够被搜索了。这就是ES的NRT——近实时性搜索。

简单介绍一下refresh API

若是你遇到过你新增了doc,可是没检索到,极可能是由于还未自动进行refresh,这是你能够尝试手动刷新

POST /student/_refresh

 

性能优化

在这里咱们须要知道一点refresh过程是很消耗性能的。若是你的系统对实时性要求不高,能够经过API控制refresh的时间间隔,可是若是你的新系统很要求实时性,那你就忍受它吧。

若是你对系统的实时性要求很低,咱们能够调整refresh的时间间隔,调大一点将会在必定程度上提高系统的性能。

PUT /student
{
  "settings": {
    "refresh_interval": "30s" 
  }
}

 

2.二、合并段——merge

经过每秒自动刷新建立新的段,用不了多久段的数量就爆炸了。有太多的段是一个问题。每一个段消费文件句柄,内存,cpu资源。更重要的是,每次搜索请求都须要依次检查每一个段。段越多,查询越慢。

ES经过后台合并段解决这个问题。小段被合并成大段,再合并成更大的段。

这是旧的文档从文件系统删除的时候。旧的段不会再复制到更大的新段中。 这个过程你没必要作什么。当你在索引和搜索时ES会自动处理。

咱们再来总结一下段合并的过程。

1 选择一些有类似大小的segment,merge成一个大的segment
2 将新的segment flush到磁盘上去
3 写一个新的commit point,包括了新的segment,而且排除旧的那些segment
4 将新的segment打开供搜索
5 将旧的segment删除
optimize API与性能优化

optimize API 最好描述为强制合并段API。它强制分片合并段以达到指定 max_num_segments 参数。这是为了减小段的数量(一般为1)达到提升搜索性能的目的。

在特定的环境下, optimize API 是有用的。典型的场景是记录日志,这中状况下日志是按照天天,周,月存入索引。旧的索引通常是只可读的,它们是不可能修改的。 这种状况下,把每一个索引的段降至1是有效的。搜索过程就会用到更少的资源,性能更好:
POST /logstash-2019-10-01/_optimize?max_num_segments=1

 通常场景下尽可能不要手动执行,让它自动默认执行就能够了

 

2.三、容灾与可靠存储

没用 fsync 同步文件系统缓存到磁盘,咱们不能确保电源失效,甚至正常退出应用后,数据的安全。为了ES的可靠性,须要确保变动持久化到磁盘。

咱们说过一次全提交同步段到磁盘,写提交点,这会列出全部的已知的段。在重启,或从新 打开索引时,ES使用此次提交点决定哪些段属于当前的分片。

当咱们经过每秒的刷新得到近实时的搜索,咱们依然须要定时地执行全提交确保能从失败中 恢复。可是提交之间的文档怎么办?咱们也不想丢失它们。

上面doc索引流程的阶段1,doc分别被写入到内存缓冲区和translog,而后每秒都将会把内存缓冲区的docs刷新到filesystem cache中的新segment,而后众多segment会进行不断的压缩,小段被合并成大段,再合并成更大的段。每次refresh操做后,内存缓冲区的docs被刷新到filesystem cache中的segemnt中,可是tanslog仍然在持续的增大增多。当translog大到必定程度,将会发生一个commit操做也就是全量提交。

详细过程以下:

1、 doc写入内存缓冲区和translog日志文件
2、 每隔一秒钟,内存缓冲区中的docs被写入到filesystem cache中新的segment,此时segment被打开并供检索使用
3、 内存缓冲区被清空
四、 重复1~3,新的segment不断添加,内存缓冲区不断被清空,而translog中的数据不断累加
5、 当translog长度达到必定程度的时候,commit操做发生
  5-1、 内存缓冲区中的docs被写入到filesystem cache中新的segment,打开供检索使用
  5-2、 内存缓冲区被清空
  5-3、 一个commit ponit被写入磁盘,标明了全部的index segment
  5-4、 filesystem cache中的全部index segment file缓存数据,被fsync强行刷到磁盘上
  5-五、 现有的translog被清空,建立一个新的translog

 

其实到这里咱们发现fsync仍是没有被舍弃的,可是咱们经过动态索引和translog技术减小了其使用频率,并实现了近实时搜索。其次经过以上步骤咱们发现flush操做包括filesystem cache中的segment经过fsync刷新到硬盘以及translog的清空两个过程。es默认每30分钟进行一次flush操做,可是当translog大到必定程度时也会进行flush操做。

对应过程图以下

 

 

5-5步骤不难发现只有内存缓冲区中的docs所有刷新到filesystem cache中并fsync到硬盘,translog才会被清除,这样就保证了数据不会丢失,由于只要translog存在,咱们就能根据translog进行数据的恢复。

简单介绍一下flush API

手动flush以下所示,可是并不建议使用。可是当要重启或关闭一个索引,flush该索引是颇有用的。当ES尝试恢复或者从新打开一个索引时,它必须重放全部事务日志中的操做,因此日志越小,恢复速度越快。

POST /student/_flush

 

 

3、更新和删除

前面咱们说过es的索引是不可变的,那么更新和删除是如何进行的呢?

段是不可变的,因此文档既不能从旧的段中移除,旧的段也不能更新以反映文档最新的版本。相反,每个提交点包括一个.del文件,包含了段上已经被删除的文档。当一个文档被删除,它实际上只是在.del文件中被标记为删除,依然能够匹配查询,可是最终返回以前会被从结果中删除。
文档的更新操做是相似的:当一个文档被更新,旧版本的文档被标记为删除,新版本的文档在新的段中索引。也许该文档的不一样版本都会匹配一个查询,可是更老版本会从结果中删除。

 

 

 

  参考文献:

  《elasticsearch-权威指南》

 

  若有错误的地方还请留言指正。

  原创不易,转载请注明原文地址:http://www.javashuo.com/article/p-wnpbnayv-cm.html

相关文章
相关标签/搜索