1、Lucene刪除文檔的幾種方式
?
- IndexReader.deleteDocument(int docID)是用 IndexReader 按文檔號(hào)刪除。??
- IndexReader.deleteDocuments(Term? term)是用 IndexReader 刪除包含此詞(Term)的文檔。??
- IndexWriter.deleteDocuments(Term? term)是用 IndexWriter 刪除包含此詞(Term)的文檔。??
- IndexWriter.deleteDocuments(Term[]? terms)是用 IndexWriter 刪除包含這些詞(Term)的文檔。??
- IndexWriter.deleteDocuments(Query? query)是用 IndexWriter 刪除能滿足此查詢(Query)的文檔。??
- IndexWriter.deleteDocuments(Query[] queries)是用 IndexWriter 刪除能滿足這些查詢(Query)的文檔。
刪除文檔既可以用reader進(jìn)行刪除,也可以用writer進(jìn)行刪除,不同的是,reader進(jìn)行刪除后,此reader馬上能夠生效,而用writer刪除后,會(huì)被緩存,只有寫(xiě)入到索引文件中,當(dāng)reader再次打開(kāi)的時(shí)候,才能夠看到。
2、Lucene文檔更新的幾個(gè)問(wèn)題
?
2.1、使用IndexReader還是IndexWriter進(jìn)行刪除
既然IndexReader和IndexWriter都能夠進(jìn)行文檔刪除,那么到底是應(yīng)該用哪個(gè)來(lái)進(jìn)行刪除呢?
本文的建議是,用IndexWriter來(lái)進(jìn)行刪除。
因?yàn)橛肐ndexReader可能存在以下的問(wèn)題:
(1) 當(dāng)有一個(gè)IndexWriter打開(kāi)的時(shí)候,IndexReader的刪除操作是不能夠進(jìn)行的,否則會(huì)報(bào)LockObtainFailedException
(2) 當(dāng)IndexReader被多個(gè)線程使用的時(shí)候,一個(gè)線程用其進(jìn)行刪除,會(huì)使得另一個(gè)線程看到的索引有所改變,使得另一個(gè)線程的結(jié)果帶有不確定性。
(3) 對(duì)于更新操作,在Lucene中是先刪除,再添加的,然而刪除的被立刻看到的,而添加卻不能夠立刻看到,造成了數(shù)據(jù)的不一致性。
(4) 即便以上問(wèn)題可以通過(guò)鎖來(lái)解決,然而背后的操作影響到了搜索的速度,是我們不想看到的。
2.2、如何在內(nèi)存中緩存文檔的刪除
在上一節(jié)中,為了能夠做到實(shí)時(shí)性,我們使用內(nèi)存中的索引,而硬盤(pán)上的索引則不經(jīng)常打開(kāi),即便打開(kāi)也在背后線程中打開(kāi)。
而要?jiǎng)h除的文檔如果在硬盤(pán)索引中,如果不重新打開(kāi)則看不到新的刪除,則需要將刪除的文檔緩存到內(nèi)存中。
那如何將緩存在內(nèi)存中的文檔刪除在不重新打開(kāi)IndexReader的情況下應(yīng)用于硬盤(pán)上的索引呢?
在Lucene中,有一種IndexReader為FilterIndexReader,可以對(duì)一個(gè)IndexReader進(jìn)行封裝,我們可以實(shí)現(xiàn)一個(gè)自己的FilterIndexReader來(lái)過(guò)濾掉刪除的文檔。
一個(gè)例子如下:
? public class MyFilterIndexReader extends FilterIndexReader { ? OpenBitSet dels; ? public MyFilterIndexReader(IndexReader in) { ??? super(in); ??? dels = new OpenBitSet(in.maxDoc()); ? } ? public MyFilterIndexReader(IndexReader in, List<String> idToDelete) throws IOException { ??? super(in); ??? dels = new OpenBitSet(in.maxDoc()); ??? for(String id : idToDelete){ ????? TermDocs td = in.termDocs(new Term("id", id));? //如果能在內(nèi)存中Cache從Lucene的ID到應(yīng)用的ID的映射,Reader的生成將快得多。 ????? if(td.next()){ ??????? dels.set(td.doc()); ????? } ??? } ? } ? @Override ? public int numDocs() { ??? return in.numDocs() - (int) dels.cardinality(); ? } ? @Override ? public TermDocs termDocs(Term term) throws IOException { ??? return new FilterTermDocs(in.termDocs(term)) { ????? @Override ????? public boolean next() throws IOException { ??????? boolean res; ??????? while ((res = super.next())) { ????????? if (!dels.get(doc())) { ??????????? break; ????????? } ??????? } ??????? return res; ????? } ??? }; ? } ? @Override ? public TermDocs termDocs() throws IOException { ??? return new FilterTermDocs(in.termDocs()) { ????? @Override ????? public boolean next() throws IOException { ??????? boolean res; ??????? while ((res = super.next())) { ????????? if (!dels.get(doc())) { ??????????? break; ????????? } ??????? } ??????? return res; ????? } ??? }; ? } } |
?
2.3、文檔更新的順序性問(wèn)題
Lucene的文檔更新其實(shí)是刪除舊的文檔,然后添加新的文檔。如上所述,刪除的文檔是緩存在內(nèi)存中的,并通過(guò)FilterIndexReader應(yīng)用于硬盤(pán)上的索引,然而新的文檔也是以相同的id加入到索引中去的,這就需要保證緩存的刪除不會(huì)將新的文檔也過(guò)濾掉,將緩存的刪除合并到索引中的時(shí)候不會(huì)將新的文檔也刪除掉。
Lucene的兩次更新一定要后一次覆蓋前一次,而不能讓前一次覆蓋后一次。
所以內(nèi)存中已經(jīng)硬盤(pán)中的多個(gè)索引是要被保持一個(gè)順序的,哪個(gè)是老的索引,哪個(gè)是新的索引,緩存的刪除自然是應(yīng)該應(yīng)用于所有比他老的索引的,而不應(yīng)該應(yīng)用于他自己以及比他新的索引。
3、具有更新功能的Lucene實(shí)時(shí)索引方案
3.1、初始化
首先假設(shè)我們硬盤(pán)上已經(jīng)有一個(gè)索引FileSystemIndex,被事先打開(kāi)的,其中包含文檔1,2,3,4,5,6。
我們?cè)趦?nèi)存中有一個(gè)索引MemoryIndex,新來(lái)的文檔全部索引到內(nèi)存索引中,并且是索引完IndexWriter就commit,IndexReader就重新打開(kāi),其中包含文檔7,8。
?
3.2、更新文檔5
這時(shí)候來(lái)一個(gè)新的更新文檔5, 需要首先將文檔5刪除,然后加入新的文檔5。
需要做的事情是:
- 首先在內(nèi)存索引中刪除文檔5,當(dāng)然沒(méi)有文檔5,刪除無(wú)效。
- 其次將對(duì)文檔5的刪除放入內(nèi)存文檔刪除列表,并與硬盤(pán)的IndexReader組成FilterIndexReader
- 最后,將新的文檔5加入內(nèi)存索引,這時(shí)候,用戶可以看到的就是新的文檔5了。
- 將文檔5放入刪除列表以及將文檔5提交到內(nèi)存索引兩者應(yīng)該是一個(gè)原子操作,好在這兩者都是比較塊的。
注:此處對(duì)硬盤(pán)上的索引,也可以進(jìn)行對(duì)文檔5的刪除,由于IndexReader沒(méi)有重新打開(kāi),此刪除是刪不掉的,我們之所以沒(méi)有這樣做,是想保持此次更新要么全部在內(nèi)存中,要么全部在硬盤(pán)中,而非刪除部分已經(jīng)應(yīng)用到硬盤(pán)中,而新文檔卻在內(nèi)存中,此時(shí),如果系統(tǒng)crash,則新的文檔5丟失了,而舊的文檔5也已經(jīng)在硬盤(pán)上被刪除。我們將硬盤(pán)上對(duì)文檔5的刪除放到從內(nèi)存索引向硬盤(pán)索引的合并過(guò)程。
如果再有一次對(duì)文檔5的更新,則首先將內(nèi)存索引中的文檔5刪除,添加新的文檔5,然后將文檔5加入刪除列表,發(fā)現(xiàn)已經(jīng)存在,則不必刪除。
3.3、合并索引
然而經(jīng)過(guò)一段時(shí)間,內(nèi)存中的索引需要合并到硬盤(pán)上。
在合并的過(guò)程中,需要重新建立一個(gè)空的內(nèi)存索引,用于合并階段索引新的文檔,而合并中的索引的IndexReader以及硬盤(pán)索引和刪除列表所組成的FilterIndexReader仍然保持打開(kāi),對(duì)外提供服務(wù),而合并階段從后臺(tái)進(jìn)行。
后臺(tái)的合并包括以下幾步:
- 將刪除列表應(yīng)用到硬盤(pán)索引中。
- 將內(nèi)存索引合并到硬盤(pán)索引中。
- IndexWriter提交。
3.4、合并的過(guò)程中更新文檔5
在合并的過(guò)程中,如果還有更新那怎么辦呢?
- 首先將合并中索引的文檔5刪除,此刪除不會(huì)影響合并,因?yàn)楹喜⒅埃喜⒅兴饕腎ndexReader已經(jīng)打開(kāi),索引合并中索引的文檔5還是會(huì)合并到硬盤(pán)中去的。此刪除影響的是此后的查詢?cè)诤喜⒅兴饕强床坏轿臋n5的。
- 然后將文檔5的刪除放入刪除列表,并同合并中索引的刪除列表,已經(jīng)硬盤(pán)索引一起構(gòu)成FilterIndexReader。
- 將新的文檔5添加到內(nèi)存中索引。
- 提交在合并中索引對(duì)文檔5的刪除,將文檔5添加到刪除列表,提交在內(nèi)存索引中對(duì)文檔5的添加三者應(yīng)該是一個(gè)原子操作,好在三者也是很快的。
3.5、重新打開(kāi)硬盤(pán)索引的IndexReader
當(dāng)合并中索引合并到硬盤(pán)中的時(shí)候,是時(shí)候重新打開(kāi)硬盤(pán)上的索引了,新打開(kāi)的IndexReader是可以看到文檔5的刪除的。
如果這個(gè)時(shí)候有新的更新,也是添加到內(nèi)存索引和刪除列表的,比如我們更新文檔6.
3.6、替代IndexReader ?
當(dāng)IndexReader被重新打開(kāi)后,則需要?jiǎng)h除合并中的索引及其刪除列表,將硬盤(pán)索引原來(lái)的IndexReader關(guān)閉,使用新的IndexReader。
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
