本文承接文档提交之flush(六),继续依次介绍每一个流程点。
图1:
update中存放的是FrozenBufferedUpdates的集合,在文档提交之flush(六)中FrozenBufferedUpdates获得nextGen的值之后,它会被添加到update容器中,在FrozenBufferedUpdates中的删除信息作用到段之后,从update中移除。
在文档提交之flush(六)中我们介绍了IndexWriter处理事件
流程中的几种事件类型,下面仅介绍发布生成的段中的处理删除信息事件
。
根据作用(apply)的目标段,处理删除信息划分为两种处理方式:
图2:
点击查看大图
该流程在以下三种情况下会被调用:
发布生成的段中的处理删除信息事件
发布生成的段中的处理删除信息事件
(下文中会介绍)上述的三种情况会发生并发执行同一个FrozenBufferedUpdates的作用(apply)删除信息工作。
图3:
如上文中介绍,多线程可能同时执行同一个FrozenBufferedUpdates的作用(apply)删除信息的工作,即图2的流程,但只允许一个线程执行,否则会重复的处理删除信息,其他线程会被阻塞直到获得锁的线程执行完图2的流程。
通过什么方式判断其他线程已经处理过当前删除信息:
1 public final CountDownLatch applied = new CountDownLatch(1);
图4:
执行段合并的线程在执行完一次段合并之后,会递增一个计数,即mergeGenStart,该值会在后面的流程介绍,这里只要知道获得mergeGenStart所在的流程点位置。
图5:
在文档提交之flush(六)中我们了解到,描述新生成的段以及旧段的索引信息SegmentCommitInfo都存放在IndexWriter的全局变量SegmentInfos中,此流程从SegmentInfos找到那些将要被作用删除信息的段的集合,根据当前处理的FrozenBufferedUpdates是全局还是段内,获取的方式有点区别:
infos为空的后执行的流程在下文介绍。
为什么这里需要通过IndexWriter对象实现同步:
图6:
获取的信息被保存到SegmentState[ ]数组中,在介绍数组元素包含的信息前,得先说明只有满足下列要求的SegmentCommitInfo才会将其信息保存到SegmentState[ ]数组中:
xxxxxxxxxx
11 bufferedDeletesGen <= delGen && alreadySeenSegments.contains(SegmentCommitInfo) == false
首先保证bufferedDeletesGen <= delGen,上文中我们提到的,根据全局FrozenBufferedUpdates内的nextGen(见文档提交之flush(六))值,即delGen,其删除信息将要作用到所有比该nextGen(delGen)值小的段,其中等号"="
考虑是段内的FrozenBufferedUpdates的delGen跟此段的delGen是相等的情况。
再满足了上面的条件之后,就可以获取infos中所有满足条件的SegmentCommitInfo的信息了。SegmentState[ ]数组中的数组元素至少(跟本篇文章相关的)包括了下面的信息:
为什么要增加段集合中的所有索引文件计数引用:
计数引用
,一个段被其他对象有N次引用时,其索引文件对应的计数引用为N,当该段没有被任何对象引用后,那么就可以删除该段对应的索引文件图7:
图7中,处理TermDeletes即处理删除信息TermArrayNode、TermNode;处理QueryDeletes即处理删除信息QueryArrayNode;处理DocValuesUpdates即处理删除信息DocValuesUpdatesNode。其删除信息的介绍看文档的增删改(下)(part 2)。
上文中我们获得了SegmentReader,该值使得我们能获得段中的索引信息,包括文档的信息:
尽管我们没有对上述几个处理逻辑进行展开介绍,但这三个流程最终的目的就是找出满足删除要求的文档号,通过ReaderPool对象暂存TermDeletes、QueryDeletes生成的删除信息以及DocValuesUpdates生成的更新信息。最后在图1的流程点更新ReaderPool
中将删除信息以及更新信息生成索引文件。
如果图2中处理的是段内FrozenBufferedUpdates,那么是不用处理处理TermDeletes
的,因为删除信息TermArrayNode、TermNode在生成索引文件.tim、.tip、.doc、.pos、.pay阶段就被处理了(见文档提交之flush(三))。
图8:
在处理完删除信息后,我们需要以下的工作:
变化
描述的是在上面的流程中,段中的一个或多个文档被标记为删除,那么我们需要另IndexWriter中的一个全局变量maybeMerge为true,maybeMerge描述的是需要进行尝试段合并操作,在执行完主动flush后,会尝试进行段合并,段的合并策略以及合并计划可以看LogMergePolicy、TieredMergePolicy、MergeScheduler图9:
该流程会在后面介绍软删除的文章中展开介绍,这里只要知道其流程所在位置即可。
图10:
图2如果到达此流程点,并且段内FrozenBufferedUpdates的流程,那么我们已经成功的处理了段内的删除信息,故可以直接进入其下一个流程,该流程点在下文会介绍。
图11:
在上文的获得当前已经完成段合并的计数mergeGenStart
中,我们获得了mergeGenStart,并且在当前流程点再次去获得段合并的计数mergeGenCur,如果mergeGenCur 与 mergeGenStart相等,说明图2的流程从开始到现在这段期间,没有其他线程执行段合并 或者 某些段合并操作还未结束,否则我们需要重新执行图2的流程,原因是索引目录中的段已经发生了变化,我们需要重新将全局的FrozenBufferedUpdates作用到索引目录中的段。
从上面的流程可以看出,这种处理逻辑即乐观锁的一种实现方式。
由于执行段合并的段集合只是索引目录中的部分段,所以有些段并没有发生变化,并且这些段已经被作用(apply)了删除信息,故可以存放到上文中提到的alreadySeenSegments中,使得在下一轮的图2流程中,不会重复作用删除信息。
图12:
处理FrozenBufferedUpdates
的流程点逻辑跟图11中的是一致的,在退出图2流程之前,我们总是需要处理FrozenBufferedUpdates
。只有mergeGenCur 与 mergeGenStart相等后才属于正确的处理删除信息
,在其他流程点进入该流程点都属于未能正确处理删除信息。
处理FrozenBufferedUpdates的工作如下,仅列出跟本篇文章有关的工作:
是否已经处理过当前删除信息
流程)的计数为0,使得并发执行图2流程的线程直接退出基于篇幅,图1中剩余流程点留到下一篇文章介绍。
点击下载附件