2012-03-09 2 views
2

私はWebファーム(3つのサーバー)にasp.netアプリケーションを持っています。そのアプリケーションの他に、ウェブサイトのすべてのリクエストをデータベースに記録するモジュールがあります。現在、インサートは同期的です。私はそれを変更したいので、挿入物はできるだけ挿入されるキューに送られます。キューにキューイングし、バックグラウンドスレッドのデータベースに挿入する

が、それは次のようになり、より良いバックグラウンドスレッド上の各要求を挿入する

  1. 試みに(データベースしゃっくりすぎる 多くのバックグラウンドスレッドは、最大慣れるでしょう?)

  2. スタートIN-単一のバックグラウンドスレッド上のプロセスキューで、ただ がキューから読み取り、挿入を実行します。

  3. 各サーバーがページ要求ログを送信するデータベースサーバーにアウトプロセスキューを作成します。 MSMQに組み込まれているものは、このようなものに使用するものでしょうか? - それとも過剰なことでしょうか?

答えて

2

オプション2が最適です。オプション1はあまりにも多くのバックグラウンドスレッドを作成し、オプション3は必要以上に複雑に聞こえるでしょう。

このようなものを試してみるとよいでしょう。

このクラスを考える:ロギングを実行する

class LogEntry 
{ 
    public string IpAddress { get; set; } 
    public string UserAgent { get; set; } 
    public DateTime TimeStamp { get; set; } 
    public string Url { get; set; } 
    //whatever else you need 
} 

使用このクラス:

class SiteLogger 
{ 
    private static object loggerLock = new object(); 
    private static List<LogEntry> _pendingEntries = new List<LogEntry>(); 
    private static Thread savingThread; 

    public static void AddEntry(LogEntry entry) 
    { 
     // lock when accessing the list to avoid threading issues 
     lock (loggerLock) 
     { 
      _pendingEntries.Add(entry); 
     } 

     if (savingThread == null) 
     { 
      // this should only happen with the first entry 
      savingThread = new Thread(SaveEntries); 
      savingThread.Start(); 
     } 
    } 

    private static void SaveEntries() 
    { 
     while (true) 
     { 
      while (_pendingEntries.Count > 0) 
      { 
       // lock around each individual save, not the whole loop 
       // so we don't force one web request to wait for 
       // all pending entries to be saved. 
       lock (loggerLock) 
       { 
        // save an entry to the database, however the app does that 
        MyDatabase.SaveLogEntry(_pendingEntries[0]); 
        _pendingEntries.RemoveAt(0); 
       } 
      } 

      Thread.Sleep(TimeSpan.FromSeconds(2)); 
      // 2 seconds is a bit of an arbitrary value. Depending on traffic levels, 
      // it might need to go up or down. 
     } 
    } 
} 

私は、任意のデータベースの関与なしに、簡単なコマンドラインテストアプリでこれを実行した(して、データベースの呼び出しをシミュレート10ミリ秒間スリープ状態になりました)、それはうまくいくように見えましたが、実際に運用環境に入る前に、より多くのテストを行う必要があります。また、データベースに保存するよりも要求が早くなった場合にも問題は発生します(これはほとんどありませんが、考慮する必要があります)。

アップデート、2018年2月:は今、このを見て、私はあなたが(あなたが意志と仮定しなければならない)スレッドタイミングに不運取得する場合、あなたは2つのsavingThreadのインスタンスで終わることができることを実現しています。そして、new Thread()は、C#でこのようなことをする古い方法のようなものです。私は現代的でスレッドセーフな実装を読者に任せています。これがあることを確認するなど、依存性注入、IoCの、のように、

class SiteLogger 
{ 
    private readonly Lazy<BlockingCollection<LogEntry>> _messageQueue = new Lazy<BlockingCollection<LogEntry>>(() => 
    { 
     var collection = new BlockingCollection<LogEntry>(); 
     Task.Factory.StartNew(processMessages, TaskCreationOptions.LongRunning); 
     return collection; 

     void processMessages() 
     { 
      foreach (var entry in collection.GetConsumingEnumerable()) 
      { 
       //Do whatever you need to do with the entry 
      } 
     } 
    }, LazyThreadSafetyMode.ExecutionAndPublication); 


    public void AddEntry(LogEntry logEntry) => _messageQueue.Value.TryAdd(logEntry); 
} 

その他のベスト・プラクティス:

1

オプション2はわかりやすいです。あまりにも多くのオーバーヘッドなしでコントロールできます。

リクエストごとに新しいスレッドではなく、ThreadPool.QueueUserWorkItem()を使用して(1のバリエーションとして)検討することもできます。 .Netはスレッドにワークアイテムの割り当てを管理するので、あまりに多くのスレッドを作成する必要はありません。 ASP.Netにスレッドを飢えさせる可能性はまだありますが、あなたが要求したものがすべて長期間停止してしまった場合、ASP.Netはその問題を回避するために作業項目スレッドとは異なるスレッドセットを使用すると思います。

2

Aより近代的な、TPLのアプローチは、(受け入れ答えで2月'18で提案されているように)、おそらくのようになります。シングルトンと適切にテストされたものが推奨されますが、それらのトピックのいずれかについてチュートリアルに残すのが最も良い方法です。

関連する問題