构造 IndexWriter 对象(六)

系列文章:
构造 IndexWriter 对象(五)
构造 IndexWriter 对象(四)
构造 IndexWriter 对象(三)
构造 IndexWriter 对象(二)
构造 IndexWriter 对象(一)

  本文承接构造 IndexWriter 对象(五),继续介绍调用 IndexWriter 的构造函数的流程。

调用 IndexWriter 的构造函数的流程图

图 1:

1.png

生成对象 BufferedUpdatesStream

图 2:

2.png

  介绍该对象会涉及很多在文档提交之 flush 系列文章中的知识点,故如果没有看过或者不熟悉 flush 流程的同学可以跳过下面的内容,只需要知道该对象的生成时机就行了

  BufferedUpdatesStream 用来追踪(track)FrozenBufferedUpdates,主要负责执行 FrozenBufferedUpdates 的两个工作:

  • 获得 nextGen:它用来描述 FrozenBufferedUpdates 中的删除信息应该作用哪些段,见文档提交之 flush(六)文章中的介绍
  • 作用(apply)删除信息:FrozenBufferedUpdates 中存放了删除信息以及更新信息(DocValues 相关),为了方便描述,在下文中 删除信息、更新信息统称为删除信息。删除信息被作用到每一个段称为处理删除信息,根据作用(apply)的目标段,处理删除信息划分为两种处理方式:
    • 全局 FrozenBufferedUpdates:根据全局 FrozenBufferedUpdates 内的 nextGen(见文档提交之 flush(六))值,其删除信息将要作用到所有比该 nextGen 值小的段
    • 段内 FrozenBufferedUpdates:在文档提交之 flush(三)中我们提到,在生成索引文件的过程中,我们只处理了部分满足删除信息,即只处理了满足删除信息 TermArrayNode、TermNode(见文档的增删改(下)(part 2))的段内部分文档,而如果段内 FrozenBufferedUpdates 还存在删除信息 QueryArrayNode、DocValuesUpdatesNode,那么根据段内 FrozenBufferedUpdates 就可以找出所有剩余的满足删除的文档

  获得 nextGen 的执行时机点在 flush 的流程中的位置如下所示,用红框标注:

图 3:

3.png

  图 3 的流程图的每个流程点的详细介绍见文档提交之 flush(六)

  作用(apply)删除信息的执行时机点在 flush 的流程中的位置如下所示,用红框标注:

图 4:

4.png

  从图 3 的流程中可以知道,在 FrozenBufferedUpdates 获得 nextGen 之后就被添加到了 eventQueue(见文档提交之 flush(四)中的介绍)中,故该作用(apply)删除信息的执行时机点在图 4 的 IndexWriter处理事件 的流程中。

生成对象 DocumentsWriter

图 5:

5.png

  DocumentsWriter 对象主要负责下面的三个工作:

  • 文档的增删改:用户通过 IndexWriter 对象执行文档的增删改的任务,实际都是 IndexWriter 通过调用 DocumentsWriter 对象来实现的,文档的增删改的详细过程可以看文档的增删改的系列文章
  • 将 DWPT 生成(flush)为一个段:该工作即图 4 中的流程 执行DWPT的doFlush()
  • 执行主动 flush 以后的收尾工作:该内容见文档提交之 flush(六)中关于 DocumentsWriterFlushControl.finishFullFlush( )的方法的介绍

生成对象 ReaderPool

  跟 BufferedUpdatesStream 一样,由于个人表达能力有限,无法通过有限的语句来描述 ReaderPool,故阅读下面的内容需要很多前置的内容,这些内容会以链接的方式给出,不会作详细的介绍,见谅。

  ReaderPool 的命名方式就能完美描述该对象的作用,字面意思就是 存放 reader 的池子(pool),在源码注释中只用了一句话来描述该对象的作用,如下所示:

Holds shared SegmentReader instances  ReaderPool 就是用来缓存 SegmentReader 对象(SegmentReader 用来描述一个段的索引信息,详细介绍可以看 SegmentReader 系列文章),使得 Lucene 在执行下面的操作时都会尝试先去 ReaderPool 取出 SegmentReader:

  • 作用(apply)删除信息、更新 DocValues 信息
  • 执行段的合并
  • 分发(handing out)一个实时的 Reader

作用(apply)删除信息、更新 DocValues 信息

  对于索引目录中的某一个段,由于后续有新的删除/更新操作,如果该段中的文档满足删除/更新的条件,那么该段对应的 SegmentReader 中的索引信息也需要发生更改,那么根据索引信息是否会被更改可以分为下面两类:



  生成一个 SegmentReader 对象的开销是极大的,原因是读取索引信息为磁盘 I/O 操作,故使用 ReaderPool 来缓存 SegmentReader,当需要作用(apply)删除信息、更新 DocValues 信息时,只需要从 ReaderPool 中取出该段对应的 SegmentReader(如果不存在则先添加到 ReaderPool),并且只修改 SegmentReader 中会发生变更的索引信息。

  在 flush()阶段,DWPT(见文章文档的增删改(中))被 flush 为一个段后,并不会马上被添加到 ReaderPool 中(lazy init 机制),而是当该段需要被作用(apply)删除信息、更新 DocValues 信息时,被添加到 ReaderPool 的时机点在下图中用红框标注:

图 6:

6.png

  图 6 的流程图在文章文档提交之 flush(七)中做了详细介绍,感兴趣的同学可以看一看。

执行合并

  执行段的合并的过程是通过每个段对应的 SegmentReader 中包含的索引信息进行合并(见执行段的合并(三)),故在合并期间需要获取待合并段的 SegmentReader,而获取的方式就是从 ReaderPool 获取。

  当然也有可能一个或多个待合并的段对应的 SegmentReader 并不在 ReaderPool(原因是没有 作用(apply)删除信息、更新 DocValues 信息),那么此时就需要生成新的 SegmentReader 对象,并添加到 ReaderPool 中。

分发(handing out)一个实时(real time)的 Reader

  在文章近实时搜索 NRT(一)中我们说到,有下面的四种方法可以获得 StandardDirectoryReader:

  • 方法一:DirectoryReader.open(final Directory directory)
  • 方法二:DirectoryReader.open(final IndexCommit indexCommit)
  • 方法三:DirectoryReader.open(final IndexWriter indexWriter)
  • 方法四:DirectoryReader.open(final IndexWriter indexWriter, boolean applyAllDeletes, boolean writeAllDeletes)

  其中通过方法三,方法四能获得具有 NRT 功能的 StandardDirectoryReader(见文章近实时搜索 NRT(三)),并且在这两个方法的实现过程中,会将 StandardDirectoryReader 中的 SegmentReader 缓存到 ReaderPool 中,这样的做法使得当再次通过方法三、方法四或者性能更高的 OpenIfChange()方法(近实时搜索 NRT(四))获得 StandardDirectoryReader 时,能先从 ReaderPool 中获得缓存的 SegmentReader,即所谓的"分发"。

  实时(real time)的 StandardDirectoryReader 缓存到 ReaderPool 的时机点如下红框标注所示所示:

图 7:

7.png

  图 7 的流程图的详细介绍见文章近实时搜索 NRT(二)

ReaderPool 的构造函数

  另外还要说下的是,在 ReaderPool 的构造函数中,会将图 1 中流程点 获取IndexCommit对应的StandardDirectoryReader 获得的 StandardDirectoryReader 中的 SegmentReader 缓存到 ReaderPool 中。

生成对象 IndexFileDeleter

图 8:

8.png

  IndexFileDeleter 用来追踪 SegmentInfos 是否还"活着(live)",在文章构造 IndexWriter 对象(四)中我们介绍了 SegmentInfos 对象跟索引文件 segments_N 的关系,简单的概括就是 SegmentInfos 对象是索引文件 segments_N 和索引文件。si 在内存中的表示。

  当执行索引删除策略时,例如默认的索引删除策略 KeepOnlyLastCommitDeletionPolicy,新的提交生成后(即生成新的 segments_N 文件)需要删除上一次提交,即需要删除上一次提交对应的所有索引信息,而用来描述所有索引信息的正是 SegmentInfos,删除 SegmentInfos 的真正目的是为了删除对应在索引目录中的索引文件,但这些索引文件如果正在被其他 SegmentInfos 引用,那么就不能被删除,IndexFileDeleter 真正的工作就是判断索引目录中的索引文件是否允许被删除。

IndexFileDeleter 如何判断索引目录中的索引文件是否允许被删除

  使用引用计数的方式。

IndexFileDeleter 的构造函数

  基于篇幅,将在下一篇文章中介绍 IndexFileDeleter 的构造函数

结语

  无

阅读原文


本文地址:https://www.6aiq.com/article/1586279638090
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出