Lucene源碼閱讀 IndexWriter結構

索引訪問原則:

  1. 同一時刻,Lucene僅允許一個進程對其進行加入文檔、刪除文檔、更新索引等操作;
  2. 同一時刻,Lucene允許多個線程同時對其進行檢索。

索引的層次結構:

  1. 索引(Index):對於FSDirectory創建的索引庫來說,指定索引生成目錄後,目錄下生成的所有文件構成一個索引;
  2. 段(Segment):索引包含多個段,不同的段由不同的寫入線程構成,段與段之間相互獨立,可以根據合併策略合併,其中,segments.gen和segments_5是段的元數據文件,保存了段的屬性信息。
  3. 文檔(Document):段包含多個文檔,是編寫代碼控制的新建索引的基本單位,相當於MySQL表中的一條數據。
  4. 域(FIeld):一個文檔包含多個域,域相當於MySQL表中的列名。
  5. 詞(Term):詞是索引的最小單位,是經過詞法分析和語言處理後的字符串。

IndexWriter

try {
	Directory dir = new FSDirectory(Path.get("D:\\123"));
	IndexWriterConfig config = new IndexWriterConfig(analyzer);
	IndexWriter writer = new IndexWriter(dir, config);
	Document document = new Document();
	Field titleField = new Field("name", "name", TextField.TYPE_STORED);
	document.add(titleField);
	try {
		writer.addDocument(document);
	} catch (IOException e) {
		log.error("索引創建失敗", e);
	}
	writer.close();
}
catch (Exception e) {
	LOGGER.error("索引構建失敗", e);
}

IndexWriter對象:IndexWriter主要負責對索引的寫入和索引的整體的維護,如合併,優化等操作;它由IndexWriterConfig和Directory兩個參數構造而成。

  • IndexWriterConfig對象:IndexWriterConfig類似於Mybatis中的Configuration文件,主要涵蓋了索引操作所需的各種操作策略及索引管理方法等。
  • Directory對象:索引文件的集合,用於管理索引文件,包括數據的讀寫,索引的添加刪除等,提供了多種實現以支持在不同的存儲結構中存儲數據。

IndexWriterConfig對象屬性詳解:

  1. OpenMode:索引創建模式,包含CREATE(新建)、APPEND(追加)、CREATE_OR_APPEND(目錄下沒有則新建,否則追加)三種模式;
    • 採用APPEND模式如果當前目錄下沒有索引庫則會出現 no segments* file found in LockValidatingDirectoryWrapper 異常;
    • 如果索引庫由於某些特殊情況導致索引庫異常,可以通過CREATE重建索引庫;
  2. IndexDeletionPolicy:用於管理CommitPoint(每次Commit之後都會生成一個CommitPoint,用於索引庫的回滾);
    • KeepOnlyLastCommitDeletionPolicy:僅保留最後一次Commit後產生的CommitPoint;
    • NoDeletionPolicy:保留所有的CommitPoint;
    • SnapshotDeletionPolicy:封裝了其他的索引刪除策略,可以通過snapshot()方法保留提交的快照,通過release()方法釋放已保存快照,快照保存在內存中;
    • PersistentSnapshotDeletionPolicy:與SnapshotDeletionPolicy類似,區別在於這種方式保留的快照會持久化到硬盤上,通過保留segment_N的方式實現; writer.commit(); persistentSnapshotDeletionPolicy.snapshot();
  3. IndexCommit:IndexWriter允許從某一個提交點打開索引庫,但是在CREATE模式下不允許setIndexCommit,會導致報錯。
    • IndexCommit索引的變更如果要可見(如IndexDeletionPolicy,IndexReader中),必須提交COMMIT。每次提交都有一個唯一的segments_N文件與之關聯。默認NULL。
  4. Similarity:Lucene核心的相關性算法,在數據寫入和搜索時都會執行相關性算法,Lucene默認實現了TF-IDF和BM25算法。
    • 數據寫入時會計算數據的標準化因子(Normalization Factor)並寫入索引,標準化因子會影響查詢時的打分計算,標準化因子的計算公式爲 Document Boost(文檔重要性,越高越重要) * Field Boost(域越大,越重要) * LengthNorm(Field)(域中包含的Term越少,文檔越短,越重要) * π
    • 數據讀取時主要根據Term的出現次數及Term的普遍程度,再乘以用於區分Term重要性的標準化因子,得出最終分數並排序
  5. MergePolicy:Index內多種行爲可能觸發Merge,比如Commit、Flush、NRT reader open。Lucene內部提供LogMergePolicy、TieredMergePolicy(默認)等策略。
    • LogMergePolicy:一種定長的合併方式,通過maxLevel、LEVEL_LOG_SPAN、levelBottom參數將連續的段分爲不同的層級,再通過mergeFactor從每個層級中選取段進行合併。levelBottom(從左開始,大於這個值的段即每層結束位置) = maxLevel(最大段值) - LEVEL_LOG_SPAN(常量)
      • LogByteSizeMergePolicy:標明瞭maxMergeSize,超過這個段值則不會被合併;minMergeSize,用於處理大量的小段,將小於這個值的段劃分爲一層
      • LogDocMergePolicy:與LogByteSizeMergePolicy的區別在於不是通過文件大小,而是通過文檔數量判斷段的大小
    • TieredMergePolicy:與LogMergePolicy的區別在於會先對IndexWriter提供的段集進行排序,然後在排序後的段集中選取部分(可能不連續)段來生成一個待合併段集,即非相鄰的段文件(Non-adjacent Segment)。
    • UpgradeIndexMergePolicy:用於老版本段與新版本之間的兼容合併。
  6. MergeScheduler:它用於執行一個或多個段的合併,並且提供了對Merge過程定製管理的能力。它也包含了多種合併策略以應對不同的情況。
    • NoMergePolicy:在這種策略下,即使你定義了合併策略,也不會執行段合併。
    • SerialMergeScheduler:多個線程使用同一個IndexWriter對象來生成索引時,當他們分別執行flush、commit操作後,就各自從合併策略中得到各自的OneMerge,這些OneMerge構成的集合就是MergeSpecification,接着MergeSpecification中的OneMerge被添加到pendingMerges中,pendingMerges是一個有序列表,IndexWriter通過synchronized關鍵字有序的將所有線程中獲得的OneMerge添加到pendingMerges鏈表中,執行合併操作時,便從pendingMerges鏈表中取出OneMerge順序合併,由於合併方法包含Synchronized,不允許多線程合併操作
    • ConcurrentMergeScheduler:使用這種執行策略,可以併發執行合併操作,且對象會通過一些參數限制最大合併線程數量、合併線程的I/O節流等。
  7. Codec:編碼或解碼一個倒排索引段,用於生成一個新的段。
  8. IndexerThreadPool:管理IndexWriter內部索引線程(DocumentsWriterPerThread)池,爲使用同一個IndexWriter對象的併發操作提供多線程支持。
  9. ReaderPooling:實例化IndexReader是非常昂貴的操作,且它是一個線程安全的,跟索引目錄是一一對應的,最好的方式就是用一個Pool去維護這些IndexReader:保證一個文件目錄只有一個實例,且不同的IndexReader可以動態的組合。默認爲false 不使用Pool。
  10. FlushPolicy:決定了In-memory buffer何時被flush,默認的實現會根據RAM大小和文檔個數來判斷Flush的時機,FlushPolicy會在每次文檔add/update/delete時調用判定。
  11. RAMPerThreadHardLimitMB:設置每個線程最大的內存用量,超出則會自動執行flush操作。
  12. MaxBufferedDoc:Lucene提供的默認FlushPolicy的實現FlushByRamOrCountsPolicy中允許DocumentsWriterPerThread使用的最大文檔數上限,超過則觸發Flush。
  13. RAMBufferSizeMB:Lucene提供的默認FlushPolicy的實現FlushByRamOrCountsPolicy中允許DocumentsWriterPerThread使用的最大內存上限,超過則觸發flush。
  14. Analyzer:Lucene分詞器。
  15. MergedSegmentWarmer:預熱合並後的新段,使其元數據能提前被Reader讀取。
  16. InfoStream:用於調試信息的管理,默認爲InfoStream.getDefault()不記錄任何調試信息。

Directory對象

  1. RAMDirectory:RAMDirectory是內存中的一個區域,只需要簡單的使用構造函數就可以得到其實例。
  2. FSDirectory:通過NativeFSLockFactory(本地文件鎖工廠)進行鎖的實現的一個本地索引庫。在諸如Linux, MacOSX, Solaris和windows 64位操作系統的JRE中,使用MMapDirectory方式,在其他非windows操作系統的JRE中,使用NIOFSDirectory方式,其他windows操作系統的JRE中,使用SimpleFSDirectory方式。
    • SimpleFSDirectory:FSDirectory的簡單實現,併發能力有限,遇到多線程讀同一個文件時會遇到瓶頸。
    • NIOFSDirectory:通過java.nio’s FileChannel實行定位讀取,支持多線程讀(默認情況下是線程安全的)。該類僅使用FileChannel進行讀操作,寫操作則是通過FSIndexOutput實現。 注意:NIOFSDirectory 不適用於Windows系統,另外如果一個訪問該類的線程,在IO阻塞時被interrupt或cancel,將會導致底層的文件描述符被關閉,後續的線程再次訪問NIOFSDirectory時將會出現ClosedChannelException異常,此種情況應用SimpleFSDirectory代替。
    • MMapDirectory:通過內存映射進行讀,通過FSIndexOutput進行寫的FSDirectory實現類。使用該類時要保證用足夠的虛擬地址空間。另外當通過IndexInput的close方法進行關閉時並不會立即關閉底層的文件句柄,只有GC進行資源回收時纔會關閉。

Directory其他相關類:

  1. FileSwitchDirectory:文件切換的Directory實現.針對lucene的不同的索引文件使用不同的Directory .藉助FileSwitchDirectory整合不同的Directory實現類的優點於一身。
  2. RateLimitedDirectoryWrapper:通過IOContext來限制讀寫速率的Directory封裝類。
  3. CompoundFileDirectory:用於訪問一個組合的數據流。僅適用於讀操作。對於同一段內擴展名不同但文件名相同的所有文件合併到一個統一的.cfs文件和一個對應的.cfe文件內。
  4. TrackingDirectoryWrapper:Directory的代理類。用於記錄哪些文件被寫入和刪除。

Lucene通過DocumentWriterPerThread的方式支持多線程寫入,每個DocumentWriterPerThread單獨寫一個Segment; Segment在Commit、Flush、NRT reopen時都可能觸發Merge操作,Merge是Segment的整理操作。

IndexWriter採用了文件鎖,IndexWriter對象生成時會在索引目錄下生成writer.lock文件,索引下包含這個文件即無法再創建IndexWriter對象否則會出現如下異常,直到現有的IndexWriter對象被close掉。

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