elasticsearch index过程简要分析

写入的数据是如何变成 ES 里可以被检索和聚合的索引内容的呢?从单文件的静态层面看,每个全文索引都是一个词元的倒排索引。这里只简单的介绍一下倒排索引的组成,其它的一些通用知识可以看看Lucene相关的一些书籍。

倒排索引通常由词汇表和记录表组成:

词汇表:文档集合中所包含的不同单词的集合。

记录表:对于词汇表中的每一个单词,包含这个单词的文档编号构成的一个列表(有可能还会保存些其他信息,例如单词在文档中的位置信息)。

从服务的层面看,要做到实时更新条件下数据的可用和可靠,就需要在倒排索引的基础上,再做一系列更高级的处理。Lucene 的处理办法,很简单,就是一句话:新收到的数据写到新的索引文件里。ES是基于Lucene的分布式搜索引擎,底层处理也当然会按照Lucene的处理方式来进行处理。

Lucene 把每次生成的倒排索引,叫做一个段(segment)。然后另外使用一个 commit 文件,记录索引内所有的 segment。而生成 segment 的数据来源,则是内存中的 buffer。也就是说,动态更新过程如下:

elasticsearch index过程简要分析

1、 当前索引中有3个段。

2、 外部调用ES API写入新数据进入内存buffer。

3、 内存 buffer 生成一个新的 segment,刷到文件系统缓存中,Lucene 即可检索这个新 segment。

4、 文件系统缓存真正同步到磁盘上,commit 文件更新,记录新生成的segment。

在refresh文件系统缓存的过程中,ES默认设置为1秒间隔(可以在ES的yml配置文件中设修改默认刷新时间,例如每5秒刷新一次,index.refresh_interval: 5s),对于大多数应用来说,几乎就相当于是实时可搜索了。如果对1秒间隔还不满意,ES也提供了单独的/_refresh 接口或在JAVA API中写入的时候设置setRefresh(true),可以主动调用该接口来保证搜索可见。

上面我们说过Lucene通过新生成文件的方式来让数据更快的被检索使用,但另一方面,新开文件会给服务器带来负载压力。因为默认1秒refresh一次文件系统缓存,都会有一个新文件产生,每个文件都需要有文件句柄,内存,CPU使用等各种资源。一天有 86400 秒,如果每次请求要扫描一遍 86400 个文件,这样性能绝对好不了!如果是每次写入的时候都通过API显式刷新,产生的文件会更多。

所以应该在满足业务需求的范围内,适当的调整refresh的间隔时间;另一方面ES会不断在后台运行任务,主动将这些小的segment 做数据归并,尽量让索引内只保有少量的,每个都比较大的segment 文件。这个过程是有独立的线程来进行的,并不影响新 segment 的产生。在归并过程中,没有完成的较大的 segment 是不可见的。当归并完成,较大的这个 segment 刷到磁盘后,commit 文件做出相应变更,删除之前几个小 segment,改成新的大 segment。等检索请求都从小 segment 转到大 segment 上以后,删除没用的小 segment。这时候,索引里 segment 数量就下降了。从上面可以看出段合并的时候会写磁盘,因此我们必须对写磁盘的速度进行控制,启用限速机制,可以在配置文件中设置indices.store.throttle.max_bytes_per_sec选项的值来进行设置,避免无限速的写,影响服务器上其它服务的性能。

当然在写索引的这个过程中还会写一些translog,来保证数据数据的一致性;默认半个小时文件系统缓存flush一次写入到磁盘,个人觉得这些默认设置对性能影响不大,所以不做过多探讨。

后续会从源代码层面对ES建索引的过程中主要的类进行解释,有兴趣的一起学习相互讨论。


分享到:


相關文章: