Asp.net 在后台线程上排队并执行数据库插入
我在一个web场(3台服务器)中有一个asp.net应用程序。除了这个应用程序之外,我还有一个模块,可以将网站的每个请求记录到数据库中。目前,插入是同步的。我想更改它,以便将插入内容发送到一个队列,该队列将尽可能插入这些内容 这样会更好吗Asp.net 在后台线程上排队并执行数据库插入,asp.net,database,Asp.net,Database,我在一个web场(3台服务器)中有一个asp.net应用程序。除了这个应用程序之外,我还有一个模块,可以将网站的每个请求记录到数据库中。目前,插入是同步的。我想更改它,以便将插入内容发送到一个队列,该队列将尽可能插入这些内容 这样会更好吗 尝试在后台线程上插入每个请求(也会 如果数据库出现故障,许多后台线程会被占用?) 在单个后台线程上启动进程内队列 从队列读取并执行插入 在每个服务器向其发送页面请求日志的数据库服务器上创建进程外队列。内置的MSMQ会用于类似的东西吗?-或者这会不会太过分了 选
选项2对我来说不错。让您无需过多开销即可控制
您还可以考虑使用TracePoal.QuealeUrWorkItMe()作为一个变体(1),而不是为每个请求提供一个新线程。Net管理工作项到线程的分配,这样就不会创建太多线程。如果所有请求都挂起很长一段时间,ASP.NET可能仍然会缺少线程,但我认为ASP.NET使用了与workitem线程不同的一组线程来避免该问题。
选项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.
}
}
}
class站点记录器
{
私有静态对象loggerLock=新对象();
私有静态列表_pendingenteries=新列表();
私有静态线程读取;
公共静态无效补遗(日志条目)
{
//访问列表时锁定,以避免线程问题
锁(罗格锁)
{
_添加(条目);
}
if(savingThread==null)
{
//这应该只发生在第一个条目中
savingThread=新线程(SaveEntries);
savingtread.Start();
}
}
私有静态void SaveEntries()
{
while(true)
{
而(_pendingenteries.Count>0)
{
//锁定每个单独的保存,而不是整个循环
//因此,我们不会强制一个web请求等待
//要保存的所有挂起条目。
锁(罗格锁)
{
//将条目保存到数据库,但应用程序会这样做
MyDatabase.SaveLogEntry(_pendingenteries[0]);
_pendingenteries.RemoveAt(0);
}
}
线程睡眠(TimeSpan.FromSeconds(2));
//2秒是一个任意值。根据流量级别,
//它可能需要上升或下降。
}
}
}
我用一个简单的命令行测试应用程序运行了这个程序,没有任何数据库参与(通过睡眠10毫秒模拟数据库调用),它似乎工作得很好,但显然在进入生产环境之前应该进行更多的测试。此外,如果请求的速度快于您将其保存到数据库的速度,则当然会出现问题(这不太可能,但应予以考虑)
更新,2018年2月:现在看看这个,我意识到如果线程计时不走运(你应该假设你会),你可能会得到两个
savingtread
实例。而new Thread()
是一种在C#now中执行此类操作的老方法。我将把这种更现代、更线程安全的实现留给读者作为练习。一种更现代、第三方物流方法(如2月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);
}
class站点记录器
{
private readonly Lazy_messageQueue=新建Lazy(()=>
{
var collection=new BlockingCollection();
Task.Factory.StartNew(processMessages、TaskCreationOptions.LongRunning);
回收;
void processMessages()
{
foreach(collection.GetConsumingEnumerable()中的var条目)
{
//对条目执行任何需要执行的操作
}
}
},LazyThreadSafetyMode.ExecutionAndPublication);
public void AddEntry(LogEntry LogEntry)=>\u messageQueue.Value.TryAdd(LogEntry);
}
建议使用其他最佳实践,如依赖注入、IoC等,以确保这是一个单例并经过适当测试,但最好将这些主题中的任何一个留给教程