代码方式配置Log4j并实现线程级日志管理 第四部分

一 异步输出模式

  目前尚剩余两个需求,一个是实现日志的异步输出模式,一个是实现日志同时按日期和文件大小进行备份。

  异步输出模式在第三部分说过了,在这部分单独讲,日志的备份则放在第五部分结束。

  这部分我只提供一个设计思路,重心还是放在代码执行性能上,总体的思路为:

  1. 维护一个日志内容集合
  2. 开辟单独线程对日志内容集合进行输出

  这里需要考虑一个事情,如何维护一个日志内容的集合?

  1. 第一点必须要满足FIFO(先进先出),尽量保障日志的输出顺序;
  2. 第二点不能阻塞,因为一旦集合的读写阻塞,会使线程一直握着系统资源,而导致CPU资源占用率高,整体处理性能的降低;
  3. 第三点必须保证线程安全,因为一旦开启异步输出模式,所有的应用线程不再通过ThreadLogger对象来直接输出日志,而会将日志内容重定向到日志内容集合中,这是一个多线程并发的写入动作,异步处理线程则单独的执行读操作,所以线程安全问题必须考虑!

  综上,我考虑使用ConcurrentLinkedQueue,这是一个非阻塞且线程的安全的FIFO队列,其API有兴趣的读者可查阅相关文档。

二 增加异步输出模式开关

  那么按照这个设计思路首先要对LogUtil进行微调,增加一个是否开启异步模式的成员,一旦该标志位为True,则LogUtil.log()方法不再直接进行日志输出,转而将日志内容写入ConcurrentLinkedQueue。

/**
 * 1.实现代码方式配置Log4j <br>
 * 2.实现线程级日志对象管理 <br>
 * 3.实现日志的异步输出模式 <br>
 * 4.实现按日志文件大小及日期进行文件备份
 * 
 * @author 胡楠
 *
 */
public final class LogUtil
{
	……
	/**
	 * 日志内容集合,其他应用线程写入日志,异步处理线程则将日志读出并写入文件
	 */
	private static ConcurrentLinkedQueue<String> queueLogBuffer = new ConcurrentLinkedQueue<String>();
	/**
	 * 异步输出模式开关,默认false
	 */
	private static boolean ASYNCHRONOUS = false;

	/**
	 * 设置日志输出模式
	 * 
	 * @param asych
	 */
	public static void setLogMode(boolean asych)
	{
		ASYNCHRONOUS = asych;
	}
	……
}

三 重构日志输出接口

  既然已经加入异步输出模式,那么LogUtil提供输出接口则不能在单纯的进行日志输出了,首先要判断是否为异步模式,如果是的话则需要将日志内容写入ConcurrentLinkedQueue:


private static void log(LogLevel level, String message)
	{
		if (ASYNCHRONOUS)
		{
			queueLogBuffer.offer(message);
			return;
		}
		
		if (level == LogLevel.Trace)
		{
			getThreadLogger().logTrace(message);
		}
		else if (level == LogLevel.Debug)
		{
		……
	}

四 异步处理线程

  异步处理线程的逻辑要略严格些,我们必须要考虑当ConcurrentLinkedQueue中无内容的时候,需要让线程释放CPU资源,仅当其他应用线程放入内容的时候才将其唤醒,这时候我们需要使用“等待-通知”模式:

/**
	 * 异步处理线程,使用等待通知模式,仅当其他应用线程放入内容,才唤醒该线程,否则线程放弃CPU资源
	 * 
	 * @author 胡楠
	 *
	 */
	class LogBufferProcessor implements Runnable
	{
		public void run()
		{
			synchronized (queueLogBuffer)
			{
				while (queueLogBuffer.poll() != null)
				{
					// TODO 进行日志输出
				}
				
				try
				{
					queueLogBuffer.wait();
				}
				catch (Exception e) 
				{
					e.printStackTrace();
				}
			}
		}
	}

  如上是一个简单的设计结构,如何进行日志输出,以及wait()中断等处理都没有很细致的实现,还是那句话,这里仅叙述设计思路,更加具体的实现,需要各位看官依自己的实际需求来实现。

  注意,按如上方式进行处理,需要对log()方法再次重构,因为一旦应用线程向ConcurrentLinkedQueue中放入内容,则需要唤醒异步处理线程,如下:

private static void log(LogLevel level, String message)
	{
		if (ASYNCHRONOUS)
		{
			queueLogBuffer.offer(message);
			queueLogBuffer.notifyAll();
			return;
		}
		……

五 总结及一些其他的建议

  如果你是一名刚入门Java的朋友,那么在接触线程操作的时候,一定别忘了关注性能问题,正如上面的设计,如果我们不采用“等待-通知”模式,不论线程是否有任务需要处理,它都会一直持有CPU资源,这就拖累了系统的整体性能。

  另外,我们还需要考虑一个事情,日志内容队列是否可以无限大,ConcurrentLinkedQueue是无界的,这意味着一旦应用线程疯狂的进行日志输出,很可能出现内存溢出,所以我建议对ConcurrentLinkedQueue设置阈值,一旦超过阈值,则转换为同步输出模式,虽然降低了性能,但是回避了内存溢出风险。

  虽然设计了异步输出模式,我依然建议补充缓存设计,即使异步模式把日志输出重定向到了单独的处理线程,但是频繁的IO操作依然是系统性能的负担,所以应该实现一个日志缓存,只有当缓存内容达到缓存阈值才进行一次IO操作,这样会更大的提升整体性能。

  请不要因为我贴上的代码不全而喷我不负责任,每个人每个项目的每个需求都有所差异,能够从别人的设计思路中获取自己需要的东西,举一反三才是最宝贵的。

  剩下最后第五部分,我会把自己重写的Appender贴上来,它实现了同时按日期及日志文件大小进行日志的备份,因为涉及对源码的延展,所以代码会贴的相对更全面一些,但是别抱太大期望,因为真的很简单……(别瞎百度,我搜过,百度上好多人都是瞎写的,各种复制粘贴,只要看过源码,跟踪过执行过程,你也能很容易实现定制化的需求)

猜你喜欢

转载自blog.csdn.net/o983950935/article/details/85057110