Lucene的缺點

1.               倒排中以docid排序,這樣做的好處是多關鍵詞查詢時,merge算法自然,高效.支持phrase query; index merge階段,處理簡單.文件定位快速,倒排壓縮高效.但是,它的一個致命的缺陷在於:當某個term的倒排很長時,在處理一次search時,系統需要對倒排所有元素都進行處理.這樣的代價是不可接受的.這就註定了lucene不適合海量數據的檢索(當然Local partition的分佈式索引可以緩解這樣的問題).大量的文獻建議採用與query無關的ranking項進行排序.這樣一方面可以對倒排剪枝,另一方面加速search.但這樣的方法也有諸如:多關鍵詞結果merge時的低效,索引merge的算法複雜,建立索引的代價大等缺點.克服這些不足,需要對這兩者的優缺點進行相互的揚棄.目前考慮的是採用block的方法,即倒排中以block爲基本單位,block之間是ranking降序,而 block內採用docid排序.具體細節這裏不詳細展開.

2.          頻繁update的數據將使lucene對disk io影響巨大.lucene的增量索引是通過它的merge算法來實現的.而該merge算法導致頻繁的disk操作.一個新的數據的update,可能導致一部分根本沒有變化的索引被重寫很多次,並且可能導致很多的小的index segment,造成了search的性能下降,當然,用戶可以通過調節幾個參數來緩解這個問題.我們可以,兼顧索引效率和檢索效率,來重新設計 merge算法(中科院的firtex進行了部分嘗試,不過缺點依然明顯),可以設計Merge算法對於小的索引可以”越級”與大索引塊進行合併,來減少 disk io.根據倒排block設計的思路,我們可以根據某些經驗的統計量爲每個block預留一定空間,每個單元有標記.這樣,我們可以在一定程度上進行 update而根本不需要重寫部分索引,從而大大減少disk io.當有大量數據update時候,再採用segment合併的算法進行合併.同時每個block都應該有block head,保留Block的一些統計信息,以便在search的時候及早剪枝.

3。再挑挑刺,Lucene結構很清爽。但唯獨一個docid排序,這個假設,遍佈與整個代碼。慘不忍睹。

4。incremental fetch。lucene不支持從中間取索引。例如:用戶取第十頁,lucene需要把前面所有的內容都要檢索出,然後所有的排序,過濾掉前面的然後返回。雖然說,這個從用戶行爲來說(因爲大多數用戶還是看前面的,不會跳着來),不是什麼大問題。但是,這個畢竟可以解決。

5。lucene用java寫。但是clucene爲了保持與java lucene一致,用了很多難看的寫法。並且更新不及時。

6。scorer 和weight寫的比較難看。:)

7。doc-partition的模式,當然這個不是lucene本身的問題。doc- partition的方法有着很多不足,諸如全局統計量不準確,disk access大等等,但是大部分文章在綜合了系統構架的簡單性,網絡負載和負載均衡還是普遍認爲doc-partition比較優秀(google就是這種架構),當然針對doc模式的種種不足,也有很多的paper提出了改進的方法。我比較關注的是collection selection function, query log -based partition 和 hybrid architecture。

一 lucene文件的基本構架

      lucene文件結構的最大特點是其結構十分緊湊。從文件開始的第一個字節直到最後一個字節都是有效數據,中間沒有任何空閒的字節。這樣有優點也有缺點,優點是讀取迅速,缺點是修改複雜。因爲lucene的作者說lucene並不是爲修改頻繁的應用設計的,所以,文件結構這麼做是無可厚非的。在修改頻繁的環境下,lucene的性能註定會很差。如果是那樣的話,您或許需要考慮使用更好的技術,因爲增加一個文檔到索引其實可以做到十分迅速。

      在壓縮方面,lucene也採用了一些基本的方法。比如,它對int類型就進行了所謂的byte壓縮方法(最初級的方法)。不過,它在String上面採用的utf-8的編碼顯然會比utf-16編碼佔用更多的空間。其它地方還能夠看到壓縮的是Field Data(域值,.fdt)文件,這個文件保存的是文檔包含的域的具體文本(一個文檔可以劃分爲多個域,每個域都是一個字符串),顯然這是很大的數據(zlib好像在這裏比較常用,google據說也這樣壓縮,不過,文本壓縮的最好辦法顯然不是zip,更好的辦法還有ppmd)。

————————————————————————————————————————————————————————————————————————————-

二 lucene構建索引的性能 

      索引,專業點說,包含2種:前向索引和反向索引(倒排索引,inverted index)。前者表示的是某個文檔裏面的所有詞語,後者表示的是包含某個詞語的所有文檔。對應到Lucene上面,它的前向索引可以認爲是Term Vectors(詞語向量)相關文件,包含.tvx、.tvd和.tvf這3種文件。前向索引沒有什麼好評論的,它一般只是做爲重組原始數據時候的依據,其構建十分簡單明瞭。反向索引對應到Lucene上就是index(索引)。Lucene把索引劃分成一個一個的segment(塊,其實是一個小索引),直觀的說,當有一批新數據到達的時候,我們一般給其構建成一個新的segment,這是因爲修改原來的segment的代價很高(並不是說一定很高,只是lucene採用的文件結構無法簡單的加入新的文檔)。當一個index包含的segment太多的時候,查找性能就很差了(因爲一次查詢需要查詢多個segment),需要進行segment的合併。

      下面是index和segment的基本結構:

1.         index:

index包含4類文件:1)記錄segment信息的文件;2)指示索引是否正在更改的標記文件;3)簡單組合了若干個文件的複雜文件;4)segment文件及其附屬文件。

2.         segment:

segment其實是一個小型index,它包含了詞彙表、域表、反向索引表、域權重表、詞語向量(即前向索引)和已經刪除文檔表。詞彙表包括了本segment裏面出現的所有詞彙(記得詞彙不見得是真的詞語,它其實就是索引的字符串)。



三 lucene修改和刪除索引的性能

      嚴格的說,lucene底層並不支持對某個文檔的修改。因爲它的緊密結構抗拒了對文檔的直接修改。當需要修改某些文檔的時候,可以是這樣的:

1.         刪除這些文檔。這樣會使得這些文檔ID加入到已經刪除的文檔表裏面。

2.         構建新的索引。這樣會生成一個新的segment。

3.         合併索引的所有segment。這樣會把所有的segment都合併到一起,構成唯一的一個segment。

大家可以看到,如果僅僅從以上3步來看,lucene的修改索引的性能極差。好在可以利用緩衝,分批的懶惰的進行上面的第2步和第3步。



四 lucene的查詢性能

      我們從幾個方面來分析它的查詢性能:

1.         文件個數。文件個數越多,查詢的時候需要訪問的文件就越多,從而開銷也會越大。這是因爲要讀取的類似數據處在不連續的位置。當你把所有segment都合併成一個之後,這種問題就不存在了。可是,合併segment的花銷很大,需要謹慎考慮。

2.       索引詞彙。lucene的詞彙其實並不是簡單的詞彙,而是“域+詞彙”的保存形式。當域比較多的時候,這種方式的索引詞彙構建方式顯然會大大降低查找的效率。不過,值得一提的是,爲了降低空間佔用,lucene在排序詞彙之後,按照如下的形式進行保存: <PrefixLength, Suffix, FieldNum>,這裏,PrefixLength表示本詞彙借用了前面一個詞彙的前面PrefixLength個字符,Suffix表示本詞彙餘下的字符串,FieldNum表示本字符串屬於的域。

3.         布爾表達式計算。布爾表達式查找的時候,涉及到幾條詞彙倒排索引的合併的問題。未壓縮的索引合併是一個十分容易(不過,算法需要很精細才能優化各種情況)的事情,可是,lucene的索引經過壓縮了(包括前面提到的和相鄰數據相減的壓縮方法)以及String長度的不確定性,所以,我們無法根據詞彙直接定位到它對應的TermInfo(做爲一個變型,你可以在內存中爲它做個索引)。於是lucene就使用了SkipInterval/SkipData(樁,即定位標記)這類結構來加快比較速度,通過和它們的比較,可以簡單的跳過多個字節,從而加快了查找速度。當然了,這種策略比起直接的排序後2分查找顯然是慢了許多。

4.         權重計算。權重的計算顯然和文件結構沒有太大關係。但是,已知的是,lucene保存了每個詞彙的出現頻率和每個域的權重值,這樣就可以通過一些簡單的公式計算滿足要求的文檔對本次查詢的匹配度了。



五 Nutch對lucene的改進

      Nutch據說還是lucene的作者寫的,不過,這次這個高手打算直接和商業搜索引擎進行抗衡,他引入了分佈式的構架。Nutch一開始就是分佈式的,它本來就是定位在百以上量級的集羣系統(或者網格)上的。對於搜索引擎來說,除了抓取(或者還包含一些前期的數據處理)外,其餘的工作都是信息保存、索引構建和索引查找。Nutch使用的分佈式構架,它利用了多臺機器的性能來同時構建索引(這一點的可行性在講MapReduce的google論文裏面已經做了詳細的描述),這顯然能夠提高做索引的速度。在索引查找上面,因爲索引查找顯然不同於做索引,它要求極高的速度和不高的精度。簡單的基於MapReduce的方法的最大缺點就是速度慢(因爲它簡單嘛),所以,這位高手強烈建議不要使用分佈式的查找方法,因爲速度比單機查找還要慢很多(考慮一下,對於google來說,它的數據量據說達到上百個T,即10萬G,沒有機器可以掛上這麼大的硬盤吧?所以,他們肯定是分佈式查詢的)。可以肯定的是,Nutch在搜索方面對lucene的改進就是分佈式的做索引。當然了,Nutch比lucene好的地方在於它有了抓取程序(雖然十分的原始)。
-- _____________________________________________________________________________________________________________________________
6、Lucene 的內建不支持羣集。
Lucene是作爲嵌入式的工具包的形式出現的,在覈心代碼上沒有提供對羣集的支持。實現對Lucene的羣集有三種方式:1、繼承實現一個 Directory;2、使用Solr 3、使用 Nutch+Hadoop;使用Solr你不得不用他的Index Server ,而使用Nutch你又不得不集成抓取的模塊;

5、區間範圍搜索速度非常緩慢;
Lucene的區間範圍搜索,不是一開始就提供的是後來才加上的。對於在單個文檔中term出現比較多的情況,搜索速度會變得很慢。因此作者稱Lucene是一個高效的全文搜索引擎,其高效僅限於提供基本布爾查詢 boolean queries;
4、排序算法的實現不是可插拔的,因爲貫穿Lucene的排序算法的tf/idf 的實現,儘管term是可以設置boost或者擴展Lucene的Query類,但是對於複雜的排序算法定製還是有很大的侷限性;
3、Lucene的結構設計不好;
Lucene的OO設計的非常糟,儘管有包package和類class,但是Lucene的設計基本上沒有設計模式的身影。這是不是c或者c++程序員寫java程序的通病?
A、Lucene中沒有使用接口Interface,比如Query 類( BooleanQuery, SpanQuery, TermQuery...) 大都是從超類中繼承下來的;
B、Lucene的迭代實現不自然: 沒有hasNext() 方法, next() 返回一個布爾值 boolean然後刷新對象的上下文;
2、封閉設計的API使得擴展Lucene變得很困難;
參考第3點;
1、Lucene的搜索算法不適用於網格計算;

總結:6大理由選用Lucene

 

6. 沒有對集羣的內置支持。

如果你創建集羣,你可以寫出自己對Directory的實現,或是使用Solr或者使用Nutch+Hadoop。Solr和Nutch都支持Lucene,但是直接的替代。Lucene是可嵌入的,而你必須支持Solr和Nutch..我認爲Hadoop從Lucene團隊中產生並驚訝:Lucene並是通用的。它的內在性決定了對大多數場合來說它是非常快速的,但是對大型文檔集合時,你排除Lucene。因爲它在內核級別上並沒有實現集羣,你必須把Lucene轉換到別的搜索引擎,這樣做並直接。轉換到Solr或者Nutch上的問題會讓你遇到許多必要的麻煩:Nutch中的集成crawling和Solr中的檢索服務。

 

5.跨度查詢太慢

這對Lingway公司來說可能是個特殊的問題。我們對跨度查詢有很強要求,Lucene檢索結構已經開始添加這一細節,但它們當初可沒這麼想。最基礎的實現導致了複雜的算法並且運行緩慢,尤其是當某些短語在一份文檔中重複了許多次出現。這是爲什麼我傾向說Lucene是一個高性能的劃詞檢索引擎當你僅僅使用基本的布爾查詢時。

 

4.積分能被插件化

Lucene有自己對積分算法的實現,當條件增加時使用Similarity類。但很快它顯示出侷限性當你想要表示複雜的積分,例如基於實際匹配和元數據的查詢。如果你這樣做,你繼承Lucene的查詢類。因爲Lucene使用類似tf/idf的積分算法,然而在我們遇到的場合,在語意上的積分上Lucene的積分機制並合適。我們被迫重寫每一個Lucene的查詢類使得它支持我們自定義的積分。這是一個問題。

 

3.Lucene並非良好設計

作爲一個系統架構師,我傾向認爲(1)Lucene有一個非常糟糕的OO設計。雖然有包,有類的設計,但是它幾乎沒有任何設計模式。這讓我想起一個由C(++)開發者的行爲,並且他把壞習慣帶到了java中。這造成了,當你需要自定義Lucene來滿足你的需求(你將來必定會遇到這樣的需求),你必須面對這樣的問題。例如:

  • <!--[if !supportLists]--> <!--[endif]-->幾乎沒有使用接口。查詢類(例如BooleanQuery,SpanQuery,TermQuery…)都是一個抽象類的子類。如果你要添加其中的一個細節,你會首先想到寫一個接口來描述你擴展的契約,但是抽象的Query類並沒有實現接口,你必須經常的變化自己的查詢對象到Query中並在本地Lucene中調用。成堆的例子如(HitCollecor,…)這對使用AOP和自動代理來說也是一個問題.
  • <!--[if !supportLists]--> <!--[endif]-->彆扭的迭代實現.沒有hasNext()方法,next()方法返回布爾類型並刷新對象內容.這對你想要保持對迭代的元素跟蹤來說非常的痛苦.我假定這是故意用來節省內存但是它又一次導致了算法上的雜亂和複雜.

 

2.一個關閉的API使得繼承Lucene成爲痛苦

在Lucene的世界中,它被稱之爲特性。當某些用戶需要得到某些細節,方針是開放類。這導致了大多數的類都是包保護級別的,這意味着你能夠繼承他們(除非在你創建的類似在同一個包下,這樣做會污染客戶代碼)或者你複製和重寫代碼。更重要的是,如同上面一點提到的,這個嚴重缺乏OO設計的結構,一些類應該被設爲內部類卻沒有,匿名類被用作複雜的計算當你需要重寫他們的行爲。關閉API的理由是讓代碼在發佈前變得整潔並且穩定。雖然想法很光榮,但它再一次讓人感到痛苦。因爲如果你有一些代碼和Lucene的主要思路並吻合,你經常回歸Lucene的改進到你自己的版本直到你的補丁被接受。

然而當開發者開始越來越長的限制API的更改,你的補丁很少有機會被接受。在一些類和方法上加上final修飾符會讓你遇到問題。我認爲如果Spring框架有這樣的限制,是覺會流行起來。

 

<!--[if !supportLists]-->1.       Lucene搜索算法適合網格計算<!--[endif]-->

Lucene被寫出來的時候硬件還沒有很大的內存,多處理器也存在。因此,索引結構是被設計成使用線性的內存開銷很小的方式。我花了很長的時間來重寫跨度查詢算法,並使用多線程內容(使用雙核處理器),但是基於迭代器的目錄讀取算法幾乎能實現。在一些罕見的場合你能做一些優化並能迭代一個索引通過並行方式,但是大多數場合這是可能的。我們遇到的情況是,當我們有一個複雜的,超過50+的內嵌跨度查詢,CPU還在空閒但I/O卻一直忙碌,甚至在使用了RAMDirectory.

 

有沒有替代品?

我認爲最後一個觀點充滿疑問:Lucene到達了它的極限當它在現在硬件基礎的條件下,檢索大型數據集合時。那就是我爲什麼尋找下一個可以替代Lucene的出現。在閱讀了博客目錄和 Wikia的討論後,我發現並沒有很多的替代品。然而我最後推薦一個有希望的方案:MG4J。它有一個良好的面向對象設計,性能良好的檢索(索引比Lucene慢),內存開銷上也很小,達到10倍於Lucene速度的跨度查詢,在我的跨度查詢基準上,並且是原生上支持集羣。同樣它也內置了負載平衡,而Lucene最近才加入這項功能並且還是實驗性質的。然而MG4J仍然缺少一些特性例如簡單的索引指數,文檔移除和更簡單的使用索引處理。讓我感到高興的是我可以自定義Lucene上的功能在MG4J上只需花幾個小時,而在Lucene上卻需要數天。

 

我認爲對開源的搜索引擎來說仍然有發展空間,它是通過單臺電腦用有限的內存來索引批量文檔,而是通過透明的分佈式索引來提供對大型數據集合檢索更爲快捷的答案。你必利用應用來獲得集羣特性。Lucene對第一類搜索引擎有了很好的實現,單我認爲它並符合我們的需求:在一個合理的時間內找到最佳的答案。基於tf/idf的搜索算法和google的等級並是未來搜索引擎的趨勢。實現對原數據和語義的複雜查詢並找出相關的信息,這是Lingway公司(通過Lucene和其他搜索引擎技術)所作的,過它要求有更多支持新硬件的新技術。

 

使用Lucene的一個好理由

無論我如何指責Lucene,它仍然是java開源解決方案中的最佳實現。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章