所以我一直在研究从Web应用程序中实现Lucene.Net索引搜索和写入的最佳方法。我提出了以下要求:
我找到了一些有用的资源,并在这里提出了几个很好的问题,例如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
中调用,但如果我有任何活跃的搜索者或作者,这是否会导致索引损坏?
非常感谢任何帮助或建议。感谢。
答案 0 :(得分:11)
Lucene.NET 非常线程安全。我可以肯定地说IndexWriter
和IndexReader
类上的所有方法都是线程安全的,您可以使用它们而不必担心同步。您可以删除所有涉及围绕这些类的实例进行同步的代码。
也就是说,更大的问题是使用ASP.NET中的Lucene.NET。但是,ASP.NET recycles the application pool for a number of reasons在关闭一个应用程序域时会引发另一个应用程序域来处理对该站点的新请求。
如果您尝试使用不同的FSDirectory
/ IndexWriter
访问相同的物理文件(假设您使用的是基于文件系统的IndexReader
),那么您将收到错误因为尚未关闭的应用程序域尚未释放文件锁定。
为此,建议的最佳实践是控制处理Lucene.NET访问的进程;这通常意味着创建一个服务,您可以通过Remoting或WCF(最好是后者)公开您的操作。
这种方式更多(因为您必须创建所有抽象来代表您的操作),但您可以获得以下好处:
服务进程将始终处于启动状态,这意味着客户端(ASP.NET应用程序)不必担心争用FSDirectory
所需的文件。他们只需要拨打该服务。
您在更高级别上抽象搜索操作。您不是直接访问Lucene.NET,而是定义这些操作所需的操作和类型。一旦你完成了抽象,如果你决定从Lucene.NET转移到其他搜索机制(比如RavenDB),那么就需要改变合同的实现。 / p>
答案 1 :(得分:3)
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;
}
}
}
答案 2 :(得分:1)
您还可以在IIS中禁用应用程序池重叠设置,以避免一个应用程序池关闭时(但仍保留write.lock)并且IIS正在为新请求准备另一个应用程序池时出现Lucene write.lock问题。