(轉) lucene索引結構改進-支持單機十億級別的索引的檢索


術語解釋:

Lucene:是apache軟件基金會4 jakarta項目組的一個子項目,是一個開放源代碼的全文檢索引擎工具包,即它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的 查詢引擎和索引引擎,部分文本分析引擎(英文與德文兩種西方語言)。Lucene的目的是為軟件開發人員提供一個簡單易用的工具包,以方便的在目標系統中 實現全文檢索的功能,或者是以此為基礎建立起完整的全文檢索引擎。

二分查找: 二分查找又稱折半查找,優點是比較次數少,查找速度快,平均性能好;其缺點是要求待查表為有序表, 且插入刪除困難。因此,折半查找方法適用於不經常變動而查找頻繁的有序列表。首先,假設表中元素是按升序排列,將表中間位置記錄的關鍵字與查找關鍵字比 較,如果兩者相等,則查找成功;否則利用中間位置記錄將表分成前、后兩個子表,如果中間位置記錄的關鍵字大於查找關鍵字,則進一步查找前一子表,否則進一 步查找后一子表。重復以上過程,直到找到滿足條件的記錄,使查找成功,或直到子表不存在為止,此時查找不成功。

倒排索引:(英語:Inverted index),也常被稱為反向索引、置入檔案或反向檔案,是一種索引方法,被用來存儲在全文搜索下某個單詞在一個文檔或者一組文檔中的存儲位置的映射。它是文檔檢索系統中最常用的數據結構。
 

單機版的lucene只能應對千萬級,或百萬級的索引,通過修改索引,能夠支持10億以上索引的檢索。

當前lucene首次加載需要讀取整個索引文件,如果數據量較大,索引文件也很大,會照成內存瓶頸。

 

 

     Lucene 使用倒排索引進行文檔的檢索,假設存在三個文檔

中華人民共和國

人民英雄

中華美食

     Lucene在創建索引的的過程中,會將三個文檔按照分詞結果進行倒排,組成一個倒排表tis文件

中華 {1,3}

人民 {1,2}

共和國 {1}

英雄 {2}

美食 {3}

     這樣當用戶搜索“中華”這個關鍵詞的時候,根據倒排  “中華 {1,3}”就可以知道在文檔1和3中含有此關鍵詞,文檔1和3就會返回給用戶。

     我們通常將倒排表中的每個詞語叫做一個term,如果想知道,人民這個term對應那些文檔,必須先知道 人民這個term在倒排表中位置,然后才能知道這個term (人民)對應那些文檔({,2}),故首先要進行的是對term的查找,找到term所在的位置

以上述倒排表為例,假設,中華在倒排表中的偏移量為1,人民的偏移量為2,共和國為3,英雄為4,美食為5

這里的偏移量為距離文件起始的位置,知道了偏移量,就可以通過seek操作,將文件指針直接定位到目標term所在的位置,然后就可以讀取文檔ID,也就是查詢的結果了。

另外很重要的一點是,lucene創建索引的時候會保證倒排表的term是有序的。

     Lucene采用128跳躍表的方式創建索引,原理如下

由於lucene索引本身是有序的特點,lucene會在索引文件里存儲一些關鍵term,

 假設倒排表里一共有1280個term,那么第1個term,第129個term,257…,1281 一個11個關鍵term以及偏移量會存儲在索引文件tii中。

在進行term的檢索前,lucene會將tii文件中的所有關鍵term,加載到內 存里,以數組的形式存儲,當然也是有序的。當我們要檢索一個term,首先會在內存里檢索此term會落在那兩個關鍵term之間,因為有序的原因,目標 term肯定會在這兩個term之間,然后根據兩個關鍵term中較小的term的偏移量,從倒排表tis文件中的偏移量的位置之后開始查找,由於跳躍表 的間隔是128位,那么最多比較128次就可以查找到這個term了。

     128跳躍表存在的問題

在進行term檢索前,索引文件tii要全部加載到內存里,如果term的數量比較 少,那么不會存在問題,但是如果term特別多,比如說10億,那么也要消耗幾十G的內存(視term的長度不同而不同),普通物理機器一般是沒有這么大 的內存的,所以導致lucene因程序崩潰而無法進行檢索。另外每次加載索引也是很消耗時間的,如果初級開發者,使用不當,每次都是打開與關閉 lucene,那就會導致索引文件被反復重復加載,也是影響檢索性能的。

     那怎么解決?

Lucene倒排表tis文件有個顯著的特點是,term是有序的,而偏移量是定長的long類型,那么正好適合還用二分查找(折半查找)。

Tii索引文件存儲的內容修改為lucene倒排表tis中的每個term的偏移量。根據偏移量我們可以到tis文件中得到對應的term的值。

假設倒排表中一共有1000個term,我們的目標term在第501個term上, 那么二分查找首先會根據中間(折半)位置的term進行比較,也就是根第500個位置的term進行比較,發現目標term應該在500到1000這個區 間內,然后這個區間內在進行折半查找,定位在500~750這個區間,然后進一步定位500~625,500~564,500~532,500~516, 最終找到目標term的偏移量。

 

在這個過程中,索引文件沒有加載到內存里,對內存的依賴較少,假設有100億個term,那么最壞的情況,進行折半的次數為34次。

另外由於折半的特點,1/2,1/4,1/8,1/16…這些點都是高命中的點,可以 根據物理機器的內存多少,也可以預先加載到內存里,但是與128跳躍表相比,這里加載內存里的都是高命中的區域,對內存的使用率會高很多。如果緩存 16384個位置,那么久可以額外減少14次seek,那么100億僅僅需要20次的文件seek,但是如果跟原先的進行對比的話,舊的128跳躍表最壞 情況下需要128次seek,平均為64次seek,遠遠高於二分法的seek次數。而且二分法由於可以在高命中點使用cache,可以進一步減少 cache的數量。

 

     存在問題以及細節的優化

隨着term數量的增加,折半查找引起的seek的次數會增加,10000個term要進行12次查找,10萬要進行15次,100萬進行20次,1000萬23次,一億26次,10億29次,100億34次。

2. Term壓縮方式由原先,存儲上一條記錄的差異,存儲關鍵點的差異(這樣會照成壓縮比降低,但是二分法必須這樣做)

3.如果索引二分查找文檔差異<128則,保留原先鏈表順序查找,調用scan方法(這樣做盡管讀的次數增多,但考慮磁盤的物理特點,操作系統通常有文件緩沖區,連續的數據讀取速度會比不斷的跳躍的seek快,物理硬盤適合讀取連續的數據),這樣可以用1~128此的連續seek,來減少約6~7次左右的跳躍性的seek.

4. 由於norms同樣非常消耗內存,這里創建索引的時候禁用norms,待以后改進此處,當前lucene也存在此問題,不過也可以采用同樣的二分法來解決此問題,禁止全部加載進內存。

 

1.        Lucene TermInfosReader使用的128位的跳躍表,示例如下

 

 

在檢索的時候,跳躍表需要加載內存里,在千萬級別內的term,檢索速度比較理想(但是也需要1~128次的seek),但是如果term數量達到億的級別,有可能會突破單台機器物理內存的限制,目前業界幾乎都是采用分布式,打散成多個索引的方式來減少這個跳躍表的長度,但是單機也是能夠支持上十億keyvalue的檢索(這個地方可以采用定長的數據類型,而且luceneTermInfos這種結構,本身就是有序的,可以支持二分法查找,如果在結合cache,不但不會像跳躍表那樣耗費太多的內存,因減少了seek的數量,檢索時間也會有所提升,而且支持無限大的term數量,取決於硬盤大小)

    當前lucene首次加載需要讀取整個索引文件,一般需要成長連接的方式,對開發者要求較高,采用二分法的索引文件就沒有此問題。

 

 

 

下表為對100W~10億條md5值進行創建索引以及查詢的情況

讀的時間為查詢10Wmd5的時間,單位毫秒

寫為創建完整索引的時間,單位為毫秒。

 

記錄數

10W記錄時間

每條記錄時間

創建索引時間

索引總大小

Tii文件大小

 

一百萬

13667

0.13667

14338

87.6 MB

7.62 MB

 
 

二百萬

14400

0.144

25508

175 MB

15.2 MB

 
 

一千萬

20234

0.20234

120262

4.26 GB

381 MB

 
 

一億

2289399

22.89399

1360215

8.51 GB

762 MB

 

五億

3793413

37.93413

12249876

42.6 GB

3.72 GB

 

十億

5063614

50.63614

27365596

85.2 GB

7.45 GB

 
 

 

 

Lucene壓縮算法簡介

Lucene采用壓縮的方式進行創建索引

         對於字符串類型

文件鏈表的第一條記錄存儲的是完整的信息,第二條記錄存儲的是跟第一條記錄的差異。

舉例來說,第一條記錄為 abcdefg,如果第二條記錄為abcdefh,他們的差異只有h,故第二條記錄僅僅會存儲一個長度,加上差異的字符,就是存儲6+h

對於lucene這種索引結構來說,由於索引是排序過的,故壓縮比非常可觀。

         數值類型的

常見對於文件偏移量的壓縮,與字符串形式的壓縮類似,但采用的與前一條記錄的差值進行存儲,如果第一條記錄為8,第二條記錄為9,則第二條僅僅存儲9-8=1,lucene存儲整形也是變長的索引,lucene通常是以append的方式創建索引,故這種壓縮方式很有效。

         新索引對於壓縮的改變

由於上述lucene的 壓縮方式,在修改成二分法的時候,無法使用,故需要放棄一定的壓縮率。我們采取定義關鍵點的方式來壓縮,關鍵點存儲完整數據,后面的點跟關鍵點比較差異, 這個跟視頻圖像壓縮的關鍵幀非常相似,關鍵幀存儲完整的圖像信息,后面的幀僅僅存儲差異,變化。這樣可以節省計算差異的時間消耗(對lucene來說這點微不足道,不是這里解決的主要問題),但是壓縮比會降低。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM