<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    huangfox

    韜光隱晦
    隨筆 - 1, 文章 - 8, 評論 - 1, 引用 - 0
    數據加載中……

    有關Lucene的問題(8):用Lucene構建實時索引的文檔更新問題【轉】

      在有關Lucene的問題(7),討論了使用Lucene內存索引和硬盤索引構建實時索引的問題。

      然而有的讀者提到,如果涉及到文檔的刪除及更新,那么如何構建實時的索引呢?本節來討論這個問題。

      1、Lucene刪除文檔的幾種方式

      IndexReader.deleteDocument(int docID)是用 IndexReader 按文檔號刪除。

      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進行刪除,也可以用writer進行刪除,不同的是,reader進行刪除后,此reader馬上能夠生效,而用writer刪除后,會被緩存,只有寫入到索引文件中,當reader再次打開的時候,才能夠看到。

      2、Lucene文檔更新的幾個問題

      2.1、使用IndexReader還是IndexWriter進行刪除

      既然IndexReader和IndexWriter都能夠進行文檔刪除,那么到底是應該用哪個來進行刪除呢?

      本文的建議是,用IndexWriter來進行刪除。

      因為用IndexReader可能存在以下的問題:

      (1) 當有一個IndexWriter打開的時候,IndexReader的刪除操作是不能夠進行的,否則會報LockObtainFailedException

      (2) 當IndexReader被多個線程使用的時候,一個線程用其進行刪除,會使得另一個線程看到的索引有所改變,使得另一個線程的結果帶有不確定性。

      (3) 對于更新操作,在Lucene中是先刪除,再添加的,然而刪除的被立刻看到的,而添加卻不能夠立刻看到,造成了數據的不一致性。

      (4) 即便以上問題可以通過鎖來解決,然而背后的操作影響到了搜索的速度,是我們不想看到的。

      2.2、如何在內存中緩存文檔的刪除

      在上一節中,為了能夠做到實時性,我們使用內存中的索引,而硬盤上的索引則不經常打開,即便打開也在背后線程中打開。

      而要刪除的文檔如果在硬盤索引中,如果不重新打開則看不到新的刪除,則需要將刪除的文檔緩存到內存中。

      那如何將緩存在內存中的文檔刪除在不重新打開IndexReader的情況下應用于硬盤上的索引呢?

      在Lucene中,有一種IndexReader為FilterIndexReader,可以對一個IndexReader進行封裝,我們可以實現一個自己的FilterIndexReader來過濾掉刪除的文檔。

      一個例子如下:

      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)); //如果能在內存中Cache從Lucene的ID到應用的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、文檔更新的順序性問題

      Lucene 的文檔更新其實是刪除舊的文檔,然后添加新的文檔。如上所述,刪除的文檔是緩存在內存中的,并通過FilterIndexReader應用于硬盤上的索 引,然而新的文檔也是以相同的id加入到索引中去的,這就需要保證緩存的刪除不會將新的文檔也過濾掉,將緩存的刪除合并到索引中的時候不會將新的文檔也刪 除掉。

      Lucene的兩次更新一定要后一次覆蓋前一次,而不能讓前一次覆蓋后一次。

      所以內存中已經硬盤中的多個索引是要被保持一個順序的,哪個是老的索引,哪個是新的索引,緩存的刪除自然是應該應用于所有比他老的索引的,而不應該應用于他自己以及比他新的索引。

      3、具有更新功能的Lucene實時索引方案3.1、初始化

      首先假設我們硬盤上已經有一個索引FileSystemIndex,被事先打開的,其中包含文檔1,2,3,4,5,6。

      我們在內存中有一個索引MemoryIndex,新來的文檔全部索引到內存索引中,并且是索引完IndexWriter就commit,IndexReader就重新打開,其中包含文檔7,8。

      3.2、更新文檔5

      這時候來一個新的更新文檔5, 需要首先將文檔5刪除,然后加入新的文檔5。

      需要做的事情是:

      首先在內存索引中刪除文檔5,當然沒有文檔5,刪除無效。

      其次將對文檔5的刪除放入內存文檔刪除列表,并與硬盤的IndexReader組成FilterIndexReader

      最后,將新的文檔5加入內存索引,這時候,用戶可以看到的就是新的文檔5了。

      將文檔5放入刪除列表以及將文檔5提交到內存索引兩者應該是一個原子操作,好在這兩者都是比較塊的。

      注:此處對硬盤上的索引,也可以進行對文檔5的刪除,由于IndexReader沒有重新打開,此刪除是刪不掉的,我們之所以沒有這樣做,是想保持此次更 新要么全部在內存中,要么全部在硬盤中,而非刪除部分已經應用到硬盤中,而新文檔卻在內存中,此時,如果系統crash,則新的文檔5丟失了,而舊的文檔 5也已經在硬盤上被刪除。我們將硬盤上對文檔5的刪除放到從內存索引向硬盤索引的合并過程。

      如果再有一次對文檔5的更新,則首先將內存索引中的文檔5刪除,添加新的文檔5,然后將文檔5加入刪除列表,發現已經存在,則不必刪除。

      3.3、合并索引

      然而經過一段時間,內存中的索引需要合并到硬盤上。

      在合并的過程中,需要重新建立一個空的內存索引,用于合并階段索引新的文檔,而合并中的索引的IndexReader以及硬盤索引和刪除列表所組成的FilterIndexReader仍然保持打開,對外提供服務,而合并階段從后臺進行。

      后臺的合并包括以下幾步:

      將刪除列表應用到硬盤索引中。

      將內存索引合并到硬盤索引中。

      IndexWriter提交。

      查看原圖(大圖)

      3.4、合并的過程中更新文檔5

      在合并的過程中,如果還有更新那怎么辦呢?

      首先將合并中索引的文檔5刪除,此刪除不會影響合并,因為合并之前,合并中索引的IndexReader已經打開,索引合并中索引的文檔5還是會合并到硬盤中去的。此刪除影響的是此后的查詢在合并中索引是看不到文檔5的。

      然后將文檔5的刪除放入刪除列表,并同合并中索引的刪除列表,已經硬盤索引一起構成FilterIndexReader。

      將新的文檔5添加到內存中索引。

      提交在合并中索引對文檔5的刪除,將文檔5添加到刪除列表,提交在內存索引中對文檔5的添加三者應該是一個原子操作,好在三者也是很快的。

      查看原圖(大圖)

      3.5、重新打開硬盤索引的IndexReader

      當合并中索引合并到硬盤中的時候,是時候重新打開硬盤上的索引了,新打開的IndexReader是可以看到文檔5的刪除的。

      如果這個時候有新的更新,也是添加到內存索引和刪除列表的,比如我們更新文檔6.

      查看原圖(大圖)

      3.6、替代IndexReader

      當IndexReader被重新打開后,則需要刪除合并中的索引及其刪除列表,將硬盤索引原來的IndexReader關閉,使用新的IndexReader。

      查看原圖(大圖)

    posted on 2010-09-25 16:18 fox009 閱讀(350) 評論(0)  編輯  收藏 所屬分類: 搜索引擎技術

    主站蜘蛛池模板: 亚洲日本视频在线观看| 亚洲AV无码日韩AV无码导航| va天堂va亚洲va影视中文字幕| 鲁大师在线影院免费观看| 亚洲日韩小电影在线观看| 免费毛片在线看不用播放器| 亚洲区小说区图片区QVOD| 国产一区二区三区免费观在线| 亚洲日本va在线视频观看| 怡红院免费的全部视频| 亚洲AV永久无码精品一百度影院 | 亚洲二区在线视频| 999国内精品永久免费观看| 亚洲av午夜精品无码专区| 成年女人毛片免费播放视频m | 高潮毛片无遮挡高清免费 | 国产亚洲精品久久久久秋霞| 99在线热播精品免费99热| 亚洲av午夜福利精品一区| 久久国产乱子伦免费精品| 亚洲综合色区中文字幕| 日韩一区二区在线免费观看 | 国产桃色在线成免费视频| 亚洲中文字幕无码中文字| 一区二区三区亚洲视频| 中文字幕在线免费看线人| 亚洲欧洲综合在线| 国产成人免费a在线视频色戒| 人人爽人人爽人人片av免费| 亚洲AV电影院在线观看| 中文字幕人成无码免费视频| 免费无遮挡无遮羞在线看| 亚洲AV无码一区东京热久久| 在线观看人成网站深夜免费| 51午夜精品免费视频| 亚洲色图.com| 亚洲国产成人乱码精品女人久久久不卡 | 99re8这里有精品热视频免费| 久久久亚洲欧洲日产国码aⅴ| 最近中文字幕无免费视频| 岛国精品一区免费视频在线观看 |