我在Web场中有一个asp.net应用程序(3台服务器)。除了该应用程序,我还有一个模块,可以将网站的每个请求记录到数据库中。目前插件是同步的。我想更改它,以便将插入内容发送到队列,该队列将尽可能插入它们。
会更好吗?
尝试在后台线程上插入每个请求(也是 如果数据库出现问题,很多后台线程都会用完?)
在单个后台线程上启动进程内队列 从队列中读取并执行插入。
答案 0 :(得分: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月:现在看看这个,我意识到如果你不熟悉线程计时你可能会得到两个savingThread
个实例(你应该假设你会这样) 。并且new Thread()
现在是一种在C#中做这种事情的旧方法。我将把现代的,更加线程安全的实现作为练习留给读者。
答案 1 :(得分:2)
更现代的TPL方法(如接受答案中的2月&#39; 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);
}
其他最佳实践,如依赖注入,IoC等,以确保这是一个单独的并经过适当测试的建议,但最好留给任何一个主题的教程。
答案 2 :(得分:1)
选项2对我来说听起来不错。让您无需太多开销即可控制。
您也可以考虑(作为1的变体)使用ThreadPool.QueueUserWorkItem()而不是每个请求的新线程。 .Net管理工作项到线程的分配,这样就不会创建太多的线程。如果所有请求都挂起了很长一段时间,那么仍然有可能使ASP.Net的线程饿死但我认为ASP.Net使用一组不同于工作项线程的线程来避免这个问题。