文档提交之commit(一)

  阅读本文章(必须)需要前置知识:文档提交之flush文档的增删改的系列文章,下文中出现的未展开介绍的变量说明已经这些文章中介绍,本文中不赘述。

  Lucene提供了TwoPhaseCommit接口(看这里:https://github.com/LuXugang/Lucene-7.5.0/blob/master/solr-7.5.0/lucene/core/src/java/org/apache/lucene/index/TwoPhaseCommit.java),支持两阶段提交(2-phase commit),在这个接口中提供了3个接口方法:

两阶段提交流程图

图1:

  上图中并没有给出rollback()方法的流程图,在介绍完文档提交之commit的系列文章后,会展开介绍。

图1中,用户如果调用一个prepareCommit()方法实现二阶段提交的第一阶段,会生成pendingCommit,然后执行尝试段合并流程后退出;用户如果直接调用commit()方法,会先检查是否执行了第一阶段的提交操作,即判断是否存在pendingCommit,如果不存在则先执行执行第一阶段提交的流程,随后执行执行第二阶段提交交操作,最后执行尝试段合并流程后退出。

  接下来我们就IndexWriter.commit()方法来介绍详细的文档提交之commit的过程。

文档提交之commit的整体流程图

图2:

点击查看大图

  介绍每一个流程点之前,先给出文档提交之flush的整体流程图,其中红框标注的流程点属于两个流程图相同的部分,这些内容不会赘述,已在文档提交之flush系列文章中介绍。

图3:

点击查看大图

检查pendingCommit

图4:

  pendingCommit是什么

  当多个线程持有相同的IndexWriter对象引用进行操作时,当前线程执行IndexWriter.commit()时会先检查其他线程或自己本身之前是否生成了pendingCommit对象,如果不存在那么执行二阶段提交的第一阶段的操作,否则直接执行二阶段提交的第二阶段的操作,即跳过图3中紫色虚线划分的所有流程点,直接执行蓝色虚线中的处理旧的Segment_N文件流程点。

  在Lucene7.5.0版本中,只允许一个pendingCommit存在,否则会抛出异常,异常内容如下:

  上面的异常描述了,当生成一个prepareCommit后,必须有线程执行二阶段提交的第二阶段后 才能再次生成一个新的prepareCommit,故使用synchronized(commitLock)来实现同步IndexWriter.prepareCommit()操作,其中commitLock对象没有实际意义,只是用来实现Java对象锁的功能:

  为什么使用两个synchronized(commitLock)同步操作

生成完整的段信息

图5:

  这几个流程点是生成完整的段信息的过程, 即DWPT(见文档的增删改(中))生成对应段的过程(见文档提交之flush系列文章),不赘述。

  在图5中,流程点执行commit前的工作跟图3中的执行flush前的工作是相同的操作,作者只是为了区分commit跟flush两种不同的操作而对流程点的名称作了区分,Lucene在该流程点提供了钩子函数doBeforeFlush (),定义如下:

设置commitUserData

图6:

  用户调用prepareCommit()或者commit()方法前通过调用IndexWriter.setLiveCommitData(...)来记录自定义的信息,即commitUserData,比如说记录一些业务信息来描述这次提交操作。

  例如Git操作时,我们可以通过命令git commit -m "add README"来记录这次git提交的messages信息,commitUserData好比是"add README"。

更新索引文件的计数引用

  在这个流程点,需要执行两个操作:

执行flush后的工作

图7:

  该流程在前面的文章已经介绍,在源码中调用DocumentsWriterFlushControl.finishFullFlush( )的方法,详细的介绍见文档提交之flush(六)文章中的IndexWriter处理事件章节的内容。

执行commit后的工作

图8:

  Lucene在当前流程点提供一个钩子函数doAfterFlush()方法,用户可以实现自己的业务逻辑,定义如下:

执行同步磁盘工作

图9:

  图3中红色标注为commit跟flush相同的流程点,在执行完这些流程点之后,索引文件已经写到了磁盘,但由于文件I/O缓冲机制,索引文件的信息(部分或全部)可能暂时被保存在内核缓冲区高速缓存中,并没有持久化到磁盘,当出现类似断电的异常,且磁盘没有备用电源的情况,索引信息可能会丢失。

  为什么使用文件I/O缓冲机制:

  在commit()操作中,执行同步磁盘操作,缓冲数据和与打开文件描述符fd相关的所有元数据都刷新到磁盘,仅在对磁盘设备的传递后,即等待索引文件都被持久化到磁盘后才会返回,故这是一个相对耗时的操作

  另外在执行完流程点执行同步磁盘工作后,释放对象锁synchronized(commitLock),离开IndexWriter.prepareCommit()操作的临界区。

结语

  本篇文章介绍了二阶段提交的第一阶段,基于篇幅,剩余内容将在下一篇文章中展开。

点击下载附件