從概念理解Lucene的Index(索引)文檔模型

Lucene主要有兩種文檔模型:Document和Field,一個Document可能包含若干個Field。

每一個Field有不同的策略:

1.被索引 or not,將該字段(Field)經過分析(Analyisi)後,加入索引中,並不是原文

2.如果被索引,可選擇是否保存“term vector”(向量),用於相似檢索。

3.可選擇是否存儲(store),將原文直接拷貝 ,不做索引,用於檢索後的取出。

Lucene中的文檔模型類似於數據庫,但是又不完全相同,體現在如下幾方面:

1.無規範格式,即無需固定的Schema,無列等預先設計,同一個索引中加入的Document可包含不同的Field

2.非正規化,Lucene中的文檔模型是一個平面化 的結構,沒有遞歸定義,自然連接等等複雜的結構。

2.2  理解索引過程

總體來說,索引過程爲:

1.提取摘要:從原文提取,並創建Document和Field對象。Tika 提供了PDF、Word等非文本的文本提取。

2.分析:Analysis,首先對Document的Field進行分解,產生token流,然後經過一系列Filter(如小寫化)等。

3.建立索引:通過IndexWriter的addDocument寫入到索引中。Lunece使用了反向索引 ,即“那個Document包含單詞X”,而不是“Document包含哪些Word”

索引文件組成

爲了保證效率,每個索引由若干segments組成:

_X.cfs  每個segments由若干個cfs組成,X爲0,1,2….如果開啓了useCompoundFile,則只有一個.cfs文件。

segments_<N>:記載每個分區對應的cfs文件。

每個一段時間後,在調用IndexWriter時,會自動合併這些segment

2.3  索引的基本操作

首先創建IndexWriter

IndexWriter(dir,new WhiteSpaceAnalyser(),IndexWriter.MaxField.UNLIMITED);

dir是索引的保存路徑,WhiteSpaceAnalyser是基於空白的分詞 ,最後部限定Field的數量。

依次創建文檔Document和Field

Document doc = new Document();

doc.add(new Filed(key,value,STORE?,INDEX?)

key就是field的檢索字段名,value就是待寫入/分析的文本。

STORE ,與索引無關,是否額外存儲原文 ,可以在搜索結果後調用出來,NO不額外存儲;YES,額外存儲。

INDEX ,NO,不索引;ANALYZED,分詞後索引;NOT_ANALYZED,不分詞索引;ANALYZED_NO_NORMS,分詞索引,不存儲NORMS;NOT_ANALYZED_NO_NORMS,不分詞,索引,不存儲NORMS。除了NO外都算索引,可以搜索 。NORMS存儲了boost所需信息,包含了NORM可能會佔用更多內存?

刪除索引

IndexWriter提供了刪除Document的功能:

deleteDocumen(Term) 

deleteDocumen(Term[])

deleteDocumen(Query)

deleteDocumen(Query [])

特別注意Term不一定是唯一的,所以有可能誤刪除多個 。另外最好選擇唯一的、非索引的Term 以防混亂(比如唯一ID)。

刪除後commit()然後close才能真正寫入索引文件中。

刪除後只是標記爲刪除,maxDoc()返回所有文檔(含已經刪除,但未清理的);numDocs:未刪除的文檔數量

使用delete後,再optimize():壓縮刪除的空間、再commit才真正的刪除釋放空間。

更新索引

updateDocument(Term,Document),Lunce只支持全部替換,即整個Docuemnt要被替換掉,沒法更新單獨的Field。

2.4  Field的選項

選項分爲三類:index、storing和term vector。

Index選項

Index.ANALYZED :分詞後索引

Index.NOT_ANALYZED : 不分詞直接索引,例如URL、系統路徑等,用於精確檢索

Index.ANALYZED_NO_NORMS : 類似Index.ANALYZED,但不存儲NORM TERMS,節約內存但不支持Boost。

Index.NOT_ANALYZED_NO_NORMS : 類似Index.NOT_ANALYZED,但不存儲NORM TERMS,節約內存但不支持Boost,非常常用

Index.NO : 根本不索引,所以不會被檢索到

默認情況,Luncene會存儲所有單詞的出現位置,可以用Field.setOmitTermFreqAndPositions(true)關閉,但是會影響PhraseQuery和SpanQuery。

Store選項

Store.YES :存儲原始value數值,可在檢索後被提取

Store.NO :不存儲原始數值,檢索後無法重新提取。

CompressionTools 可用於壓縮、解壓縮byte數組。

Term Vector選項

Term Vector主要用於爲相似搜索 提供支持 ,例如搜索cat,返回cat。

TermVector.YES :記錄Term Vector

TermVector.WITH_POSITIONS :記錄Term Vector以及每個Term出現的位置

TermVector.WITH_OFFSETS :記錄Term Vector以及每個Term出現的偏移

TermVector.WITH_POSITIONS_OFFSETS :記錄Term Vector以及出現的位置+偏移

TermVector.NO :不存儲TermVector

如果Index選擇了No,則TermVector必須選擇No

將String外的類型作爲Field的數據源

Reader:無法被STORE,默認TokenStream始終被分詞和索引。

TokenStream:分詞之後的結果作爲源,無法被Store,始終analyzed並索引。

byte[] :無法被索引,沒有TermVector,必須被Store.YES

與排序相關選項

數字Field可以用NumericField,如果是文本Field必須Field.Index.NOT_ANALYZED,才能排序,即保證這個Field只含有一個Token才能排序

多值Field(Multi-valued Fields)

比如一本書有多個作者,怎麼辦呢?

一種方法是,添加多個同一key,不同value的Field

  Document doc = new Document();
    for (int i = 0; i < authors.length; i++) {
      doc.add(new Field(“author”, authors[i],
                        Field.Store.YES,
                        Field.Index.ANALYZED));
    }

還有一種方法在第4章中提出。

2.5  Boost(提升)

boost可以對影響搜索返回結果的排序

boost可以在index或者搜索時候完成,後者更具有靈活性可獨立制定但耗費更多CPU。

Booost Doument

index時候boost將存儲在NORMS TERM中。默認情況下,所有Document有相等的Boost,即1.0,可以手動提升一個Docuemnt的Boost數值。

Document.settBoost(float bei),bei是1.0的倍數。

Boost Field

也可以對Field進行索引,使用Document的Boost,對下屬的Field都執行相同的Field。

單獨對Field進行Boost

Field.boost(float)

注意:Lucene的Rank算法由多種因素組成,Boost只是一個因素之一,不是決定性因素

Norms

boost的數值存儲在Norms中,可能會導致Search時佔用大量內存。因此可將其關閉:

設置NO_NORMS,或者再Field中指定Field.setOmitNorms(true)。

 2.6  對數字、日期、時間等進行索引

索引數字

有兩種場景:

1.數字嵌入在Text中,例如“Be sure to include Form 1099 in your tax return”,而你想要搜索1099這個詞。此時需要選擇不分解數字的Analyzer ,例如WhitespaceAnalyzer或者StandardAnalyzer。而SimpleAnalyzer和StopAnalyzer會忽略數字,無法通過1099檢出。

2.數字式單獨的Field,2.9之後,Lucene支持了數字類型,使用NumericField即可:doc.add(new NumericField(“price”).setDoubleValue(19.99));此時,對數字Field使用字典樹存儲,

可向document中添加一樣的NumericField數值,在NumericRangeQuery、NumericRangeFilter中以or的方式支持,但是排序中不支持。因此如果要排序,必須添加唯一的NumericField。

precisionStep控制了掃描精度,越小越精確但速度越慢。

索引日期和時間

方法是:將日期轉化爲時間戳(長整數) ,然後按照NumericField進行處理。

或者,如果不需要精確到毫秒,可以轉化成秒處理

  doc.add(new NumericField(“day”) .setIntValue((int) (new Date().getTime()/24/3600)));

甚至對某一天進行索引而不是具體時間。

    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    doc.add(new NumericField(“dayOfMonth”)
            .setIntValue(cal.get(Calendar.DAY_OF_MONTH)));

 2.7  Field截斷

   Lucene支持對字段的截斷。IndexWriter.MaxFieldLength表示字段的最大長度,默認爲MaxFieldLength.UNLIMITED,無限。

而MaxFieldLength.LIMITED表示有限制,可以通過setMaxFieldLength(int n)進行指定。

上述設定之後,只保留前n個字符。

可以通過setInfoStream(System.out)獲得詳細日誌信息。

2.8  實時搜索

2.9後支持實時搜索,或者說很快的入索引–檢索過程

IndexReader  IndexWriter.getReader()

本方法將立即刷新Index的緩存,生效後立即返回IndexReader用於搜索。

2.9  優化索引

索引優化可以提升搜索速度 ,而非索引速度。它指的是將小索引文件合併成幾個。

IndexWriter提供了幾個優化方法:

optimize():將索引合併爲一個段,完成前不會返回。但是太耗費資源。

optimize(int maxNumSegments):部分優化,優化到最多maxNumSegments個段?是優化於上述極端情況的這種,例如5個。

optimize(boolean doWait):通optimize(),但是它將立即返回。

optimize(int maxNumSegments, boolean doWait):同optimize(int maxNumSegments),但是將立即返回。

另外:在優化中會耗費大量的額外空間 。即舊的廢棄段直到IndexWriter.commit()之後才能被移除

2.10  Directory

Directory封裝了存儲的API,向上提供了抽象的接口,有以下幾類:

SimpleFSDirectory:存儲於本地磁盤使用java.io,不支持多線程,要自己加鎖

NIOFSDirectory:多線程可拓展,使用java.nio,支持多線程安全,但是Windows下有Bug

MMapDirectory:內存映射存儲(將文件映射到內存中進行操作,類似nmap)。

RAMDirectory:全部在內存中存儲。

FileSwitchDirectory:使用兩個目錄,切換交替使用。

使用FSDirectory.open將自動挑選合適的Directory。也可以自己指定:

Directory ramDir = new RAMDirectory();
IndexWriter writer = new IndexWriter(ramDir, analyzer,  IndexWriter.MaxFieldLength.UNLIMITED);

RAMDirectory適用於內存比較小的情況。

可以拷貝索引以用於加速:

Directory ramDir = new RAMDirectory(otherDir);

或者

Directory.copy(Directory sourceDir,
               Directory destDir,
               boolean closeDirSrc);

2.11  線程安全、鎖

線程、多JVM安全

任意多個IndexReaders可同時打開,可以跨JVM。

同一時間 只能打開一個 IndexWriter,獨佔寫鎖 。內建線程安全機制。

IndexReaders可以在IndexWriter打開的時候打開。

多線程間可共享IndexReader或者IndexWriter,他們是線程安全的,內建同步機制且性能較高。

通過遠程文件系統共享IndexWriter

注意不要反覆打開、關閉,否則會影響性能。

Index的鎖

以文件鎖的形式,名爲write.lock。

如果在已經被鎖定的情況下再創建一個IndexWriter,會遇到LockObtainFailedException。

也支持其他鎖定方式,但是一般情況下無需改變它們。

IndexWriter.isLocked(Directory):檢查某目錄是否被鎖。

IndexWriter.unlock(Directory):對某目錄解鎖,危險!。

注意!每次IndexWriter無論執行了什麼操作,都要顯示的close !不會自動釋放鎖的!

2.12  調試索引

2.14  高級的索引選項

IndexReader可以用來徹底刪除已經去除的Index,優點如下:

1.通過Document的具體Number來刪除,更精確而IndexWriter不行。

2.IndexReader可以在刪除後立即顯示出來,而IndexWriter必須重新打開才能顯示出來。

3.IndexReader擁有undeleteAll,可以撤銷所有刪除的索引(只對尚未merged的有效 )。

釋放刪除索引後的空間

可以調用expungeDeletes顯示的釋放空間,它將執行merge從而釋放刪除但僅僅做了標記,尚未釋放的空間。

緩存和刷新

當添加索引、刪除索引時候,在內存中建立了一個緩存以減少磁盤I/O,Lucene會定期把這些緩存中的改動放入Directory中便形成了一個segment (段)。

IndexWriter刷新緩存的條件是:

當內存中數據已經大於setRAMBufferSizeMB的指定。

當索引中的Document數量多於setMaxBufferedDocs的指定。

當索引被刪除的數量多於setMaxBufferedDeleteTerms的指定。

上述條件之一發生時,即觸發緩存刷進,它將建立新的Segment但不存入磁盤,只有當commit後才寫入磁盤的index。

索引的commit

commit將改動持久化到本次索引中。只有調用commit後,再打開的IndexReader或者IndexSearcher才能看到最近一次commit之後的結果。

關閉close也將間接調用commit。

與commit相對的是rollback方法,它將撤銷上次commit之後的所有改動。

commit非常耗時,不能經常調用。

“雙緩衝”的commit

在圖形界面開發中,經常有雙緩衝技術,即一個用於被刷新,一個用於顯示,兩個之間互換使用。Lucene也支持這樣的機制。

Lucene暴露了兩個接口:

prepareCommit

Commit

prepareCommit比較慢,而調用prepareCommit後再調用Commit則會非常快。

刪除策略

IndexDeletionPolicy決定了刪除策略。可以決定是否保留之前的commit版本。

Lucene對ACID的事務支持

這主要是通過“同時只能打開一個IndexWriter”來實現的。

如果JVM、OS或者機器掛了,Lucene會自動恢復到上一個commit版本。

合併Merge

當索引有過多的Segnmnet的時候,需要進行合併Merge。優點:

1.減少了Segnment的文件數量

2.減少索引文件佔用的空間大小。

MERGEPOLICY決定何時需要執行合併Merge

MERGEPOLICY

選擇那些文件需要被合併,默認有兩種策略:

LogByteSizeMergePolicy :根據Index大小決定是否需要合併

LogDocMergePolicy :根據Document的數量決定是否需要合併

分別通過

setMergeFactor

和setMaxMergeDocs來指定,具體參數見API。

MERGESCHEDULER

決定如何進行合併:

ConcurrentMergeScheduler,後臺額外線程進行合併,可通過waitForMerges得知合併完成。

SerialMergeScheduler,在addDocument時候串行合併,使用統一線程。

發佈了29 篇原創文章 · 獲贊 2 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章