基于多线程的高性能异步日志管理机制与实现
通常在日志写的过程,每次写日志时,都是直接写入文件中。当数据量不大的时候,完成可以这样做,不会有什么问题。但是当数据量大时,每次写数据则会由于IO的瓶颈造成很大的性能问题。
为了解决这个问题,本文设计了一套基于多线程的在内存中追加日志后再写入文件的缓冲机制,从而解决这个问题。经测试,在没有任何特别文件读写优化的情况下,每秒可以实现20万条长度为30字节的日志的硬盘(SSD)写入。
基本原理
内存日志列表
首先建立一个缓冲列表用于保存日志。当日志在生成以后,先写入缓冲中,从而利用高速的内存可以以极高的速度写入数据。
多线程写入
多线程用于日志数据的收集和写入文件。本机制采用一个独立的线程,每隔一定的时间,根据日志列表中是否有新的数据,再决定向指定的文件追加日志信息。
源代码
使用示例
————————————————
static void Main(string[] args)
{
LogManager.Log(LogType.L101001_UserLogin, "10232");
LogManager.Log(LogType.L101002_UserLogout, "10232", DateTime.Now);
LogManager.Log(LogType.L415001_Debug, "MyLog.Program", true, 135.2);
// Simulates this program running for 5 seconds.
Thread.Sleep(5000);
}
源代码
LogManager 类
/// <summary>
/// This class is used to manage logs from the same program.
/// It has a key method AddLog to receive log. This is a
/// multi-threading class to save logs into a file in specified
/// seconds to avoid save logs too frequently.
/// </summary>
public class LogManager
{
/// <summary>
/// The file path to store log.
/// </summary>
public string LogPath { get; private set; } = "system.log";
/// <summary>
/// The interval (in second) of saving logs into file.
/// </summary>
public int SaveInterval { get; set; } = 1;
// To contronl the running status of the thread
bool isRunning = false;
// Teporary log list for holding current logs.
List<Log> logs = new List<Log>();
// The thread to perform method process()
Thread thread;
/// <summary>
/// Construct a LogManager object.
/// </summary>
public LogManager()
{
}
/// <summary>
/// Construct a LogManager object.
/// </summary>
/// <param name="logPath">The file path to store logs.</param>
/// <param name="saveInterval">Writing intervl.</param>
public LogManager(string logPath, int saveInterval = 10)
{
LogPath = logPath;
SaveInterval = saveInterval;
}
/// <summary>
/// Add a new log.
/// </summary>
/// <param name="logType">The type of this log.</param>
/// <param name="args">The arguments of this log.</param>
public void AddLog(LogType logType, object[] args)
{
AddLog(new Log(logType, args));
}
/// <summary>
/// Add a new log.
/// </summary>
/// <param name="log">The log to be added.</param>
public void AddLog(Log log)
{
lock (this)
{
logs.Add(log);
}
}
/// <summary>
/// main method to process logs.
/// </summary>
private void process()
{
isRunning = true;
while (isRunning)
{
if (logs.Count > 0)
{
// Obtain all logs and release lock
List<Log> loglist = new List<Log>();
lock (this)
{
loglist.AddRange(logs);
logs.Clear();
}
// append new logs into log file.
using (StreamWriter fw = new StreamWriter(LogPath, true, Encoding.UTF8))
{
foreach (Log log in loglist)
fw.WriteLine(log);
}
System.Console.WriteLine(DateTime.Now + " Saved.");
}
// Sleep SaveInterval seconds for no waisting CPU resource.
Thread.Sleep(SaveInterval);
}
}
/// <summary>
/// Starts thread of this log manager.
/// </summary>
public void Start()
{
if (thread != null)
return;
thread = new Thread(process);
thread.IsBackground = true;
thread.Start();
}
/// <summary>
/// Stops the thread of this log manager.
/// </summary>
public void Stop()
{
isRunning = false;
}
// used in static
static LogManager logManager;
// a static method to save logs.
public static void Log(LogType logType, params object[] args)
{
if (logManager == null)
{
logManager = new LogManager();
logManager.Start();
}
logManager.AddLog(logType, args);
}
}
public class Log
{
/// <summary>
/// The occuring time.
/// </summary>
public DateTime Time { get; set; } = DateTime.Now;
/// <summary>
/// The type of the log.
/// </summary>
public LogType LogType { get; set; }
/// <summary>
/// Arguements of a log.
/// </summary>
public string[] Arguments { get; set; }
// Used to separate data fields of a log when serializing.
char separator = (char)6;
/// <summary>
/// Construct a Log object.
/// </summary>
/// <param name="logType">The type of log.</param>
/// <param name="args">Argument list.</param>
public Log(LogType logType, object[] args)
{
LogType = logType;
Arguments = new string[args == null ? 0 : args.Length];
for (int i = 0; i < Arguments.Length; i++)
Arguments[i] = args[i] == null ? "" : args[i].ToString();
}
/// <summary>
/// Format: yyyyMMddHHmmss|LogType|Arguments
/// </summary>
/// <returns></returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append((int)(Time - new DateTime(2019,1,1)).TotalSeconds);
sb.Append(separator);
sb.Append((int)LogType);
sb.Append(separator);
sb.Append(string.Join(separator.ToString(), Arguments));
return sb.ToString();
}
/// <summary>
/// Parses a string into a Log object.
/// </summary>
/// <param name="str">Input string.</param>
/// <returns></returns>
public Log Parse(string str)
{
return null;
}
}