【问题标题】:Using Lucene.Net thread-safe from asp.net web application从 asp.net Web 应用程序中使用 Lucene.Net 线程安全
【发布时间】:2012-07-06 10:55:28
【问题描述】:

所以我一直在研究从 Web 应用程序中实现 Lucene.Net 索引搜索和写入的最佳方法。我提出了以下要求:

  • 需要允许并发搜索和访问索引(查询并行运行)
  • 会有多个索引
  • 不要求索引搜索完全是最新的(“实时”)
  • 以某种频率运行作业以更新索引(每个索引的频率不同)
  • 显然,希望以遵循 lucene“最佳实践”并且可以很好地执行和扩展的方式来完成所有这些工作

我在这里找到了一些有用的资源,以及一些关于 SO 的好问题,例如 this one

按照那篇文章作为指导,我决定尝试单例模式,其中包含一个用于管理索引的包装器的并发字典。

为了简单起见,我假设我只管理一个索引,在这种情况下,包装器可以成为单例。这最终看起来像这样:

public sealed class SingleIndexManager
{
    private const string IndexDirectory = "C:\\IndexDirectory\\";
    private const string IndexName = "test-index";
    private static readonly Version _version = Version.LUCENE_29;

    #region Singleton Behavior
    private static volatile SingleIndexManager _instance;
    private static object syncRoot = new Object();

    public static SingleIndexManager Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (syncRoot)
                {
                    if (_instance == null)
                        _instance = new SingleIndexManager();
                }
            }

            return _instance;
        }
    }
    #endregion

    private IndexWriter _writer;
    private IndexSearcher _searcher;

    private int _activeSearches = 0;
    private int _activeWrites = 0;

    private SingleIndexManager()
    {
        lock(syncRoot)
        {
            _writer = CreateWriter(); //hidden for sake of brevity
            _searcher = new IndexSearcher(_writer.GetReader());
        }
    }

    public List<Document> Search(Func<IndexSearcher,List<Document>> searchMethod)
    {
        lock(syncRoot)
        {
            if(_searcher != null && !_searcher.GetIndexReader().IsCurrent() && _activeSearches == 0)
            {
                _searcher.Close();
                _searcher = null;
            }
            if(_searcher == null)
            {
                _searcher = new IndexSearcher((_writer ?? (_writer = CreateWriter())).GetReader());
            }
        }
        List<Document> results;
        Interlocked.Increment(ref _activeSearches);
        try
        {
            results = searchMethod(_searcher);
        } 
        finally
        {
            Interlocked.Decrement(ref _activeSearches);
        }
        return results;
    }

    public void Write(List<Document> docs)
    {
        lock(syncRoot)
        {
            if(_writer == null)
            {
                _writer = CreateWriter();
            }
        }
        try
        {
            Interlocked.Increment(ref _activeWrites);
            foreach (Document document in docs)
            {
                _writer.AddDocument(document, new StandardAnalyzer(_version));
            }

        } 
        finally
        {
            lock(syncRoot)
            {
                int writers = Interlocked.Decrement(ref _activeWrites);
                if(writers == 0)
                {
                    _writer.Close();
                    _writer = null;
                }
            }
        }
    }
}

理论上,这应该允许索引的线程安全单例实例(这里称为“index-test”),其中我有两个公开公开的方法,Search()Write(),可以从内部调用ASP.NET Web 应用程序不关心线程安全? (如果这不正确,请告诉我)。

现在有一件事给我带来了一些麻烦:

我如何优雅地关闭 Global.asax.cs 文件中Application_End 上的这些实例,以便如果我想在 IIS 中重新启动我的 Web 应用程序,我不会遇到一堆 write.lock 失败等?

目前我能想到的只有:

public void Close()
{
    lock(syncRoot)
    {
        _searcher.Close();
        _searcher.Dispose();
        _searcher = null;

        _writer.Close();
        _writer.Dispose();
        _writer = null;
    }
}

并在 Application_End 中调用它,但如果我有任何活跃的搜索者或作者,这会导致索引损坏吗?

非常感谢任何帮助或建议。谢谢。

【问题讨论】:

  • 您的代码似乎找到了,但是由于您在构造函数中初始化了 Writer,所以我会简单地将其保持打开状态并删除 Write() 方法中的所有初始化/锁定。
  • 最好将搜索器从目录中初始化,并仅在需要时打开编写器 - 如果我的阅读量远大于写作量?
  • 我不知道,我没有太多经验,我通常在应用程序的生命周期内保持我的 IndexWriters 保持打开状态,并在我修改索引并使用 IndexWriter 打开搜索器时使用 commit()。 GetReader() 方法。
  • @JfBeaulac 所以也许我应该将其更改为始终打开编写器(调用关闭后除外)并创建一个 Commit() 方法?或者也许在每次写入后提交?感谢您的帮助。
  • @LelandRichardson 仅供参考,Lucene.net 是线程安全的,您不必使用任何同步机制(如 SingleIndexManagers、锁等)。只需创建/获取您的 IndexReaders/IndexWriters 并使用它们。我通常在应用程序范围内打开一个 IndexReader 和一个 IndexWriter 并在所有线程中使用它们。

标签: asp.net singleton lucene.net


【解决方案1】:

您还可以在 IIS 中禁用应用程序池重叠设置,以避免在一个应用程序池关闭(但仍持有 write.lock)并且 IIS 正在为另一个应用程序池准备新请求时出现 Lucene write.lock 问题。

【讨论】:

    【解决方案2】:

    Lucene.NET 非常线程安全。我可以肯定地说IndexWriterIndexReader 类上的所有方法都是线程安全的,您可以使用它们而不必担心同步问题。您可以摆脱所有涉及围绕这些类的实例进行同步的代码。

    也就是说,更大的问题是使用 ASP.NET 中的 Lucene.NET。 ASP.NET recycles the application pool for a number of reasons,然而,在关闭一个应用程序域时,它会启动另一个应用程序域来处理对站点的新请求。

    如果您尝试使用不同的IndexWriter/IndexReader 访问相同的物理文件(假设您使用基于文件系统的FSDirectory),那么您将收到一个错误,因为文件被锁定尚未被尚未关闭的应用程序域释放。

    为此,推荐的最佳实践是控制处理对 Lucene.NET 的访问的进程;这通常意味着创建一个服务,您可以在其中通过 Remoting 或 WCF(最好是后者)公开您的操作。

    这种方式工作量更大(因为您必须创建所有抽象来表示您的操作),但您可以获得以下好处:

    • 服务进程将始终启动,这意味着客户端(ASP.NET 应用程序)不必担心争夺FSDirectory 所需的文件。他们只需调用服务即可。

    • 您在更高级别上抽象您的搜索操作。您不是直接访问 Lucene.NET,而是定义这些操作所需的操作和类型。一旦你把它抽象出来,如果你决定从 Lucene.NET 转移到其他搜索机制(比如RavenDB),那么就需要更改合同的实现

    【讨论】:

    • 关于使用IndexReader/IndexWriter 锁定文件,如果您尝试在索引上打开两个IndexWriters,通常只会收到锁定错误。您可以共享IndexReader/IndexWriter,或者您可以在不同的线程和/或进程中打开多个 IndexReaders 而不会出现任何问题,即使您正在使用以下方式写入索引另一个 single IndexWriter。但是,如果作者已提交更改,则需要重新打开任何打开的 IndexReaders 以查看更改。
    【解决方案3】:
    • 打开 IndexWriter 可能是一项繁重的操作。您可以重复使用它。
    • Write(...) 中有一个锁以确保事务行为,所有文档都在方法返回之前添加并写入磁盘。对 Commit() 的调用可能是一个冗长的操作(它可能会导致段合并)。如果需要,您可以将其移至后台线程(这会引入一些添加的文档在提交中写入的场景,而另一些则在另一个中)。
    • 您的 Search(...) 方法中不需要无条件锁定。你可以检查你是否有一个 _searcher 实例,并使用它。在 Write(...) 中将其设置为 null 以强制使用新的搜索器。
    • 我不确定您是否使用了 searchMethod,它看起来更适合收集器。


    public sealed class SingleIndexManager {
        private static readonly Version _version = Version.LUCENE_29;
        private readonly IndexWriter _writer;
        private volatile IndexSearcher _searcher;
        private readonly Object _searcherLock = new Object();
    
        private SingleIndexManager() {
            _writer = null; // TODO
        }
    
        public List<Document> Search(Func<IndexSearcher, List<Document>> searchMethod) {
            var searcher = _searcher;
            if (searcher == null) {
                lock (_searcherLock) {
                    if (_searcher == null) {
                        var reader = _writer.GetReader();
                        _searcher = searcher = new IndexSearcher(reader);
                    }
                }
            }
    
            return searchMethod(searcher);
        }
    
        public void Write(List<Document> docs) {
            lock (_writer) {
                foreach (var document in docs) {
                    _writer.AddDocument(document, new StandardAnalyzer(_version));
                }
    
                _writer.Commit();
                _searcher = null;
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2014-09-19
      • 2015-07-02
      • 1970-01-01
      • 2013-12-19
      • 1970-01-01
      • 1970-01-01
      • 2013-08-12
      • 2013-03-03
      • 2018-05-20
      相关资源
      最近更新 更多