Lucene的索引文件,會(huì)包含很多個(gè)segments文件,每個(gè)segment中包含多個(gè)documents文件,一個(gè)segment中會(huì)有完整的正向索引和反向索引。
在搜索時(shí),Lucene會(huì)遍歷這些segments,以segments為基本單位獨(dú)立搜索每個(gè)segments文件,而后再把搜索結(jié)果合并。

建立索引文件的過(guò)程,實(shí)際就是把documents文件一個(gè)個(gè)加入索引中,Lucene的做法是最開(kāi)始為每個(gè)新加入的document獨(dú)立生成一個(gè)segment,放在內(nèi)存中。而后,當(dāng)內(nèi)存中segments數(shù)量到達(dá)一個(gè)闕值時(shí),合并這些segments,新生成一個(gè)segment加入文件系統(tǒng)的segments列表中。
而當(dāng)文件系統(tǒng)的segments數(shù)量過(guò)多時(shí),勢(shì)必影響搜索效率,因此需要不斷合并這些segments文件。

那么Lucene的合并策略是什么?如何保證合適的segments數(shù)量呢?

其實(shí)Lucene有兩套基本的策略:
第一種策略實(shí)現(xiàn)代碼位于IndexWriter#optimize()函數(shù),其實(shí)就是把所有segments文件合并成一個(gè)文件。合并的算法是遞歸合并列表最后的mergeFactor個(gè)segments文件直到合并成一個(gè)文件。這兒mergeFactor是Lucene的一個(gè)參數(shù)。
btw: 從算法細(xì)節(jié)上看,其實(shí)我不是喜歡這段實(shí)現(xiàn),因?yàn)榱斜淼淖詈髆ergeFactor個(gè)文件內(nèi)容實(shí)際被掃描了segmens_count/mergeFactor次。如果分段歸納合并的方式不知道是否更好,每個(gè)segment文件內(nèi)容都將被掃描 ceil(Log_mergeFactor(segmens_count)) 或ceil(Log_mergeFactor(segmens_count)) +1次,是否更好?

第二種策略實(shí)現(xiàn)代碼位于IndexWriter#maybeMergeSegments()函數(shù)中,這個(gè)代碼就復(fù)雜了,它的基本策略就是要求確保合并后兩個(gè)公式的成立:
假定segments是個(gè)有序列表,B表示maxBufferedDocs,f(n)=ceil(log_M(ceil(n/B))),M表示mergeFactor,這兒maxBufferedDocs和mergeFactor是兩個(gè)參數(shù)
1. 如果第i個(gè)segment的documents數(shù)量為x,第i+1個(gè)segment的documents數(shù)量為y,那么f(x)>f(y)一定成立
2. f(n)值相同的segments的數(shù)量不得超過(guò)M。
那么maybeMergeSegments()函數(shù)是如何確保這兩個(gè)公式成立的呢?
1.首先,從代碼:
    protected final void maybeFlushRamSegments() throws IOException {
        
// A flush is triggered if enough new documents are buffered or
        
// if enough delete terms are buffered
        if (ramSegmentInfos.size() >= minMergeDocs
                
|| numBufferedDeleteTerms >= maxBufferedDeleteTerms) {
            flushRamSegments();
        }
    }
這兒minMergeDocs=maxBufferedDocs, 因此可以看出,當(dāng)內(nèi)存中緩存的segments被合并寫(xiě)回磁盤(pán)時(shí)生成的segment的document count等于或小于maxBufferedDocs(即minMergeDocs)。
補(bǔ)充:因?yàn)閙aybeMergeSegments()運(yùn)行在同步代碼中,因此只要ramSegmentInfos.size==minMergerDocs(即maxBufferedDocs)就會(huì)寫(xiě)回磁盤(pán),因此應(yīng)該不存在ramSegmentInfos.size>maxBufferedDocs才寫(xiě)回的情況。而且,但如果是這種情況,因?yàn)楹喜⒑蟮膕egment文件的document count過(guò)大,后面的maybeMergeSegments()將不做合并處理直接退出,上述公式就可能不成立,那么算法將有錯(cuò)。
----
2.
2.1 因此maybeMergeSegments()第一次執(zhí)行時(shí),所有segments的document count都小于等于maxBufferedDocs。此時(shí),從i=0開(kāi)始,合并i~i+mergeFactor-1個(gè)文件,如果合并后的doc count>maxBufferedDocs時(shí),保留第i個(gè)segment,否則繼續(xù)合并改變后的i~i+mergeFactor-1,直到doc count>maxBufferedDocs或所有segments文件個(gè)數(shù)已經(jīng)<mergeFactor了。經(jīng)過(guò)這樣一輪的合并,除了最后<mergeFactor個(gè)的doc counts<=maxBufferedDocs文件外,其它文件的doc counts一定都>maxBufferedDocs并<maxBufferedDocs*mergeFactor了。
 2.2 這時(shí),不理會(huì)最后<mergeFactor個(gè)doc count<maxBufferedDocs的文件,而后按2.1的同理規(guī)則,合并之前的文件,讓這些文件的最后<mergerFactor個(gè)segment符合 maxBufferedDocs<doc counts<=maxBufferedDocs*mergeFactor,之前的segment文件都符合maxBufferedDocs*mergeFactor<doc counts<=maxBufferedDocs*mergeFactor^2
2.3 重復(fù)2.2,最后得到的列表就會(huì)滿足上述兩等式的成立
---
3
之后,每次從內(nèi)存緩存中flush出一個(gè)新的segment時(shí),也就是往這個(gè)segments列表的最后增加一個(gè)doc_count<=maxBufferedDocs的文件,同樣執(zhí)行上述步驟2進(jìn)行合并,能夠始終保證上述兩公式的成立。
----
4
4.1
IndexWriter#addIndexesNoOptimize同樣借鑒使用了maybeMergeSegments()算法,區(qū)別此時(shí),實(shí)際是已有一個(gè)符合兩公式的segments序列T,在T之后追加上隨意順序的segments序列S。這時(shí),我們先找到S中doc count值最大的那個(gè)segment,計(jì)算出它屬于的區(qū)間f(x),使得maxBufferedDocs*mergeFactor^x<doc counts<=maxBufferedDocs*mergeFactor^(x+1),而后按2.2的算法合并出除了最后<mergerFactor個(gè)segments外, 之前所有segments都符合 a. doc count>maxBufferedDocs*mergeFactor^(x+1) b.符合上述2等式。
btw: 因?yàn)檫@兒調(diào)用IndexWriter#addIndexesNoOptimize傳入的參數(shù)是maxBufferedDocs*mergeFactor^(x+1),因?yàn)镾所有segment的doc count都一定小于maxBufferedDocs*mergeFactor^(x+1),因此S的所有元素都會(huì)參與收縮合并。
4.2 將最后<mergerFactor個(gè)doc count<maxBufferedDocs*mergeFactor^(x+1)的segments合并,如果合并后的segment的doc count大于maxBufferedDocs*mergeFactor^(x+1),就繼續(xù)2.2步驟,使得整個(gè)隊(duì)列符合上述兩公式
-----

上述兩種策略,最終確保了Lucene中的segments不會(huì)太多,確保效率。

BTW:實(shí)際上,如果documents太多時(shí),Lucene還支持把docuements分成幾個(gè)組,每個(gè)組用獨(dú)立的進(jìn)程或電腦進(jìn)行索引,而后再多個(gè)目錄的索引合并起來(lái),具體可參考IndexWriter#addIndexesNoOptimize和IndexWriter#addIndexes函數(shù)