近实时搜索NRT(一)

  Lucene提供了近实时搜索NRT(near real time)的功能,它描述了索引信息发生改变后,不需要执行commit操作或者关闭IndexWriter(调用IndexWriter.close()方法)就能使得这些更改的信息很快(quickly)变得可见。

  "很快"意味着不是马上变得可见,Lucene无法保证更改索引信息后,在某个确定的时间之后,使得最新的索引信息变得可见,这取决具体的业务。

  近实时搜索NRT通过DirectoryReader来实现,故下面的文章中将会介绍DirectoryReader的实现原理。

DirectoryReader

  通过DirectoryReader,我们可以读取索引目录中已经提交(执行commit操作)或者未提交(执行flush操作)的段的信息,IndexSearcher通过DirectoryReader包含的信息来进行查询,它是一个抽象类,其类图如下所示,在下面的内容中会详细介绍DirectoryReader如何生成的:

图1:

  上图中,DirectoryReader有两个子类StandardDirectoryReader、FilterDirectoryReader,其中StandardDirectoryReader是Lucene7.5.0中默认的DirectoryReader具体实现,FilterDirectoryReader作为一个抽象类,它的两个具体实现ExitableDirectoryReader、SoftDeletesDirectoryReaderWrapper通过封装其他的DirectoryReader对象,来实现功能扩展。下面我们一一介绍图1中的每一个类。

StandardDirectoryReader

  StandardDirectoryReader作为Lucene7.5.0中默认且唯一的DirectoryReader类的具体实现,我们需要详细的来了解它,该类的对象可以通过个调用以下四个方法来获得:

  其中通过调用方法三&&方法四的方法实现了NRT功能,而方法一&&方法二则没有,下文将会描述它们之间的差异

获取StandardDirectoryReader对象的流程图

  尽管提供了4种方法,但这些方法的实现原理都可以在一张流程图中表示:

图2:

点击查看大图

  我们先介绍方法一&&方法二的所有流程点。

统一入口

图3:

  IndexCommit是什么

  如何获得IndexCommit

获取segment_N文件

图4:

  该流程只有方法二才会执行,图4中的多个流程,执行它们的最终目的是为了获得索引目录(根据方法二的参数Directory对象获得索引目录)中的segment_N文件。

  在图4的流程中,Lucene连续两次获取索引目录中的所有文件,获得两个文件名集合file、file2,在分别对file、file2进行排序后,通过比较两个集合是否包含相同的文件名来判断是否当前索引目录是否有频繁的新的写入操作,如果有,那么通过重试的方法,直到file、file2是相同的文件集合,由于这段代码较为简洁,故直接给出:

  在上面的代码中,其中directory即方法二的参数。

  从上面的代码我们可以看出哪些信息

  如何根据文件集合files来获得最后一次提交的segment_N文件

图5:

获得所有段的信息集合SegmentInfos

图6:

  在前面的流程中,无论是方法一还是方法二,到达此流程点时,已经获得了segment_N文件,那在图6的流程中,我们根据segment_N获取段的信息集合segmentInfos,该集合在前面的文章中已经多次介绍,它包含的一个重要信息是一个链表,链表元素是每一个段的信息:

  SegmentCommitInfo的数据分散在两个索引文件中,如图7所示:

图7:

点击查看大图

  图7中,两块黄色的框标注的内容即SegmentCommitInfo的信息,在读取segment_N阶段,先读取出SegmentCommitInfo的第一块数据,然后根据第一块数据的中的SegName(该字段的含义见segment_N,生成segment_N的时机见文档提交之commit(二))从索引文件.si(生成索引文件.si的时机见文档提交之flush(三))中读取出SegmentCommitInfo的第二块数据,两块数据组成完整的SegmentCommitInfo的信息,在源码中,这些信息即SegmentCommitInfo类的成员变量

获得StandardDirectoryReader

图8:

  在前面的流程中,我们获得了每一个段的SegmentCommitInfo,在Lucene中,将SegmentCommitInfo再次封装为SegmentReader,然后将所有段对应的SegmentReader最后封装为StandardDirectoryReader。

SegmentReader

  SegmentReader类不展开作介绍了,我们只需要知道该类描述的是一个段的所有的信息,该信息通过封装的SegmentCommitInfo来描述。

StandardDirectoryReader

  所有段对应的SegmentReader集合以数组方式LeafReader[ ]被封装在StandardDirectoryReader中,如下图所示:

图9:

点击查看大图   图9中描述了StandardDirectoryReader中包含的最重要的一些数据,其中LeafReader[ ]数组中的元素个数跟索引文件segment_N中的黑框字段SegmentCommitInfo的个数是一致的。

结束

图10:

  对于方法一&&方法二,严格的来讲,至此我们获得了流程点获得所有段的信息集合SegmentInfos之前索引目录中最新的索引数据,由于其他线程可能通过IndexWriter并发的执行更改索引的操作,所以在多线程下,方法一&&跟方法二并不能实现NRT的功能。

结语

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

点击下载附件