1. 程式人生 > >Lucene與HBase的組合使用及HBasene的分析報告

Lucene與HBase的組合使用及HBasene的分析報告

Lucene簡介

  Lucene中,以document的形式作為搜尋的主體。document由fieldName和fieldValue所組成,每個fieldValue又可以由一個或多個term元素來組成。基於不同的分詞及索引規則,可用於搜尋fieldValue的term少於組成fieldValue的term。Lucene的搜尋基於反向索引,包含著可用於搜尋document的field資訊。通過Lucene,可以正向查詢document,以便了解其包含哪些field資訊;也可以通過反向索引,通過搜尋欄位的term,來查詢包含該term的document。

 

[ 圖1 ]  Lucene總體架構

  由圖1所示,IndexSearcher實現了搜尋的邏輯,IndexWriter實現了文件的插入與反向索引的建立,IndexReader由IndexSearcher呼叫以便讀取索引的內容。IndexReader和IndexWriter都依賴於抽象類Directory,Directory提供操作索引資料及的API。

  標準的Lucene是基於檔案系統和基於記憶體的。

  標準基於檔案系統的後端的缺點在於,隨著索引增加效能會下降,人們使用了各種不同的技術來解決這個問題,包括負載均衡和索引分片(index sharding,在多個Lucene例項之間切分索引)。儘管分片功能很強大,但它讓總體的實現架構變得更復雜,並且需要大量對期望文件的預測知識,才能對Lucene索引進行合適地分片。另一方面,在大資料量的情況下,segment的合併花銷巨大;頻繁的update資料將使得Lucene對Disk io產生巨大的影響。一個新的資料的update,可能導致一部分根本沒有變化的索引被重寫很多次,並且可能導致很多的小的index segment,造成了search的效能下降。

  Lucene的優勢在於索引查詢的迅速,而非document的儲存。為解決上述問題,基於NoSQL資料庫儲存索引的後端結構應運而生。

以下,將基於HBase的實現來進行分析。

實現方法

  在Lucene中,其會操作兩個單獨的資料集:

  • 文件資料集中儲存了所有文件,包括儲存的欄位等。
  • 索引資料集中儲存了所有欄位/詞彙/詞頻/位置等資訊,以及包含當前欄位的document

  如果要實現將Lucene的後端移植到HBase上,直接構建一個Directory的實現並不會是最簡單的。在已有的開源專案中,Lucandra和HBasene均採用了直接重寫IndexReader和IndexWriter的方式,直接繞開了Directory的API。此實現並不會重寫Lucene的索引查詢機制。如若過載IndexSearcher,則可以在使用現有的Lucene索引查詢機制上,根據後端的功能增強效能。

 

[ 圖2 ] Lucene的後端重新設計

  圖2的設計,可以將Lucene後端與HBase整合起來,將索引資料儲存到HBase中,從而利用HBase的大資料儲存以及分散式效能。

架構設計

  在架構設計上,將HBase用作索引的持久化後端,同時可以如網上所說,基於記憶體實現一套快取機制,用來提高資料讀取速度。實現一套高效的快取同步機制,也將有利於資料讀寫速率的提高。

 

[ 圖3 ] 帶有記憶體快取以及同步快取的HBase後端實現

  對於HBase的訪問,每一次互動都需要通過乙太網,乙太網的執行狀態將大大影響系統的使用情況,而索引的建立又希望能達到實時且高響應。為了平衡這兩種相互衝突的需求,在記憶體中,快取能夠最小化HBase用於搜尋和檔案返回的資料讀取量,從而極大提升效能;按照需要執行為多個Lucene示例以支援日益增長的搜尋客戶端的能力。後者需要最小化快取的生命週期,從而和HBase例項(上面提到例項的副本)中的內容同步。通過為活動引數實現可配置的快取時間,限制每個Lucene例項中展現的快取,我們可以達成一種折中方案。

  根據上述所描述的結構,對於讀操作,首先會檢查所需資料是否在記憶體中且沒有過期,如果有效將直接使用,否者將從HBase中獲取資料並更新到記憶體中。而對於寫操作,可以簡化到直接將資料寫入到HBase中,進而不需要考慮是否需要建立或更新快取這種複雜的問題,這也將提高系統的實時響應性。

HBase Table的實現

當前瞭解到的兩種可參考的實現方式:HBasene型別、Lucandra型別。

1-- HBasene

其索引表由以下幾個column family組成:

  • fm.sequence:記錄sequenceId,表示當前新增的第幾個document。在執行createLuceneIndexTable時建立該行,且rowKey為segmentId,Column.qulifier為qual.sequence,Column.value=-1。每add一個document,當前segmentId的Column.value將自增1。
  • fm.doc2int:每個document的儲存都將被分配一個唯一的id,如果document的Field.Store=YES,則能夠通過該id獲取到對應的document的全部資訊。
  • fm.fields:記錄了Field中value的內容,rowKey為documentId,Column.qulifier為FieldName,Column.value為FieldValue的內容。
  • fm.termVector:向量偏移資料,用於模糊查詢,記錄了偏移量等資訊,rowKey為FileldName/Term的組合,Column.qulifier為documentId,Column.value為指向的document中的所有位置偏移量。Column.value的結構為:[A][size][position]……[position]
  • fm.termFrequencies:關鍵詞在每個document中出現的頻率,rowKey結構為zfm/FileName/Term,Column.qulifier為documentId,Column.value為出現的次數。

2-- Lucandra

在Lucendra中,只有兩個ColummFamily來儲存資料,分別是TermInfo和Documents

<Keyspace Name="Lucandra"> 

    <ColumnFamily   Name="TermInfo" CompareWith="BytesType" ColumnType="Super"  CompareSubcolumnsWith="BytesType"  KeysCached="10%" /> 

    <ColumnFamily Name="Documents" CompareWith="BytesType" KeysCached="10%" /> 

</Keyspace>

  • TermInfo

TermInfo儲存了Lucandra逆向索引的資訊,用來儲存index的Field資訊,其結構如下:

RowKey:field/term

SuperColumn.name:documentId

[ SubColumn.name:"frequencies"  Column.value:count ]

[ SubColumn.name:"position"  Column.value:position vector ]

[ SubColumn.name:"offsets"  Column.value:offsets vector ]

[ SubColumn.name:"norms"  Column.value:norms vector ]

由於HBase中不存在SuperColumn和SubColumn的概念,我們可以將其簡化為:

RowKey:field/term

Column.qulifier:documentId

Column.value:fieldInfo [ 此fieldInfo可以通過AVRO類定義 ]

  • Documents

Documents儲存了Document資料,其結構如下:

RowKey:documentId

Column.name:fieldName

Column.value:fieldValue

對比:

由HBasene的表結構可以知道,對於每一個document的更新以及每一次term的查詢,都需要操縱多行資料才能實現,邏輯上相對複雜;而Lucandra只需要處理兩行(documents及TermInfo各一行)。但是HBasene的優勢在於單行的儲存結構小,每次與HBase互動只需要傳輸少量的資料及可,並且不像Lucandra結構一樣需要而外的AVRO元件來序列化與反序列化資料。

HBasene的缺陷:

1、HBasene在三年前已經停止了其專案的更新,開源的支援斷開了。

2、在對HBasene的最新原始碼分析過程中,首先是發現其設計上保留著segment的概念,但是其設計阻礙著document的查詢。其documentId由segmentId/sequenceId組成,每次segment的commit,sequenceId恢復為-1。而search時返回的TopDocs中只包含了sequenceId[int]。

3、每次新增document時,只有fm.Fields以及fm.doc2int的資料被實時flush到了HBase中,而其他資料需要segment大小到了一定程度[預設1000條document]才commit,容易造成資料缺失。在segment沒被commit的情況下,是無法查詢到新新增的document的。

4、HBase Table的設計中,沒有考慮到當document中fieldName相同的情況。當fieldName相同的情況下,後面新增的會覆蓋前面新增資料的,最後只保留最後新增的一份。

5、fm.termVector中,Column.value儲存的並不是positionVector資訊,而是segment中所有document裡出現的次數。

6、fm.termFrequencies只是單純地儲存了,並沒有被應用上。

7、IndexWriter的重寫並沒有實現所有函式,只實現了最基本的addDocument

8、對IndexSearch的重寫和擴充套件也不夠。