2012-03-09 64 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 ms),它似乎很好,但在進入生產環境之前顯然應該進行更多的測試。而且,如果請求的速度比將數據保存到數據庫的速度要快(這不太可能,但應該考慮),那麼當然會出現問題。

更新,2018年2月:現在這個來看,我知道你可能有兩個savingThread情況下,如果你用螺紋時機倒黴(你應該假設你會)結束。和new Thread()是現在在C#中做這種事情的老方法。我將把這個作爲練習的現代,更線程安全的實現留給讀者。

1

選項2聽起來不錯。讓您無需太多開銷即可進行控制。

對於每個請求,您可能還會考慮使用ThreadPool.QueueUserWorkItem()(而不是每個請求的新線程)(作爲1的變體)。 .Net管理工作項分配給線程,以便不會創建太多的線程。如果你請求的所有請求都掛了很長一段時間,但仍然有可能讓ASP.Net中的線程捱餓,但我認爲ASP.Net使用與工作項線程不同的一組線程來避免這個問題。

2

一個更現代,TPL的方法(如在接受的答案在二月'18建議)可能會是這個樣子:

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

其他最佳實踐,比如依賴注入,國際奧委會等,以確保這是推薦單身人士並經過適當測試,但最好留給任何一個主題的教程。

相關問題