2011-11-19 29 views
2

Lucene文檔指出,IndexSearcher和IndexWriter的單個實例應該用於整個應用程序和所有線程中的每個索引。此外,在索引重新打開之前,對索引的寫入操作將不可見。在Lucene中,如何查找IndexSearcher或IndexWriter是否在另一個線程中使用?

所以,我試圖按照這些指南,在一個多線程的設置。 (一些線程編寫,多個用戶線程搜索)。我不想在每次更改時重新打開索引,而是希望讓搜索器實例不超過一定的時間(比如20秒)。

中央組件負責打開索引讀取器和編寫器,並保留單個實例並同步這些線程。我記錄了上次任何用戶線程訪問過IndexSearcher的時間以及它變髒的時間。如果有人需要在20秒後訪問它,我想關閉搜索器並重新打開它。

問題是,我不確定搜索者以前的請求(由其他線程完成)是否已完成,以便我可以關閉IndexSearcher。這意味着如果我關閉並重新打開在所有線程之間共享的單個IndexSearcher實例,則可能會在其他某個線程中同時進行搜索。

更糟糕的是,這裏是理論上可能發生的事情:可以一直在同一時間執行多個搜索。 (假設您有成千上萬的用戶在同一個索引上運行搜索)。單個的IndexSearcher實例可能永遠不會變成免費的,因此它可以被關閉。理想情況下,我想創建另一個IndexSearcher並向它發送新的請求(而舊的請求仍然打開並運行之前請求的搜索)。當在舊實例上運行的搜索完成時,我想關閉它。

什麼是同步IndexSearcher(或IndexWriter)的多個用戶調用close()方法的最佳方法? Lucene是否爲此提供了任何功能/設施,還是應完全由用戶代碼完成(如使用搜索器對線程進行計數,並在每次使用時增加/減少計數)?

有沒有關於上述設計的建議/想法?

回答

9

感謝在最近的版本(3.x或晚期2.x),他們添加了一種方法,告訴你在搜索器打開後是否有任何寫入。 IndexReader.isCurrent()會告訴你自該閱讀器打開或不打開後是否發生了任何更改。所以你可能會創建一個簡單的封裝類來封裝讀取和寫入,並且通過一些簡單的同步,你可以提供1個類來管理所有線程之間的所有這些。

這大致就是我做的:

public class ArchiveIndex { 
     private IndexSearcher search; 
     private AtomicInteger activeSearches = new AtomicInteger(0); 
     private IndexWriter writer; 
     private AtomicInteger activeWrites = new AtomicInteger(0); 

     public List<Document> search(...) { 
      synchronized(this) { 
       if(search != null && !search.getIndexReader().isCurrent() && activeSearches.get() == 0) { 
       searcher.close(); 
       searcher = null; 
       } 

       if(search == null) { 
        searcher = new IndexSearcher(...); 
       } 
      } 

      activeSearches.increment(); 
      try { 
       // do you searching 
      } finally { 
       activeSearches.decrement(); 
      } 
      // do you searching 
     } 


     public void addDocuments(List<Document> docs) { 
      synchronized(this) { 
      if(writer == null) { 
       writer = new IndexWriter(...); 
      } 
      } 
      try { 
       activeWrites.incrementAndGet(); 
       // do you writes here. 
      } finally { 
       synchronized(this) { 
        int writers = activeWrites.decrementAndGet(); 
        if(writers == 0) { 
         writer.close(); 
         writer = null; 
        } 
       } 
      } 
     } 
    } 

所以我有,我用兩個讀者和作家單個類。注意,這個類允許同時進行書寫和閱讀,並且多個閱讀器可以同時進行搜索。唯一的同步是快速檢查,看看你是否需要重新打開搜索器/作家。我沒有在方法級別上同步,這種級別只允許一個讀寫器在性能方面不佳。如果有活躍的搜索者,那麼你不能放棄搜索者。所以如果你有很多讀者進入它只是簡單地搜索沒有更改。一旦渺茫,下一個搜索者將重新打開骯髒的搜索者。對於流量暫停的網站數量較少的情況,這可能非常有用。它仍然可能導致飢餓(即你總是閱讀更老和更舊的結果)。你可以添加邏輯來簡單地停止並重新初始化,如果自從它被發現以來的時間髒了比X舊,否則我們懶惰,因爲它現在。這樣,你會得到保證的搜索決不會比X.

作家可以以同樣的方式來處理老得多。我傾向於記得定期關閉作者,所以讀者會注意到它的改變(提交它)。我並沒有做出很好的描述,但它的搜索方式非常相似。如果有活躍的作家,你不能關閉作家。如果你是最後一位關閉作家的作家。你明白了。

+0

,你提出的基本思路是算主動搜索,因爲我已經在我的帖子的結尾寫的。正如我所說的,每次索引變髒時我都不想重新打開,所以應該在上面添加一些計時方法。它實際上接近我打算做的事情,但我想知道是否還有其他Lucene自己提供的關閉索引的東西。 Lucene引擎應該很容易告訴我們是否在另一個線程上發生了某些事情。 – Iravanchi

+0

順便說一句,+1給你的答案,但我在等待更多的想法。我猜代碼有一些同步問題(比如最後的finally塊也應該同步(this)),我建議你修復它們(如果有的話),以防其他人出現並使用代碼。 – Iravanchi

+0

其實這個代碼根據文檔沒問題。它的優良允許多個線程訪問的IndexWriter「注:IndexWriter類實例完全線程安全的,這意味着多個線程可以調用它的任何方法,同時如果你的應用需要外部同步,你不應該在的IndexWriter實例,因爲這可以同步。導致死鎖;改用你自己的(非Lucene)對象。「 – chubbsondubs

0

你只希望如果實際指數已更改爲創建一個新的閱讀器。我所做的是保留對IndexReader的引用,並在重新編制索引之後將其刪除。這是因爲我希望能夠在索引期間進行搜索,並且我相信在寫入時無法打開IndexReader(如果我錯了,請糾正我)。

我讓應用程序創建一個新的讀者,如果有沒有可用的,所以它的排序各指標提交後得到處理緩存的。

如果您需要實時索引功能(在獨佔操作期間在當前索引實體中搜索),則可以使用getReader()方法從當前IndexWriter獲取IndexReader。

+0

據我所知,您可以打開任意數量的只讀'IndexReader's和一個可以同時寫入的數據。我的問題並不涉及創建搜索者,它關注關閉它們並確定何時這樣做是安全的。 – Iravanchi

+0

您無法使用Reader進行書寫。是的,你可以打開併發閱讀器,但是這有一些開銷,你可以同時閱讀,而且它們是線程安全的,所以沒有理由有多個閱讀器,除非你有特殊的理由。當你從一個讀者閱讀,從指數的狀態創建閱讀器時,從重建索引之前閱讀,這意味着它仍然會重新索引操作過程中正常工作,即使(考慮如果你可以用deleteAll(會發生什麼) ,下一個Reader將是一個空索引)。 – jishi

2

有一個相對較新的SearcherManager類需要這個問題的關心和可以隱藏你的代碼中IndexReader完全。儘管API可能會發生變化,但我認爲這大大簡化了事情。

基礎教程從Mike McCandless,一個Lucene項目comitter:http://blog.mikemccandless.com/2011/09/lucenes-searchermanager-simplifies.html

+0

+1感謝您提到這一點。其實我在發佈問題後最終在代碼中使用了這個,但忘了在這個頁面中放置一個指針。 – Iravanchi

相關問題