2017-01-23 11 views
-2

Я использую Lucene.Net для поиска и хотел узнать, как я могу справиться с этой проблемой многопоточности.Создание потоковой передачи Lucene.Net в коде

У меня есть один экземпляр класса Test, но поисковик в этом случае не является потокобезопасным, так как поток таймера может обновлять индекс одновременно с подачей запроса, и я вижу исключение из-за этого. Любые указатели на то, как сделать поток потокобезопасным.

public class Test 
{ 
    private static object syncObj = new object(); 

    private System.Threading.Timer timer; 

    private Searcher searcher; 

    private RAMDirectory idx = new RAMDirectory(); 

    public Test() 
    { 
     this.timer = new System.Threading.Timer(this.Timer_Elapsed, null, TimeSpan.Zero, TimeSpan.FromMinutes(3)); 
    } 


    private Searcher ESearcher 
    { 
     get 
     { 
      return this.searcher; 
     } 

     set 
     { 
      lock (syncObj) 
      { 
       this.searcher = value; 
      } 
     } 
    } 

    public Document CreateDocument(string title, string content) 
    { 
     Document doc = new Document(); 
     doc.Add(new Field("A", title, Field.Store.YES, Field.Index.NO)); 
     doc.Add(new Field("B", content, Field.Store.YES, Field.Index.ANALYZED)); 
     return doc; 
    } 

    public List<Document> Search(Searcher searcher, string queryString) 
    { 
     List<Document> documents = new List<Document>(); 
     QueryParser parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "B", new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30)); 
     Query query = parser.Parse(queryString); 
     int hitsPerPage = 5; 
     TopScoreDocCollector collector = TopScoreDocCollector.Create(2 * hitsPerPage, true); 
     this.ESearcher.Search(query, collector); 

     ScoreDoc[] hits = collector.TopDocs().ScoreDocs; 

     int hitCount = collector.TotalHits > 10 ? 10 : collector.TotalHits; 
     for (int i = 0; i < hitCount; i++) 
     { 
      ScoreDoc scoreDoc = hits[i]; 
      int docId = scoreDoc.Doc; 
      float docScore = scoreDoc.Score; 
      Document doc = searcher.Doc(docId); 
      documents.Add(doc); 
     } 

     return documents; 
    } 

    private void Timer_Elapsed(object sender) 
    { 
     this.Log("Started Updating the Search Indexing"); 
     // Get New data to Index 
     using (IndexWriter writer = new IndexWriter(this.idx, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30), true, IndexWriter.MaxFieldLength.LIMITED)) 
     { 
      foreach (var e in es) 
      { 
       writer.AddDocument(this.CreateDocument(e.Value.ToString(), e.Key)); 
      } 

      writer.Optimize(); 
     } 

     this.ESearcher = new IndexSearcher(this.idx); 
     this.Log("Completed Updating the Search Indexing"); 
    } 

    public Result ServeRequest() 
    { 
     var documents = this.Search(this.EntitySearcher, searchTerm); 
     //somelogic 
     return result; 

    } 

}

+0

Зачем использовать один экземпляр для начала? – CodeCaster

+0

В timer_elapsed выключите таймер, выполните работу, снова включите таймер –

+0

@CodeCaster ServiceFabric будет кэшировать экземпляр, поэтому он будет использовать один и тот же индекс. Также нужно каждый раз создавать индекс для нового запроса. – loneshark99

ответ

4

Много вещей "неправильно" с этим.

Как уже упоминалось, блокировка не была безопасной (вам необходимо заблокировать чтение и запись).

Более важно, что в Lucene есть более эффективные способы обработки. Во-первых, IndexWriter сам по себе является потоковым. Он должен быть владельцем Directory. Обычно «плохая практика» заключается в том, что разные части открывают/закрывают каталог.

Существует стиль для индексов NRT (Near Real Time), который включает в себя получение IndexReader из IW, а не обертывание каталога.

Стиль, используемый в вашем примере только действительно «хорошо», если индекс, по существу, только для чтения и, возможно, регенерировать в пакетном режиме ежедневно/еженедельно и т.д.

Я переписал пример, чтобы показать некоторые из подхода. Очевидно, что это просто тестовый код будет нюансы, которые будут нуждаться в рефакторинга/улучшение в зависимости от случая использования ...

public class Test 
{ 
    private static object syncObj = new object(); 

    private System.Threading.Timer timer; 

    private Searcher searcher; 

    private IndexWriter writer; 
    private IndexReader reader; 

    public Test() 
    { 
     writer = new IndexWriter(new RAMDirectory(), new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30), true, IndexWriter.MaxFieldLength.LIMITED); 
     reader = writer.GetReader(); 
     searcher = new IndexSearcher(reader); 
     timer = new System.Threading.Timer(Timer_Elapsed, null, TimeSpan.Zero, TimeSpan.FromMinutes(3)); 
    } 


    public void CreateDocument(string title, string content) 
    { 
     var doc = new Document(); 
     doc.Add(new Field("A", title, Field.Store.YES, Field.Index.NO)); 
     doc.Add(new Field("B", content, Field.Store.YES, Field.Index.ANALYZED)); 

     writer.AddDocument(doc); 
    } 

    public void ReplaceAll(Dictionary<string, string> es) 
    { 
     // pause timer 
     timer.Change(Timeout.Infinite, Timeout.Infinite); 

     writer.DeleteAll(); 
     foreach (var e in es) 
     { 
      AddDocument(e.Value.ToString(), e.Key); 
     } 

     // restart timer 
     timer.Change(TimeSpan.Zero, TimeSpan.FromMinutes(3)); 
    } 

    public List<Document> Search(string queryString) 
    { 
     var documents = new List<Document>(); 
     var parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "B", new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30)); 
     Query query = parser.Parse(queryString); 
     int hitsPerPage = 5; 
     var collector = TopScoreDocCollector.Create(2 * hitsPerPage, true); 
     searcher.Search(query, collector); 

     ScoreDoc[] hits = collector.TopDocs().ScoreDocs; 

     int hitCount = collector.TotalHits > 10 ? 10 : collector.TotalHits; 
     for (int i = 0; i < hitCount; i++) 
     { 
      ScoreDoc scoreDoc = hits[i]; 
      int docId = scoreDoc.Doc; 
      float docScore = scoreDoc.Score; 
      Document doc = searcher.Doc(docId); 
      documents.Add(doc); 
     } 

     return documents; 
    } 

    private void Timer_Elapsed(object sender) 
    { 
     if (reader.IsCurrent()) 
      return; 

     reader = writer.GetReader(); 
     var newSearcher = new IndexSearcher(reader); 
     Interlocked.Exchange(ref searcher, newSearcher); 
     Debug.WriteLine("Searcher updated"); 
    } 

    public Result ServeRequest(string searchTerm) 
    { 
     var documents = Search(searchTerm); 
     //somelogic 
     var result = new Result(); 

     return result; 

    } 
} 

Примечание:

  • писатель «владеет» каталог
  • , если это был файловый каталог, тогда у вас были бы Open и Close методы создания/удаления писателя (который касается обработки файла lock). RamDirectory может быть только GC'd
  • использует Interlocked.Exchange вместо lock. Таким образом, нулевая стоимость при использовании searcher члена (здесь драконы!)
  • новых документов добавляют непосредственно к писателю
  • IsCurrent() позволяет нулевой стоимости, если не были добавлены никаких новые документы. В зависимости от того, как часто вы добавляете документы, вам может не понадобиться таймер вообще (просто позвоните Timer_Elapsed - переименовано, очевидно, - в верхней части Search).
  • не использует Optimize() это похмелье от предыдущих версий, и это использование крайне нежелательно (перфорация и диск причина ввода/вывода)

Наконец, если вы используете Lucene.Net v4.8, то вам следует использовать SearcherManager (как предложено в другом ответе). Но используйте ctor, который принимает IndexWriter и сохранит его как «singleton» (тот же масштаб, что и writer). Он будет обрабатывать блокировку и получать новые читатели для вас.

+0

Возьмите мой верхний знак - уважение к количеству времени и усилий, предпринятых здесь. –

+0

Почему, спасибо, сэр (только после полудня) – AndyPook

+0

Спасибо за ответ, осталось несколько вопросов. Где называется созданный документ, как он создается и обновляется. Iscurrent, как он узнает о новых данных для индексации. – loneshark99

0

Вместо того, чтобы использовать новый IndexSearcher. Вы можете использовать класс «SearcherManager».

SearcherManager _searcherManager = новый SearcherManager (LuceneMapDirectory, NULL);

И поиск следующим образом:

_searcherManager.ExecuteSearch(searcher => 
     { 
      //Execute query using <searcher> 
     }, ex => { Trace.WriteLine(ex); }); 
+0

вы должны заметить, что SearcherManager доступен только в версии 4.8 – AndyPook

+0

Не похоже, что Lucene.net 4.8 имеет SearcherManager.ExecuteSearch. Я использую выпуск от 10 мая – mdiehl13