【搜索引擎】lucene事务
本文分兩部份,第一部份為譯:是對是對于lucene事務(wù)的一篇佳作《Transactional Lucene》的翻譯。第二部份為解:是本人對一文中提到一些概念在源碼層次的一些理解分析,參考lucene源碼版本為4.10.4。《Transactional Lucene》中還提到了多commit在實際生產(chǎn)中的一些妙用,值得參考。
一、譯
很多用戶并不了解Lucene API的事務(wù)語義及其在搜索應(yīng)用中的用途。對于初學(xué)者應(yīng)當(dāng)了解的Lucene ACID特性如下:
- Atomiciy 原子性
當(dāng)你在一次IndexWriter session中做操作(增加,刪除文檔),然后commit,要么你的所有的操作修改都是可見的(commit成功),要么所有的操作修改都不可見(commit失敗),絕不會處于某種中間狀態(tài)。有些方法有它自身的原子操作:如果你調(diào)用updateDocument方法,其內(nèi)在實現(xiàn)是先刪除后添加文檔,即使你打開了一個近實時(NRT)reader或者使用另一個線程做commit,絕不會出現(xiàn)只有刪除而沒有添加的情況。與此類似,如果使用addDocuments方法添加一組文檔,對于任何reader而言,要么所有的文檔可見,要么所有文檔不可見。
(對原文未提到的一點補充:通過Indexwriter.getReader獲得的Reader是能看到上次commit之后,IndexWriter執(zhí)行至當(dāng)前的所有變化的,在解的部份中將對其進行詳細說明。) - Consistency 一致性
如果計算機或者OS奔潰,或者jvm掛掉或被殺死,亦或是電源被拔掉了,你的索引都會保持完好。注意,像RAM故障,cpu位翻轉(zhuǎn)或者文件系統(tǒng)損壞之類的問題,還是容易造成索引破壞的。 - Isolation 隔離性
當(dāng)IndexWriter正在做更改的時候,所有更改都不會對當(dāng)前搜索該索引的IndexReader可見,直到你commit或者打開了一個新的NRT reader。一次只能有一個IndexWriter實例對索引進行更改。 - Durablity 持久性
一旦commit操作返回,所有變更都會被寫入到持久化存儲。如果計算機或者OS奔潰,或者jvm掛掉或被殺死,亦或是電源被拔掉了,所有的變更都已在索引中保存。
Lucene提供了二階段提交API: 調(diào)用prepareCommit方法,完成主要的提交工作(應(yīng)用緩存的delete操作,寫入被緩存中的文檔,fsync文件)。如果發(fā)生故障(例如,硬盤占滿),基本可以肯定它會發(fā)生在第一階段(prepareCommit階段)。然后,調(diào)用commit方法完成事務(wù)。
當(dāng)你調(diào)用IndexWriter的close方法時,會自動調(diào)用commit方法。如果你想要丟棄自上次commit以來的所有修改,可以調(diào)用rollback方法。你甚至可以對一次CREATE進行rollback: 如果你有已經(jīng)有了一個index,你使用OpenMode.CREATE選項打開一個IndexWriter,然后再調(diào)用rollback,index會保持不變。同樣,你也可以調(diào)用deleteAll方法,然后rollback回來。
注意,僅在一個新的目錄上打開一個IndexWriter并不會產(chǎn)生一個空的commit操作,即你無法在這個目錄上打開一個IndexReader,直到你完成一次commit。
Lucene自身并未實現(xiàn)事務(wù)日志,但在更高層上可以方便實地現(xiàn)。例發(fā),Solr與ElasticSearch就實現(xiàn)了事務(wù)日志。
同一索引的多Commit
單個lucene索引可以保存多個commit,這是一個強大的特性,但往往容易被人們所忽視。每次commit都持有該commit被創(chuàng)建的時間點的過引索視圖。
這個特點類似于ZFS和新興的Btrfs這樣的現(xiàn)代文件系統(tǒng)的快照和寫克隆功能。事實上,Lucene是能夠暴露多次commit狀態(tài),其內(nèi)在的原理是:所有的索引分片和文件只會被寫一次,正如ZFS和Btrfs上的塊。
為了保存多次commit到你的索引中,只需要實現(xiàn)你自己的IndexDeletionPolicy,并將其傳遞給IndexWriter。Lucene正是通過這個類才知站定哪些commit是要被刪除的:IndexWriter會在打開索引和任何一次完成commit的時候調(diào)用它。KeepOnlyLastCommitDeletionPolicy是默認(rèn)的刪除機制實現(xiàn),它會刪除掉除了最近一次commit以外的所有commit。如果采用NoDeletionPolicy,那么每一次commit都會保存。
你可以在commit的時候傳送userData (Map<String,String>),用于記錄關(guān)于本次commit的一些用戶自定義信息(對Lucene不透明),然后使用IndexReader.listCommits方法獲得索引的所有commit信息。一旦你找到了一次commit,你可以在其上找開一個IndexReader對commit點上的索引數(shù)據(jù)進行搜索。
你也可以基于之前的commit打開一個IndexWriter,回滾之后的所有變動。這有點類似于rollback方法,不同之處在于它允許你在多個commit間rollback,而不僅是回滾當(dāng)前IndexWriter會話中所做的變動。
當(dāng)你使用OpenMode.CREATE參數(shù)打開一個索引的時候,老的commit仍會保存。你也可使用OpenMode.CREATE,同時還在老的commit上用IndexReader進行搜索。這使得一些有趣的應(yīng)用情影成為可能,例如,你可以在不影響當(dāng)前任何打開的reader前提下,在各個commit間做過索引全量重建工作。
組合上這些有意思的事務(wù)特征,你可以完成一些很酷的工作:
-
利用SnapShotDeletionPolicy或者PersistentSnapshotDeletionPolicy實現(xiàn)熱備:
these deletion policies make it trivial to take a "live" backup of the index without blocking ongoing changes with IndexWriter. 備份可以輕易的實現(xiàn)增量(只要復(fù)制新的文件,并去掉刪除文件),而你可以減少IO消耗以減輕對于搜索的干擾。 -
搜索不同版本的目錄
也許你在運行一個商業(yè)網(wǎng)站,卻要輾轉(zhuǎn)于不同版本的目錄之間。在這種情況下,你就可以保存更老的commit,允許用戶選擇搜索哪個版本的目錄。 -
在同一個初始索引上進行多次索引實驗:也許你想要在同一個大的索引上,運行一系列的性能測試實驗,例如嘗試不的RAM緩存大小或者merge因子。要想如此,你可以在運行完每次測試之后,不要關(guān)閉IndexWriter,而使用回滾方法快迅地恢得到初始狀態(tài),以備下次測試。
-
強制merge所有的片段到單一片段中,但依然保存之前的多片段時候的commit。如此,你就可以做多個段vs單一段的性能比較實驗。
-
在NFS文件系統(tǒng)上做索引與搜索:
由于NFS無法保證當(dāng)前打開的文件不被刪除,你必須使用IndexDeletionPolicy來保存每次提交,直至所有的reader都不再使用該commit(即,重新在一個更新的commit上打開)。一種簡單的解決方案是基本于時間的,例如:不刪除該commit,直到創(chuàng)建該commit15分鐘之后,然后每5分鐘重新打開reader一次。不然你在NFS上進行搜索時,將會遇到各種可怕的異常。 -
分布式commit:
如果你有其他的資源,需要有l(wèi)ucene索引發(fā)生變化的同時提交,你就可以使用二階段提交API。這樣的做法很簡單,但容易在第二階段發(fā)生失敗; 例如lucene在其第二階段完成了提交,而數(shù)據(jù)庫在第二階段中發(fā)生了一些錯誤、奔潰或者是斷電,這時,你就可以通過在一個較早的commit上重新打開一個IndexWriter來rollback Lucene的這次commit。 -
進行實驗性的索引更新:也許你只想對索引的一個子集做一次re-indexing操作,但你又不確定這樣的操作是否會成功。這種情況下,只要保存老的commit,如果操作失敗,就執(zhí)行rollback,如果成功則刪除老的commit。
-
基于時間的快照:也許你想能夠更自由地回滾到1天前,一周前,一個月之前...的索引狀態(tài),就保可以根據(jù)這些時間點,保存這些commit。
注意:保存多個版本的commit必然會帶來更多的磁盤空間消耗。然而,這些消耗往往會比較小,因為多個commit往往會共享一些索引片段,尤其是那些更大更早的片段。
二、 解
對于Lucene事務(wù)的原子性、隔離性以及近時實(NRT)搜索
上文在原子性的描述中提到“要么你的所有的操作修改都是可見的(commit成功),要么所有的操作修改都不可見(commit失敗),絕不會處于某種中間狀態(tài)。” 但沒有提到,通過Indexwriter.getReader獲得的Reader是能看到上次commit之后IndexWriter執(zhí)行到當(dāng)前的所有變化的。
When you ask for the IndexReader from the IndexWriter, the IndexWriter will be flushed (docs accumulated in RAM will be written to disk) but not committed (fsync files, write new segments file, etc).The returned IndexReader will search over previously committed segments, as well as the new, flushed but not committed segment. Because flushing will likely be processor rather than IO bound, this should be a process that can be attacked with more processor power if found to be too slow.
還有人們常使用openIfChanged方法來實現(xiàn)近實時搜索。我們來看看Lucene DirectoryReader的標(biāo)準(zhǔn)實現(xiàn)StandardDirectoryReader中的oldOpenIfChanged方法的實現(xiàn)。可以發(fā)現(xiàn)所謂的doOpenIfChanged內(nèi)部也是靠優(yōu)先嘗試從IndexWriter獲得DirectoryReader來實現(xiàn)的。
@Overrideprotected DirectoryReader doOpenIfChanged() throws IOException {return doOpenIfChanged((IndexCommit) null);}@Overrideprotected DirectoryReader doOpenIfChanged(final IndexCommit commit) throws IOException {ensureOpen();// If we were obtained by writer.getReader(), re-ask the// writer to get a new reader.if (writer != null) {return doOpenFromWriter(commit);} else {return doOpenNoWriter(commit);}}現(xiàn)在拋開近實時搜索,不使用該新特性,那么上文中作者提到的原子性特征是完備的。在不考慮merge的情況下,Lucene的每一次commit將內(nèi)存中積累的索引變更寫入到硬盤,形成新的索引分段,包括文檔刪除操作。這樣的好處在于,每一次commit不用去復(fù)雜又耗時地修改之前的索引分段,只要累加新文件即可。但這樣的后果是越來越多的分片存在,影響查詢效率,所以才需要merge機制的存在,不斷地去合并分段,這是另話了。
上文中所提的隔離性就是基于這種機制實現(xiàn)的,非NRT的正常reader打開時,只能獲得當(dāng)前已commit到磁盤的分段文件信息的索引數(shù)據(jù),無法得到兩次commit的中間狀態(tài)。 而NRT的reader,可以獲得打開時IndexWriter所應(yīng)用的所有變更,但也無法感知到之后的IndexWriter所做的索引變化,除非重新打開。
至于“一次只能有一個IndexWriter實例對索引進行更改”,這是靠Lucene實現(xiàn)的排它鎖實現(xiàn)的,你將會在你的索引目錄下看到write.lock文件的存在。
Lucene commit
Lucene多個commit的保存與刪除到底是怎么回事?如何基于commit打開索引? 這一系列的問題,還是得需要通過了解lucene如何保存每次commit才能理解。
我們首先看看一個IndexCommit的子類一般都包含哪些信息。一個IndexCommit實例就代表了一次索引commit。對索引內(nèi)容的任何變更只有在segments_N文件完成寫入之后才可見。
public abstract class IndexCommit implements Comparable<IndexCommit> {/*** Get the segments file (<code>segments_N</code>) associated * with this commit point.*/public abstract String getSegmentsFileName();/*** Returns all index files referenced by this commit point.*/public abstract Collection<String> getFileNames() throws IOException;/*** Returns the {@link Directory} for the index.*/public abstract Directory getDirectory();/*** Delete this commit point. This only applies when using* the commit point in the context of IndexWriter's* IndexDeletionPolicy.*/public abstract void delete();... }兩個重要的方法getSegmentsFileName與getFileNames。segmentsFileName是與當(dāng)前commit關(guān)聯(lián)的segments_N文件名。N是一個遞增的值,每次commit對應(yīng)的N都會增大。所以如果保存多個commit的話,自然會出現(xiàn)多個segments_N文件。segments_N具體格式內(nèi)容在此不作具體解釋,總體它記錄了lucene索引的所有分段的元數(shù)據(jù)信息,根據(jù)segments_N文件,可以清楚地知道當(dāng)前commit的索引內(nèi)容分布于哪些分段之中。getFileNames返回的是當(dāng)前commit所引用的所有文件。
如此每次某個commit上打開的IndexReader或IndexWriter就知道應(yīng)該去加載哪些文件。也正因如此,多個commit能夠共享一些老的索引分段,而不至于每個commit占用太大的存儲空間。當(dāng)涉及到commit刪除時,由于lucene對索引文件的刪除是通過引用計數(shù)的方式實現(xiàn)的,只要對commit引用的文件調(diào)用一次IndexFileDeleter.decRef(Collection?files)方法即可。只有引用計數(shù)為0的文件才會真正地被刪除。
Luncene IndexDeletionPolicy
IndexDeletionPolicy它所能做的是在兩個方法時間結(jié)點上對IndexCommit做刪除。定義如下:
public abstract class IndexDeletionPolicy {protected IndexDeletionPolicy() {}public abstract void onInit(List<? extends IndexCommit> commits) throws IOException;public abstract void onCommit(List<? extends IndexCommit> commits) throws IOException; }onInit方法只在IndexWriter初始化時被調(diào)用,onCommit在每次commit操作的時候被調(diào)用。commits列表中包含了當(dāng)前所有的commit點,按從老到新的順序排列。
默認(rèn)實現(xiàn)KeepOnlyLastCommitDeletionPolicy刪除上一次commit以外的所有commit相關(guān)文件(減少引用計數(shù))。
SnapshotDeletionPolicy是采用wrapper模式,對現(xiàn)有IndexDeletionPolicy的一層封裝。除了onInit和onCommit方法外,它還提供了snapshot和release兩個方法。snapshot得到的IndexCommit將不會被刪除,直到其被release,所以比較適用于備份的場影,在備份之前調(diào)用snapshot,直到備份完成,再調(diào)用release。
SnapshotDeletionPolicy只在內(nèi)存中保存snapShot信息,如果要保證數(shù)據(jù)持久化不丟失,可使用PersistentSnapshotDeletionPolicy。
Lucene二階段提交的實現(xiàn)
IndexWirter繼承了TwoPhaseCommit接口,實現(xiàn)三個方法:prepareCommit,commit與rollback。
- prepareCommit,完成二階段提交第一階段的工作,它會盡可能多的完成更新工作,但又避免完成真實的提交。你可以輕松地利用rollback廢棄掉當(dāng)前階段完成的所有工作。 事實上本次commit所產(chǎn)生的段文件,已寫入存儲。
- commit方法是完成第二階段的工作,它只作很少的工作,只有該方滿返回,調(diào)用者才能確認(rèn)索引相應(yīng)操作已完成,并持久化到存儲。跟蹤代碼直至SegmentIfnos的finishCommit方法,可見commit成功的情況下,只做了兩件事情,一是在segments_N未填入4byte的校驗合,還有就是close寫入流完成fsync。新的commit,只有在校驗和正確的情況下對IndexReader可見。
- rollback:廢棄掉上次commit以來的所有變更操作。
20151005首發(fā)于3dobe.com
鏈接地址:http://3dobe.com/archives/172/
本文分兩部份,第一部份為譯:是對是對于lucene事務(wù)的一篇佳作《Transactional Lucene》的翻譯。第二部份為解:是本人對一文中提到一些概念在源碼層次的一些理解分析,參考lucene源碼版本為4.10.4。《Transactional Lucene》中還提到了多commit在實際生產(chǎn)中的一些妙用,值得參考。
一、譯
很多用戶并不了解Lucene API的事務(wù)語義及其在搜索應(yīng)用中的用途。對于初學(xué)者應(yīng)當(dāng)了解的Lucene ACID特性如下:
- Atomiciy 原子性
當(dāng)你在一次IndexWriter session中做操作(增加,刪除文檔),然后commit,要么你的所有的操作修改都是可見的(commit成功),要么所有的操作修改都不可見(commit失敗),絕不會處于某種中間狀態(tài)。有些方法有它自身的原子操作:如果你調(diào)用updateDocument方法,其內(nèi)在實現(xiàn)是先刪除后添加文檔,即使你打開了一個近實時(NRT)reader或者使用另一個線程做commit,絕不會出現(xiàn)只有刪除而沒有添加的情況。與此類似,如果使用addDocuments方法添加一組文檔,對于任何reader而言,要么所有的文檔可見,要么所有文檔不可見。
(對原文未提到的一點補充:通過Indexwriter.getReader獲得的Reader是能看到上次commit之后,IndexWriter執(zhí)行至當(dāng)前的所有變化的,在解的部份中將對其進行詳細說明。) - Consistency 一致性
如果計算機或者OS奔潰,或者jvm掛掉或被殺死,亦或是電源被拔掉了,你的索引都會保持完好。注意,像RAM故障,cpu位翻轉(zhuǎn)或者文件系統(tǒng)損壞之類的問題,還是容易造成索引破壞的。 - Isolation 隔離性
當(dāng)IndexWriter正在做更改的時候,所有更改都不會對當(dāng)前搜索該索引的IndexReader可見,直到你commit或者打開了一個新的NRT reader。一次只能有一個IndexWriter實例對索引進行更改。 - Durablity 持久性
一旦commit操作返回,所有變更都會被寫入到持久化存儲。如果計算機或者OS奔潰,或者jvm掛掉或被殺死,亦或是電源被拔掉了,所有的變更都已在索引中保存。
Lucene提供了二階段提交API: 調(diào)用prepareCommit方法,完成主要的提交工作(應(yīng)用緩存的delete操作,寫入被緩存中的文檔,fsync文件)。如果發(fā)生故障(例如,硬盤占滿),基本可以肯定它會發(fā)生在第一階段(prepareCommit階段)。然后,調(diào)用commit方法完成事務(wù)。
當(dāng)你調(diào)用IndexWriter的close方法時,會自動調(diào)用commit方法。如果你想要丟棄自上次commit以來的所有修改,可以調(diào)用rollback方法。你甚至可以對一次CREATE進行rollback: 如果你有已經(jīng)有了一個index,你使用OpenMode.CREATE選項打開一個IndexWriter,然后再調(diào)用rollback,index會保持不變。同樣,你也可以調(diào)用deleteAll方法,然后rollback回來。
注意,僅在一個新的目錄上打開一個IndexWriter并不會產(chǎn)生一個空的commit操作,即你無法在這個目錄上打開一個IndexReader,直到你完成一次commit。
Lucene自身并未實現(xiàn)事務(wù)日志,但在更高層上可以方便實地現(xiàn)。例發(fā),Solr與ElasticSearch就實現(xiàn)了事務(wù)日志。
同一索引的多Commit
單個lucene索引可以保存多個commit,這是一個強大的特性,但往往容易被人們所忽視。每次commit都持有該commit被創(chuàng)建的時間點的過引索視圖。
這個特點類似于ZFS和新興的Btrfs這樣的現(xiàn)代文件系統(tǒng)的快照和寫克隆功能。事實上,Lucene是能夠暴露多次commit狀態(tài),其內(nèi)在的原理是:所有的索引分片和文件只會被寫一次,正如ZFS和Btrfs上的塊。
為了保存多次commit到你的索引中,只需要實現(xiàn)你自己的IndexDeletionPolicy,并將其傳遞給IndexWriter。Lucene正是通過這個類才知站定哪些commit是要被刪除的:IndexWriter會在打開索引和任何一次完成commit的時候調(diào)用它。KeepOnlyLastCommitDeletionPolicy是默認(rèn)的刪除機制實現(xiàn),它會刪除掉除了最近一次commit以外的所有commit。如果采用NoDeletionPolicy,那么每一次commit都會保存。
你可以在commit的時候傳送userData (Map<String,String>),用于記錄關(guān)于本次commit的一些用戶自定義信息(對Lucene不透明),然后使用IndexReader.listCommits方法獲得索引的所有commit信息。一旦你找到了一次commit,你可以在其上找開一個IndexReader對commit點上的索引數(shù)據(jù)進行搜索。
你也可以基于之前的commit打開一個IndexWriter,回滾之后的所有變動。這有點類似于rollback方法,不同之處在于它允許你在多個commit間rollback,而不僅是回滾當(dāng)前IndexWriter會話中所做的變動。
當(dāng)你使用OpenMode.CREATE參數(shù)打開一個索引的時候,老的commit仍會保存。你也可使用OpenMode.CREATE,同時還在老的commit上用IndexReader進行搜索。這使得一些有趣的應(yīng)用情影成為可能,例如,你可以在不影響當(dāng)前任何打開的reader前提下,在各個commit間做過索引全量重建工作。
組合上這些有意思的事務(wù)特征,你可以完成一些很酷的工作:
-
利用SnapShotDeletionPolicy或者PersistentSnapshotDeletionPolicy實現(xiàn)熱備:
these deletion policies make it trivial to take a "live" backup of the index without blocking ongoing changes with IndexWriter. 備份可以輕易的實現(xiàn)增量(只要復(fù)制新的文件,并去掉刪除文件),而你可以減少IO消耗以減輕對于搜索的干擾。 -
搜索不同版本的目錄
也許你在運行一個商業(yè)網(wǎng)站,卻要輾轉(zhuǎn)于不同版本的目錄之間。在這種情況下,你就可以保存更老的commit,允許用戶選擇搜索哪個版本的目錄。 -
在同一個初始索引上進行多次索引實驗:也許你想要在同一個大的索引上,運行一系列的性能測試實驗,例如嘗試不的RAM緩存大小或者merge因子。要想如此,你可以在運行完每次測試之后,不要關(guān)閉IndexWriter,而使用回滾方法快迅地恢得到初始狀態(tài),以備下次測試。
-
強制merge所有的片段到單一片段中,但依然保存之前的多片段時候的commit。如此,你就可以做多個段vs單一段的性能比較實驗。
-
在NFS文件系統(tǒng)上做索引與搜索:
由于NFS無法保證當(dāng)前打開的文件不被刪除,你必須使用IndexDeletionPolicy來保存每次提交,直至所有的reader都不再使用該commit(即,重新在一個更新的commit上打開)。一種簡單的解決方案是基本于時間的,例如:不刪除該commit,直到創(chuàng)建該commit15分鐘之后,然后每5分鐘重新打開reader一次。不然你在NFS上進行搜索時,將會遇到各種可怕的異常。 -
分布式commit:
如果你有其他的資源,需要有l(wèi)ucene索引發(fā)生變化的同時提交,你就可以使用二階段提交API。這樣的做法很簡單,但容易在第二階段發(fā)生失敗; 例如lucene在其第二階段完成了提交,而數(shù)據(jù)庫在第二階段中發(fā)生了一些錯誤、奔潰或者是斷電,這時,你就可以通過在一個較早的commit上重新打開一個IndexWriter來rollback Lucene的這次commit。 -
進行實驗性的索引更新:也許你只想對索引的一個子集做一次re-indexing操作,但你又不確定這樣的操作是否會成功。這種情況下,只要保存老的commit,如果操作失敗,就執(zhí)行rollback,如果成功則刪除老的commit。
-
基于時間的快照:也許你想能夠更自由地回滾到1天前,一周前,一個月之前...的索引狀態(tài),就保可以根據(jù)這些時間點,保存這些commit。
注意:保存多個版本的commit必然會帶來更多的磁盤空間消耗。然而,這些消耗往往會比較小,因為多個commit往往會共享一些索引片段,尤其是那些更大更早的片段。
二、 解
對于Lucene事務(wù)的原子性、隔離性以及近時實(NRT)搜索
上文在原子性的描述中提到“要么你的所有的操作修改都是可見的(commit成功),要么所有的操作修改都不可見(commit失敗),絕不會處于某種中間狀態(tài)。” 但沒有提到,通過Indexwriter.getReader獲得的Reader是能看到上次commit之后IndexWriter執(zhí)行到當(dāng)前的所有變化的。
When you ask for the IndexReader from the IndexWriter, the IndexWriter will be flushed (docs accumulated in RAM will be written to disk) but not committed (fsync files, write new segments file, etc).The returned IndexReader will search over previously committed segments, as well as the new, flushed but not committed segment. Because flushing will likely be processor rather than IO bound, this should be a process that can be attacked with more processor power if found to be too slow.
還有人們常使用openIfChanged方法來實現(xiàn)近實時搜索。我們來看看Lucene DirectoryReader的標(biāo)準(zhǔn)實現(xiàn)StandardDirectoryReader中的oldOpenIfChanged方法的實現(xiàn)。可以發(fā)現(xiàn)所謂的doOpenIfChanged內(nèi)部也是靠優(yōu)先嘗試從IndexWriter獲得DirectoryReader來實現(xiàn)的。
@Overrideprotected DirectoryReader doOpenIfChanged() throws IOException {return doOpenIfChanged((IndexCommit) null);}@Overrideprotected DirectoryReader doOpenIfChanged(final IndexCommit commit) throws IOException {ensureOpen();// If we were obtained by writer.getReader(), re-ask the// writer to get a new reader.if (writer != null) {return doOpenFromWriter(commit);} else {return doOpenNoWriter(commit);}}現(xiàn)在拋開近實時搜索,不使用該新特性,那么上文中作者提到的原子性特征是完備的。在不考慮merge的情況下,Lucene的每一次commit將內(nèi)存中積累的索引變更寫入到硬盤,形成新的索引分段,包括文檔刪除操作。這樣的好處在于,每一次commit不用去復(fù)雜又耗時地修改之前的索引分段,只要累加新文件即可。但這樣的后果是越來越多的分片存在,影響查詢效率,所以才需要merge機制的存在,不斷地去合并分段,這是另話了。
上文中所提的隔離性就是基于這種機制實現(xiàn)的,非NRT的正常reader打開時,只能獲得當(dāng)前已commit到磁盤的分段文件信息的索引數(shù)據(jù),無法得到兩次commit的中間狀態(tài)。 而NRT的reader,可以獲得打開時IndexWriter所應(yīng)用的所有變更,但也無法感知到之后的IndexWriter所做的索引變化,除非重新打開。
至于“一次只能有一個IndexWriter實例對索引進行更改”,這是靠Lucene實現(xiàn)的排它鎖實現(xiàn)的,你將會在你的索引目錄下看到write.lock文件的存在。
Lucene commit
Lucene多個commit的保存與刪除到底是怎么回事?如何基于commit打開索引? 這一系列的問題,還是得需要通過了解lucene如何保存每次commit才能理解。
我們首先看看一個IndexCommit的子類一般都包含哪些信息。一個IndexCommit實例就代表了一次索引commit。對索引內(nèi)容的任何變更只有在segments_N文件完成寫入之后才可見。
public abstract class IndexCommit implements Comparable<IndexCommit> {/*** Get the segments file (<code>segments_N</code>) associated * with this commit point.*/public abstract String getSegmentsFileName();/*** Returns all index files referenced by this commit point.*/public abstract Collection<String> getFileNames() throws IOException;/*** Returns the {@link Directory} for the index.*/public abstract Directory getDirectory();/*** Delete this commit point. This only applies when using* the commit point in the context of IndexWriter's* IndexDeletionPolicy.*/public abstract void delete();... }兩個重要的方法getSegmentsFileName與getFileNames。segmentsFileName是與當(dāng)前commit關(guān)聯(lián)的segments_N文件名。N是一個遞增的值,每次commit對應(yīng)的N都會增大。所以如果保存多個commit的話,自然會出現(xiàn)多個segments_N文件。segments_N具體格式內(nèi)容在此不作具體解釋,總體它記錄了lucene索引的所有分段的元數(shù)據(jù)信息,根據(jù)segments_N文件,可以清楚地知道當(dāng)前commit的索引內(nèi)容分布于哪些分段之中。getFileNames返回的是當(dāng)前commit所引用的所有文件。
如此每次某個commit上打開的IndexReader或IndexWriter就知道應(yīng)該去加載哪些文件。也正因如此,多個commit能夠共享一些老的索引分段,而不至于每個commit占用太大的存儲空間。當(dāng)涉及到commit刪除時,由于lucene對索引文件的刪除是通過引用計數(shù)的方式實現(xiàn)的,只要對commit引用的文件調(diào)用一次IndexFileDeleter.decRef(Collection?files)方法即可。只有引用計數(shù)為0的文件才會真正地被刪除。
Luncene IndexDeletionPolicy
IndexDeletionPolicy它所能做的是在兩個方法時間結(jié)點上對IndexCommit做刪除。定義如下:
public abstract class IndexDeletionPolicy {protected IndexDeletionPolicy() {}public abstract void onInit(List<? extends IndexCommit> commits) throws IOException;public abstract void onCommit(List<? extends IndexCommit> commits) throws IOException; }onInit方法只在IndexWriter初始化時被調(diào)用,onCommit在每次commit操作的時候被調(diào)用。commits列表中包含了當(dāng)前所有的commit點,按從老到新的順序排列。
默認(rèn)實現(xiàn)KeepOnlyLastCommitDeletionPolicy刪除上一次commit以外的所有commit相關(guān)文件(減少引用計數(shù))。
SnapshotDeletionPolicy是采用wrapper模式,對現(xiàn)有IndexDeletionPolicy的一層封裝。除了onInit和onCommit方法外,它還提供了snapshot和release兩個方法。snapshot得到的IndexCommit將不會被刪除,直到其被release,所以比較適用于備份的場影,在備份之前調(diào)用snapshot,直到備份完成,再調(diào)用release。
SnapshotDeletionPolicy只在內(nèi)存中保存snapShot信息,如果要保證數(shù)據(jù)持久化不丟失,可使用PersistentSnapshotDeletionPolicy。
Lucene二階段提交的實現(xiàn)
IndexWirter繼承了TwoPhaseCommit接口,實現(xiàn)三個方法:prepareCommit,commit與rollback。
- prepareCommit,完成二階段提交第一階段的工作,它會盡可能多的完成更新工作,但又避免完成真實的提交。你可以輕松地利用rollback廢棄掉當(dāng)前階段完成的所有工作。 事實上本次commit所產(chǎn)生的段文件,已寫入存儲。
- commit方法是完成第二階段的工作,它只作很少的工作,只有該方滿返回,調(diào)用者才能確認(rèn)索引相應(yīng)操作已完成,并持久化到存儲。跟蹤代碼直至SegmentIfnos的finishCommit方法,可見commit成功的情況下,只做了兩件事情,一是在segments_N未填入4byte的校驗合,還有就是close寫入流完成fsync。新的commit,只有在校驗和正確的情況下對IndexReader可見。
- rollback:廢棄掉上次commit以來的所有變更操作。
總結(jié)
以上是生活随笔為你收集整理的【搜索引擎】lucene事务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【链接保存】十分钟上手sklearn:安
- 下一篇: 【转载保存】修改IK分词器源码实现动态加