Fork me on GitHub

构造 IndexWriter 对象(七)

构造 IndexWriter 对象(五)
构造 IndexWriter 对象(四)
构造 IndexWriter 对象(三)
构造 IndexWriter 对象(二)
构造 IndexWriter 对象(一)

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

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

图 1:

1.png

生成对象 IndexFileDeleter

  在文章构造 IndexWriter 对象(六)中,我们简单介绍了 IndexFileDeleter 作用,即用来删除索引目录中的索引文件,本文根据 IndexFileDeleter 的构造函数的实现来介绍关于计数引用、删除无效(Invalid)索引文件的内容。

IndexFileDeleter 的构造函数流程图

图 2:

2.png

SegmentInfos

图 3:

3.png

  构造 IndexFileDeleter 最重要的一个对象就是 SegmentInfos,它是在图 1 的 生成对象IndexFileDeleter 流程点之前通过用户配置的 IndexCommit 或者索引目录中的旧索引生成的 SegmentInfos 对象(见构造 IndexWriter 对象(三)构造 IndexWriter 对象(四)构造 IndexWriter 对象(五)中关于生成 SegmentInfos 的介绍),并作为 IndexFileDeleter 的构造函数的参数之一。

是否能获得索引文件 segments_N 的文件名?

图 4:

4.png

  构造 IndexWriter 对象里面调用 IndexFileDeleter 的构造函数时,总是能通过 SegmentInfos 获得一个 segments_N 的文件名,并且通过下面的方法来获得 segments_N 的文件名,那么当前流程点的判断结果总是为 true,由于该方法可能会返回 null,所以在这里会有一个判断。

SegmentInfos.getSegmentsFileName()#### 判断索引文件是否满足要求

图 5:

5.png

  接着就是从索引目录依次取出索引文件,然后判断是否满足某个要求。

需要满足什么要求:

先给出该要求对应的代码:

if (!fileName.endsWith("write.lock") && (m.matches() || fileName.startsWith(IndexFileNames.SEGMENTS) || fileName.startsWith(IndexFileNames.PENDING_SEGMENTS)){
    ... ...
}

其中 fileName 就是待处理的索引文件的文件名、m.matches()描述的是 fileName 是否满足下面的正则表达式

[a-z0-9]+(.)?\..  上述的代码进行拆分后即需要满足下面三个字要求之一:

  • 子要求一:!fileName.endsWith("write.lock") && (m.matches()
    • writer.lock 即索引目录的索引文件锁,用来同步不同的 IndexWriter 对象,只允许一个 IndexWriter 可以操作同一个索引目录,占用了索引文件锁的 IndexWriter 可以通过调用 Inde.close()方法来释放该锁,wrtier.lock 不满足要求
    • m.matches()则是根据正则表达式来匹配命名方式为下图中的文件名,满足正则表达式即满足要求:

图 6:

6.png

  • 子要求二:fileName.startsWith(IndexFileNames.SEGMENTS)
    • 满足以"segment"开头的文件名即满足子要求二,比如 segments_N 文件
  • 子要求三:fileName.startsWith(IndexFileNames.PENDING_SEGMENTS)
    • 满足以“pending_segments”开头的文件名即满足子要求三,比如执行 commit()操作时,在两阶段提交之第一阶段会生成该命名方式的文件,详细见文件文档提交之 commit(二)

初始化索引文件的计数引用

图 7:

7.png

  如果索引文件满足上文中的要求,那么我们初始化这些索引文件的计数引用。

Lucene 中如何实现对索引文件的计数引用

  通过一个 HashMap 对象 refCounts 以及一个 RefCount 类实现,他们的定义如下所示:

final private static class RefCount {
    final String fileName; // 索引文件名
    int count; // 计数值
  
    public int IncRef() {
        // 增加计数
    }
  
    public int DecRef() {
        // 减少计数
    }
}

// key 为索引文件名,value 为 RefCount 对象

private Map<String, RefCount> refCounts = new HashMap<>();

  如果我们要增加某个索引文件的计数引用,那么根据 refCounts 找到该文件对应的 RefCount 对象,接着通过对象的 IncRef()方法来增加计数。

  上述的 RefCount 类的具体内容可以查看这个链接:https://github.com/LuXugang/Lucene-7.5.0/blob/master/solr-7.5.0/lucene/core/src/java/org/apache/lucene/index/IndexFileDeleter.java

索引文件是否为 segments_N?

图 8:

8.png

  如果是 segments_N 文件,那么需要对该文件进行额外的处理,判断方法同上文中的子要求二。

处理索引目录中所有的 segments_N

图 9:

9.png

  索引目录中可能存在多个 segments_N 文件,那么这些文件都需要被处理,其中只有一个 segments_N 对应的 SegmentInfos 为构造 IndexFileDeleter 对象的参数,即图 2 中 SegmentInfos 流程点的 segmentInfos。

为什么索引目录中会存在多个 segments_N 文件

  原因主要取决于上一个 IndexWriter 对象使用了哪种索引删除策略 IndexDeletionPolicy(见文档提交之 commit(二)关于 IndexDeletionPolicy 的介绍),比如使用了索引删除策略 NoDeletionPolicy,那么每次提交都会保留,又比如使用了默认的索引删除策略 KeepOnlyLastCommitDeletionPolicy,那么只会保留最后一次提交。

图 10:

10.png

  在图 10 中,使用不同的索引删除策略对相同的数据进行索引,在执行了 4 次 commit 提交后,对于 NODeletetionPolicy 来说,它会保留所有的提交,而对于 KeepOnlyLastCommitDeletionPolicy,当生成 segments_2 时,会删除 segments_1,生成 segments_3 时,会删除 segments_2,以此类推,即只保留最后一次提交。

为什么要根据每一个 segments_N 对应的 SegmentInfos 生成 CommitPoint,并且添加到 CommitPoint 集合 commits 中

  • 原因是我们需要根据正在构造的 IndexWriter 对象中的索引删除策略来处理这些提交,而 CommitPoint 对象为索引删除策略作用的对象。
  • 这里将所有的 CommitPoint 添加到 commits 中是为了在下面的流程中作为索引删除策略的输入数据进行统一处理。

  接着如果某个 segments_N 对应的 SegmentInfos 为构造 IndexFileDeleter 的参数,即图 2 中 SegmentInfos 流程点的 SegmentInfos,那么它对应生成的 CommitPoint 被设置为当前提交点 currentCommitPoint,该对象在后面的流程点 是否出现异常 会作为条件进行异常判断。

  最后我们需要增加每一个 segments_N 对应的 SegmentInfos 中对应的索引文件的计数引用,其原因是在后面的流程中能判断能否能删除索引文件。

是否处理异常

图 11:

11.png

  这里考虑的异常是使用 NFS(Network File System)网络文件系统的场景,使用该文件系统可能会导致下面的情况:我们能通过构造 IndexFileDeleter 的参数获得 SegmentInfos 对象,并且通过 SegmentInfos 获得 segments_N 的文件名(注意只是文件名),那么 segments_N 文件必定是在索引目录中(见构造 IndexWriter 对象(五)/构造 IndexWriter 对象(四)/构造 IndexWriter 对象(三)不同的打开模式 OpenMode 的内容),但是我们在图 9 的流程中,如果没有读取到 segments_N 文件(通过是否获得 currentCommitPoint 对象来判断),那么有可能就是 NFS 导致的,例如目录缓存机制的影响,那么可以根据 segment_N 文件名,通过重试机制来获得它对应的 SegmentInfos 对象,并且生成 currentCommitPoint 对象,如果还是读取不到,那么就会抛出下面的异常:

throw new CorruptIndexException("unable to read current segments_N file", currentSegmentsFile, e);

结语

  基于篇幅,剩余的内容将在下一篇文章中展开。

阅读原文


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