huangfox

          韜光隱晦
          隨筆 - 1, 文章 - 8, 評論 - 1, 引用 - 0
          數據加載中……

          有關Lucene的問題(7):用Lucene構建實時的索引【轉】

            由于前一章所述的Lucene的事務性,使得Lucene可以增量的添加一個段,我們知道,倒排索引是有一定的格式的,而這個格式一旦寫入是非常難以改變 的,那么如何能夠增量建索引呢?Lucene使用段這個概念解決了這個問題,對于每個已經生成的段,其倒排索引結構不會再改變,而增量添加的文檔添加到新 的段中,段之間在一定的時刻進行合并,從而形成新的倒排索引結構。

            然而也正因為Lucene的事務性,使得Lucene的索引不夠實時,如果想Lucene實時,則必須新添加的文檔后IndexWriter需要 commit,在搜索的時候IndexReader需要重新的打開,然而當索引在硬盤上的時候,尤其是索引非常大的時候,IndexWriter的 commit操作和IndexReader的open操作都是非常慢的,根本達不到實時性的需要。

            好在Lucene提供了RAMDirectory,也即內存中的索引,能夠很快的commit和open,然而又存在如果索引很大,內存中不能夠放下的問題。

            所以要構建實時的索引,就需要內存中的索引RAMDirectory和硬盤上的索引FSDirectory相互配合來解決問題。

            1、初始化階段

            首先假設我們硬盤上已經有一個索引FileSystemIndex,由于IndexReader打開此索引非常的慢,因而其是需要事先打開的,并且不會時常的重新打開。

            我們在內存中有一個索引MemoryIndex,新來的文檔全部索引到內存索引中,并且是索引完IndexWriter就commit,IndexReader就重新打開,這兩個操作時非常快的。

            如下圖,則此時新索引的文檔全部能被用戶看到,達到實時的目的。

            有關Lucene的問題(7):用Lucene構建實時的索引

            2、合并索引階段

            然而經過一段時間,內存中的索引會比較大了,如果不合并到硬盤上,則可能造成內存不夠用,則需要進行合并的過程。

            當然在合并的過程中,我們依然想讓我們的搜索是實時的,這是就需要一個過渡的索引,我們稱為MergingIndex。

            一旦內存索引達到一定的程度,則我們重新建立一個空的內存索引,用于合并階段索引新的文檔,然后將原來的內存索引稱為合并中索引,并啟動一個后臺線程進行合并的操作。

            在合并的過程中,如果有查詢過來,則需要三個IndexReader,一個是內存索引的IndexReader打開,這個過程是很快的,一個是合并中索引 的 IndexReader打開,這個過程也是很快的,一個是已經打開的硬盤索引的IndexReader,無需重新打開。這三個IndexReader可以 覆蓋所有的文檔,唯一有可能重復的是,硬盤索引中已經有一些從合并中索引合并過去的文檔了,然而不用擔心,根據Lucene的事務性,在硬盤索引的 IndexReader沒有重新打開的情況下,背后的合并操作它是看不到的,因而這三個IndexReader所看到的文檔應該是既不少也不多。合并使用 IndexWriter(硬盤索引).addIndexes(IndexReader(合并中索引)),合并結束后Commit。

            如下圖:

            有關Lucene的問題(7):用Lucene構建實時的索引

            查看原圖(大圖)

            3、重新打開硬盤索引的IndexReader

            當合并結束后,是應該重新打開硬盤索引的時候了,然而這是一個可能比較慢的過程,在此過程中,我們仍然想保持實時性,因而在此過程中,合并中的索引不能丟 棄,硬盤索引的IndexReader也不要動,而是為硬盤索引打開一個臨時的IndexReader,在打開的過程中,如果有搜索進來,返回的仍然是上 述的三個IndexReader,仍能夠不多不少的看到所有的文檔,而將要打開的臨時的IndexReader將能看到合并中索引和原來的硬盤索引所有的 文檔,此IndexReader并不返回給客戶。如下圖:

            有關Lucene的問題(7):用Lucene構建實時的索引

            查看原圖(大圖)

            4、替代IndexReader

            當臨時的IndexReader被打開的時候,其看到的是合并中索引的IndexReader和硬盤索引原來的IndexReader之和,下面要做的是:

            (1) 關閉合并中索引的IndexReader

            (2) 拋棄合并中索引

            (3) 用臨時的IndexReader替換硬盤索引原來的IndexReader

            (4) 關閉硬盤索引原來的IndexReader。

            上面說的這幾個操作必須是原子性的,如果做了(2)但沒有做(3),如果來一個搜索,則將少看到一部分數據,如果做了(3)沒有做(2)則,多看到一部分數據。

            所以在進行上述四步操作的時候,需要加一個鎖,如果這個時候有搜索進來的時候,或者在完全沒有做的時候得到所有的IndexReader,或者在完全做好 的時候得到所有的IndexReader,這時此搜索可能被block,但是沒有關系,這四步是非常快的,絲毫不影響替代性。

            如下圖:

            有關Lucene的問題(7):用Lucene構建實時的索引

            查看原圖(大圖)

            經過這幾個過程,又達到了第一步的狀態,則進行下一個合并的過程。

            5、多個索引

            有一點需要注意的是,在上述的合并過程中,新添加的文檔是始終添加到內存索引中的,如果存在如下的情況,索引速度實在太快,在合并過程沒有完成的時候,內 存索引又滿了,或者硬盤上的索引實在太大,合并和重新打開要花費太長的時間,使得內存索引以及滿的情況下,還沒有合并完成。

            為了處理這種情況,我們可以擁有多個合并中的索引,多個硬盤上的索引,如下圖:

            有關Lucene的問題(7):用Lucene構建實時的索引

            查看原圖(大圖)

            新添加的文檔永遠是進入內存索引

            當內存索引到達一定的大小的時候,將其加入合并中索引鏈表

            有一個后臺線程,每隔一定的時刻,將合并中索引寫入一個新的硬盤索引中取。這樣可以避免由于硬盤索引過大而合并較慢的情況。硬盤索引的 IndexReader也是寫完并重新打開后才替換合并中索引的IndexReader,新的硬盤索引也可保證打開的過程不會花費太長時間。

            這樣會造成硬盤索引很多,所以,每隔一定的時刻,將硬盤索引合并成一個大的索引。也是合并完成后方才替換IndexReader

            大家可能會發現,此合并的過程和Lucene的段的合并很相似。然而Lucene的一個函數IndexReader.reopen一直是沒有實現的,也即 我們不能選擇哪個段是在內存中的,可以被打開,哪些是硬盤中的,需要在后臺打開然后進行替換,而IndexReader.open是會打開所有的內存中的 和硬盤上的索引,因而會很慢,從而降低了實時性。

          posted on 2010-09-25 16:15 fox009 閱讀(184) 評論(0)  編輯  收藏 所屬分類: 搜索引擎技術

          主站蜘蛛池模板: 沅江市| 开原市| 连州市| 宝兴县| 永胜县| 齐齐哈尔市| 建德市| 库伦旗| 醴陵市| 三台县| 阿图什市| 茌平县| 四会市| 榕江县| 洛浦县| 尖扎县| 石家庄市| 通道| 尼玛县| 平果县| 桃源县| 松江区| 康保县| 绵阳市| 五寨县| 克拉玛依市| 德钦县| 卢湾区| 思茅市| 富川| 舒兰市| 开阳县| 沧州市| 西乌珠穆沁旗| 全南县| 会同县| 桦甸市| 庐江县| 莱州市| 娄底市| 祁阳县|