10

そこで私は、Web アプリケーション内から Lucene.Net インデックスの検索と書き込みを実装するための最良の方法についていくつかの調査を行ってきました。私は次の要件を設定しました。

  • インデックスの同時検索とアクセスを許可する必要がある (クエリは並行して実行される)
  • 複数のインデックスがあります
  • インデックス検索を完全に最新 (「リアルタイム」) にする必要はありません
  • 一定の頻度でジョブを実行してインデックスを更新します (頻度はインデックスごとに異なります)
  • 明らかに、これらすべてを lucene の「ベスト プラクティス」に従い、適切に実行およびスケーリングできる方法で実行したいと考えています。

私はいくつかの役立つリソースを見つけました。また、このような SO に関するいくつかの良い質問も見つけました

ガイダンスとしてその投稿に従い、インデックスを管理するために構築されたラッパーの同時実行ディクショナリでシングルトン パターンを試すことにしました。

簡単にするために、1 つのインデックスのみを管理していると仮定します。この場合、ラッパーがシングルトンになる可能性があります。これは最終的に次のようになります。

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;
                }
            }
        }
    }
}

理論的には、これにより、公開されている 2 つのメソッドがあり、ASP.NET Web アプリケーション内から問題なく呼び出すことができる、インデックス (ここでは "index-test" という名前) のスレッドセーフなシングルトン インスタンスが許可されるはずSearch()ですWrite()。スレッドセーフ?(これが間違っている場合は、お知らせください)。

今私に少し問題を引き起こしていることが1つありました:

これらのインスタンスをApplication_EndGlobal.asax.cs ファイルで適切に閉じて、IIS で Web アプリケーションを再起動する場合に write.lock の失敗などを繰り返さないようにするにはどうすればよいですか?

これまでのところ、私が考えることができるのは次のとおりです。

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

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

でそれを呼び出しApplication_Endますが、アクティブなサーチャーまたはライターがある場合、インデックスが破損することはありますか?

どんな助けや提案も大歓迎です。ありがとう。

4

3 に答える 3

11

Lucene.NET は非常にスレッドセーフです。IndexWriterおよびクラスのすべてのメソッドIndexReaderはスレッドセーフであり、同期を気にせずに使用できると断言できます。これらのクラスのインスタンスの同期に関連するすべてのコードを取り除くことができます。

とはいえ、より大きな問題は、ASP.NET から Lucene.NET を使用することです。 ASP.NET はさまざまな理由でアプリケーション プールをリサイクルしますが、 1 つのアプリケーション ドメインをシャットダウンしている間に、サイトへの新しい要求を処理するために別のアプリケーション ドメインを起動します。

FSDirectory別のIndexWriter/を使用して同じ物理ファイルにアクセスしようとすると (ファイル システム ベースの を使用していると仮定) IndexReader、ファイルのロックがアプリケーション ドメインによって解放されていないため、エラーが発生します。まだシャットダウンされていません。

そのために推奨されるベスト プラクティスは、Lucene.NET へのアクセスを処理するプロセスを制御することです。これは通常、Remoting または WCF (できれば後者) を介して操作を公開するサービスを作成することを意味します。

この方法ではより多くの作業が必要になります (操作を表すためにすべての抽象化を作成する必要があるため) が、次の利点が得られます。

  • サービス プロセスは常に稼働しています。つまり、クライアント (ASP.NET アプリケーション) は、必要なファイルの競合について心配する必要がありませんFSDirectory。サービスを呼び出すだけです。

  • 検索操作をより高いレベルで抽象化しています。Lucene.NET に直接アクセスするのではなく、操作とそれらの操作に必要な型を定義します。それを抽象化したら、Lucene.NET から他の検索メカニズム ( RavenDB など) に移行する場合は、コントラクトの実装を変更するだけです。

于 2012-08-20T14:42:24.130 に答える
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;
        }
    }
}
于 2012-08-20T14:47:17.687 に答える
1

また、IIS でアプリケーション プールのオーバーラップ設定を無効にして、1 つのアプリケーション プールがシャットダウンしている (ただし、write.lock を保持している) ときに、IIS が新しい要求のために別のアプリケーション プールを準備しているときに、Lucene の write.lock の問題を回避することもできます。

于 2014-04-19T18:50:05.677 に答える