需求很簡單,就是在C#開發中高速寫日志。比如在高並發,高流量的地方需要寫日志。我們知道程序在操作磁盤時是比較耗時的,所以我們把日志寫到磁盤上會有一定的時間耗在上面,這些並不是我們想看到的。
使用列隊先緩存到內存,然後我們一直有個線程再從列隊中寫到磁盤上,這樣就可以高速高性能的寫日志了。因為速度慢的地方我們分離出來了,也就是說程序在把日志扔給列隊後,程序的日志部分就算完成了,後面操作磁盤耗時的部分程序是不需要關心的,由另一個線程操作。
俗話說,魚和熊掌不可兼得,這樣會有一個問題,就是如果日志已經到列隊了這個時候程序崩潰或者電腦斷電都會導致日志部分丟失,但是有些地方為了高性能的寫日志,是否可以忽略一些情況,請各位根據情況而定。

這裡寫日志的部分LZ選用了比較常用的log4net,當然也可以選擇其他的日志組件,比如nlog等等。
第一步我們首先需要把日志放到列隊中,然後才能從列隊中寫到磁盤上。
public void EnqueueMessage(string message, FlashLogLevel level, Exception ex = null)
{
if ((level == FlashLogLevel.Debug && _log.IsDebugEnabled)
|| (level == FlashLogLevel.Error && _log.IsErrorEnabled)
|| (level == FlashLogLevel.Fatal && _log.IsFatalEnabled)
|| (level == FlashLogLevel.Info && _log.IsInfoEnabled)
|| (level == FlashLogLevel.Warn && _log.IsWarnEnabled))
{
_que.Enqueue(new FlashLogMessage
{
Message = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff") + "]\r\n" + message,
Level = level,
Exception = ex
});
// 通知線程往磁盤中寫日志
_mre.Set();
}
}
_log是log4net日志組件的ILog,其中包含了寫日志,判斷日志等級等功能,代碼開始部分的if判斷就是判斷等級和現在的日志等級做對比,看是否需要寫入列隊,這樣可以有效的提高日志的性能。
其中的_que是ConcurrentQueue列隊。_mre是ManualResetEvent信號,ManualResetEvent是用來通知線程列隊中有新的日志,可以從列隊中寫入磁盤了。當從列隊中寫完日志後,重新設置信號,在等待下次有新的日志到來。
從列隊到磁盤我們需要有一個線程從列隊寫入磁盤,也就是說我們在程序啟動時就要加載這個線程,比如asp.net中就要在global中的Application_Start中加載。
/// <summary>
/// 另一個線程記錄日志,只在程序初始化時調用一次
/// </summary>
public void Register()
{
Thread t = new Thread(new ThreadStart(WriteLog));
t.IsBackground = false;
t.Start();
}
/// <summary>
/// 從隊列中寫日志至磁盤
/// </summary>
private void WriteLog()
{
while (true)
{
// 等待信號通知
_mre.WaitOne();
// 判斷是否有內容需要如磁盤
while (_que.Count > 0)
{
FlashLogMessage msg;
if (_que.TryDequeue(out msg)) // 從列隊中獲取內容,並刪除列隊中的內容
{
// 判斷日志等級,然後寫日志
switch (msg.Level)
{
case FlashLogLevel.Debug:
_log.Debug(msg.Message, msg.Exception);
break;
case FlashLogLevel.Info:
_log.Info(msg.Message, msg.Exception);
break;
case FlashLogLevel.Error:
_log.Error(msg.Message, msg.Exception);
break;
case FlashLogLevel.Warn:
_log.Warn(msg.Message, msg.Exception);
break;
case FlashLogLevel.Fatal:
_log.Fatal(msg.Message, msg.Exception);
break;
}
}
}
// 重新設置信號
_mre.Reset();
}
}

經過測試發現
使用原始的log4net寫入日志100000條數據需要:29178毫秒。
同樣數據使用列隊方式只需要261毫秒。

public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
FlashLogger.Instance().Register();
}
}
FlashLogger.Debug("Debug");
FlashLogger.Debug("Debug", new Exception("testexception"));
FlashLogger.Info("Info");
FlashLogger.Fatal("Fatal");
FlashLogger.Error("Error");
FlashLogger.Warn("Warn", new Exception("testexception"));
https://github.com/Emrys5/Emrys.FlashLog
最後望對各位有所幫助,本文原創,歡迎拍磚和推薦。