2012-03-09 43 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); 
} 

其他最佳实践,比如依赖注入,国际奥委会等,以确保这是推荐单身人士并经过适当测试,但最好留给任何一个主题的教程。

相关问题