构造 IndexWriter 对象(七)

构造 IndexWriter 对象(六)

构造 IndexWriter 对象(五)

构造 IndexWriter 对象(四)

构造 IndexWriter 对象(三)

构造 IndexWriter 对象(二)

构造 IndexWriter 对象(一)

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

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

图 1:

生成对象 IndexFileDeleter

  我们紧接上一篇文章,继续介绍剩余的流程点,下面先给出 IndexFileDeleter 的构造函数流程图:

IndexFileDeleter 的构造函数流程图

图 2:

执行检查点(checkPoint)工作

图 3:

  在介绍当前流程点之前,我们先通过下面的流程图来介绍 执行检查点的工作 这个流程点做了哪些事情。

  另外 执行检查点(checkPoint)工作 在源码中对应的方法方法定义:

public void checkpoint(SegmentInfos segmentInfos, boolean isCommit) throws IOException {
... ...
}
执行检查点(checkPoint)工作的流程图

图 4:

准备数据

图 5:

  图 4 流程图的准备数据就是 checkPoint 方法的两个参数 SegmentInfos 以及 boolean。

增加 SegmentInfos 对应的索引文件的计数引用

图 6:

  在后面的流程中,可能会执行索引文件的删除,如果某些索引文件被 SegmentInfos 引用,那么这些索引文件不应该被删除,防止被删除的方法就是增加 SegmentInfos 对应的索引文件的计数引用。

当 isCommit 为 true 时的处理流程

图 7:

  在执行 commit()操作时,也会执行 checkPoint 的操作,那么此时的 isCommit 是位 true 的,在文章 文档提交之 commit(二) 中介绍了这个流程,不赘述。

当 isCommit 为 false 时的处理流程

图 8:

  lastFiles 是一个 IndexFileDeleter 类的成员变量,它用来存放上次执行 checkPoint 的 SegmentInfos 中对应的索引文件定义如下:

private final List lastFiles = new ArrayList<>();  所以当 isCommit 为 false 时,先尝试删除 lastFiles 中的索引文件,删除的方式就是减少每一个索引文件的计数引用,如果计数值为 0,那么该索引文件就要被删除,否则不删除,最后清空 lastFiles 中的索引文件后,将这次 SegmentInfos 对应的索引文件添加到 lastFiles 中。

  结合图 6 跟图 8 我们可以发现这种流程的逻辑设计可以使得,即使对同一个 SegmentInfos 对象执行 多次 checkPoint 的操作时,如果该对象中的段没有发生变化,那么段对应索引文件的计数是不会变的(先增加计数,再减少计数),同样地,如果 SegmentInfos 中如果增加了段,能通过图 6 的流程点对该段中的索引文件执行正确的 +1 计数,如果删除了某个段,能通过图 8 的流程点 尝试删除lastFiles中的索引文件 对该段中的索引文件执行正确的-1 计数。

为什么要通过 checkPoint 来实现索引文件的删除

  Lucene 通过 IndexWriter 对象中的成员变量 SegmentInfos 来描述当前 IndexWriter 对应的索引信息,索引信息的变化通过 SegmentInfos 对象来反应,但是 SegmentInfos 对象并不真正的拥有索引文件的数据,它拥有只是这些索引文件的文件名,所以当 SegmentInfos 对应的信息发生变化时,例如某个段中包含的所有文档满足某个删除操作,该段的信息会从 SegmentInfos 中移除(段的信息即 SegmentCommitInfo,见文章 构造 IndexWriter 对象(四) 关于流程点 获得回滚(rollback)信息 的介绍),那么这个段对应的索引文件也应该要删除(如果索引文件的计数引用为 0),当然如果其他段同时引用这些索引文件,那么就不会被删除(索引文件的计数引用不为 0),也就是为什么图 7 的流程点 尝试删除lastFiles中的索引文件 为什么要带有 尝试 两个字。

我们回到当前流程点,介绍下为什么获得 StandardDirectoryReader 后需要执行 执行检查点(checkPoint)工作

  根据图 2 的流程,我们是在构造 IndexFileDeleter 对象的流程中第一次调用 checkPoint()方法,那么 lastFiles 中必定不包含任何的数据,并且在源码中调用 checkPoint()方法的参数如下所示:

checkpoint(segmentInfos, false);  segmentInfos 即 StandardDirectoryReader 中对应的索引信息(见文章 构造 IndexWriter 对象(四)用StandardDirectoryReader初始化一个新的SegmentInfos对象 流程点的介绍),同时 isCommit 的值为 false,也就说在当前流程点调用 checkPoint()方法的目的就是 仅仅 为了增加 segmentInfos 对应的索引文件的计数,那么就转变为如下的问题:

为什么获得 StandardDirectoryReader 后,需要增加 segmentInfos 对应的索引文件的计数

  首先给出源码中的解释:

// Incoming SegmentInfos may have NRT changes not yet visible in the latest commit, so we have to protect its files from deletion too:  我们用一个例子来介绍上文的注释所描述的场景,完整代码见: https://github.com/LuXugang/Lucene-7.5.0/blob/master/LuceneDemo/src/main/java/lucene/index/CheckPointInIndexFileDeleter.java

图 9:

  图 9 中当第 62 行的 oldIndexWriter.commit()操作执行结束后,索引目录中的索引文件如下所示:

图 10:

  接着执行完第 64 行的代码后,我们获得了一个 NRT 的 Reader(见文章 近实时搜索 NRT(三) 的介绍),接着第 70 行代码结束后,oldIndexWriter 新增了一篇文档 2,注意的是并没有执行 commit()操作(即没有生成新的 segments_N 文件),随后通过第 72 行的 openIfChange() 方法获得一个包含最新索引信息的 reader(即 StandardDirectoryReader),通过该 reader 获得一个 IndexCommit,IndexCommit 中包含了第 70 行代码增加的索引信息,即图 11 中以_1 为前缀的索引文件,并且在第 76 行通过 IndexWriterConfig.setIndexommit()方法将 IndexCommit 成为 newIndexWriter 的配置,在执行完第 78 行代码后,索引目录中的索引文件如下所示: