本文承接文档的增删改(上)、文档的增删改(中)、文档的增删改(下)(part 1)继续介绍文档的增删改,为了能深入理解,还是得先介绍下几个预备知识。
下面是Node类中仅有的两个成员变量:
xxxxxxxxxx
41static class Node<T> {
2 volatile Node<?> next;
3 final T item;
4}
多个Node对象通过next实现了队列结构,其中item为队列中某个结点(Node)的删除信息,每当DWPT处理一个删除信息,就会将该删除信息作为一个item加入到队列中,即deleteQueue。
在文档的增删改(上)我们已经介绍了删除的几种方式,其删除信息会生成不同的Node子类:
图1:
在多线程(持有相同IndexWriter对象的引用)执行删除操作时,多个DWPT将自己携带的删除信息生成Node对象,并添加到deleteQueue中,deleteQueue是一个全局的变量:
图2:
为什么上图的队列有 "多线程"的前提:因为单线程的话每当添加一个Node到队列中,该Node的信息会马上被添加到BufferedUpdates(下文中会介绍)中,故在单线程下,不会出现结点数量大于1个的队列。
该变量的定义如下:
xxxxxxxxxx
11 private volatile Node<?> tail;
tail用来指向deleteQueue中最后一个Node结点,也就是最新的一个删除操作(latest delete operation)。
下面是DeleteSlice仅有的两个成员变量:
xxxxxxxxxx
41static class DeleteSlice {
2 Node<?> sliceHead;
3 Node<?> sliceTail;
4}
DeleteSlice中的sliceHead、sliceTail指向deleteQueue中的Node结点。
在文档的增删改(中)中我们知道,每一个执行添加/更新操作的线程会先从DWPTP获得一个ThreadState,如果ThreadState中没有持有DWPT的对象引用,那么需要生成一个新的DWPT对象让其持有,并且每一个DWPT对象中都拥有一个私有的DeleteSlice对象,并且在初始化DeleteSlice对象,会让DeleteSlice对象的sliceHead、sliceTail同时指向tail指向的deleteQueue对象中的Node结点,即最新的一个删除操作,DeleteSlice类的构造函数如下,其中参数currentTail为tail:
xxxxxxxxxx
41DeleteSlice(Node<?> currentTail) {
2 // 另sliceHead、sliceTail与currentTail有相同的对象引用
3 sliceHead = sliceTail = currentTail;
4}
为什么sliceHead、sliceTail指向最后一个删除操作:因为最后一个删除操作只能对该删除操作执行前的添加的所有文档(满足删除要求的)进行删除,并且每一个DWPT中的索引信息最终会flush为一个段(在后面介绍flush时会详细介绍),而DWPT的私有DeleteSlice对象记录了DWPT添加的第一篇文档(最后一个删除操作不能作用于该文档及后面的所有文档)到生成一个段这段时间内所有的删除操作,该操作包括自己跟其他线程的删除操作,这些删除信息在flush时需要作用于(apply)该DWPT对应的段中的文档,即删除段中满足删除要求的所有文档。
上面的解释有点绕。。。自己看着都有些变扭。
所以简单的表述就是sliceHead、sliceTail必须指向最后一个删除操作的目的就是不让该操作及之前的删除操作添加到DWPT私有的DeleteSlice对象中,因为这些操作不能作用于后生成的新的文档。
有两个线程ThreadA、ThreadB,他们分别调用了updateDocument(Term, Document)和deleteDocuments(Querys),即分别会生成TermNode和QueryArrayNode。我们这里假设两个线程从DWPTP中获得的ThreadState对象持有的DWPT引用都是新生成的,并且当前DeleteQueue为空,并且假设(实际添加到deleteQueue顺序是不可确定的,下文会介绍)ThreadA先往deleteQueue中添加。
图3:
图4:
图5:
图6:
图6中,deleteQueue反映了两个情况:
情况1是因为删除操作不能作用于后新增的文档,情况2是因为删除操作要作用于该删除操作前已添加的所有文档。
由于DWPT初始化私有的DeleteSlice跟添加到删除结点到deleteQueue 这两个操作不是一个临界区内的两个操作,只有添加到deleteQueue是线程同步操作(synchronized),所以根据CPU调度的不同,同样是例子1中两个线程的删除操作,生成的deleteQueue不总是一样,下面例子的一个假设前提是deleteQueue为空。
例如图7跟图8是ThreadA跟ThreadB初始化各自私有的DeleteSlice,并且DeleteQueue还没有删除结点,ThreadA和ThreadB分别先
将删除结点添加到DeleteQueue中:
图7:
图8:
上文中提到一个DWPT从生成到flush到一个段,在这段期间其私有的DeleteSlice会记录所有的删除操作,这些删除操作会作用于(apply)DWPT添加的文档,即删除那些满足删除要求的文档,然而对于某一个删除操作来说,它只能作用于该删除操作前DWPT已添加的文档,其随后新添加的文档不能被apply,BufferedUpdates类就是通过以下几个Map对象来描述 某一个删除操作的作用范围(apply scope):
numericUpdates、binaryUpdate的介绍 见文章软删除softDeletes(一)。
在deleteTerms中,该Map的key为Term,表示包含该Term的文档都会被删除,value为一个哨兵值,描述了该删除操作的作用范围,即只能作用于文档号小于哨兵值的文档,这里需要补充一个概念:
在deleteQueries中,该Map的key为Query,表示满足该查询要求的文档都会被删除,value的概念同deleteTerms。
哨兵值怎么用:
期间
,利用倒排表中的信息找到包含该Term的所有文档的文档号,文档号小于哨兵值的文档都会删除,当然所谓的""删除""其实是将被删除的文档号从文档号集合(docId Set,0 ~ (numDocsInRAM - 1)的文档号集合)中剔除,在处理完所有的删除(比如下文中的deleteQueries)后,该集合会生成索引文件.liv之后
,通过查询的方式找出满足删除要求的文档号,然后从文档号集合中剔除这些文档号处理被删除的文档号的详细过程在介绍flush时会详细展开。
下面的例子介绍了当出现多个更新操作,并且存在相同删除操作的情况下,deleteTerms如何处理:
图9:
更新
为3,表示该删除作用范围是文档号在[0, 3)左闭右开的区间内的文档故最终deleteTerms中包含了两条删除信息: 图10:
在文档的增删改(上)的文章中,我们给出了文档的增删改流程图,我们紧接文档的增删改(下)(part 1),继续介绍剩余的两个流程点:处理删除信息、处理文档后的工作。
图11:
图12:
文档的增删改都需要处理删除信息。
图13:
图14:
检查其他线程是否新增了删除操作,这些删除操作要作用于DWPT已经添加的文档,判断方式通过判断DWPT的私有DeleteSlice对象中的sliceTail是否跟tail有相同的对象引用,如果不是,那么更新私有DeleteSlice,即另sliceTail引用跟tail相同的对象。
图15:
通过DWPT的numDocsInRAM是否大于0来判断是否已经添加过文档:
图16:
到达此流程点说明我们添加了一篇文档,故更新numDocsInRAM的值,即执行numDocsInRAM++的操作。
图17:
将DWPT自己的删除信息添加到DeleteQueue中,然后更新BufferedUpdates,注意的是,在上文中我们知道,当前DWPT添加到DeleteQueue时可能有其他的线程先添加了新的删除信息,故可能会更新自己以及其他线程的删除信息到BufferedUpdates。
由于删除文档的处理删除信息
的流程与添加/更新文档的处理文档后的工作
的流程有很多的相似点,又因为剩余的内容会导致本篇文章的篇幅过长,故在下一篇文章中展开。
无
点击下载附件