谈一谈Java开发中的坑(一) -- log4j2在SaaS项目中的应用

背景介绍:
做server端开发有些年头了,特别是开始做SaaS类型的项目时,深知log的重要性,特别是半夜三更用户(大都是美国那边的)遇到问题的电话打过来,这个时候没有一个强大log,那只有抓瞎的份了。以前都是走的微软系的开发工具,log的框架也是别人早就写好的,所以对于log性能这一块也没有什么关注,觉得它就应该是轻量级的,不应该耗费太多的资源。
后来换公司以后,开始学习用java编程了,紧跟着也就是各种开源类项目的引用。这次就跟大家说说,我在log这一块遇到的问题和解决方案。

遇到的问题:
log框架的选型上比较顺利,直接选用了比较常用的log4j2。开发工程中,没有考虑太多,直接采用了常用的同步logger,appender采用的RollingFileAppender。类似如下配置方法:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
开发工程中一切安好,可是在打压测试时发现,service的响应性能随着线程的增加会变得很差(初期目标8000个用户线程)。后来抓了dump(jstack)发现了好多线程都停留在执行log的方法里。这才开始看看为什么log的性能为什么这么差。

解决方案:
后来上网搜索了一下,发现对于像这种SaaS,log需求量比较大的应用(多线程并发打log),需要采用异步的log输出方式。log4j2对于异步log有很好的支持。这里就把我学到的做一个小的总结。
  1. 异步log的历史
起初log4j 1.x中引入了async appender,例如在RollingFileAppender中,设置一个属性immediateFlush=false,就可以实现异步log,其后台通过一个ArrayBlockingQueue,来实现log的缓存机制,其I/O操作使用的是BufferedOutputStream。
其后的log4j2中引入了AsyncLogger的概念,其中引用到了LMAX Disprutor的概念,性能明显优化于ArrayBlockingQueue;同时Log4j2还引入了一个新的RandomAccessFileAppender,其使用ByteBuffer + RandomAccessFile的方式,比BufferedOutputStream在性能方面有明显的提升。
  1. 最终的解决方案
AsyncLogger + RandomAccessFileAppender,示例如下:
<Configuration status="WARN">
<Appenders>
<!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
<RandomAccessFile name="RandomAccessFile" fileName="async.log"
immediateFlush="false" append="false">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ThdID=%t] %-level: %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
</Policies>
</RandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="RandomAccessFile"/>
</Root>
</Loggers>
  1. 一个小建议:
在使用log的方式上,不要采取这样的方式,例如:myLogger.logDebug("aa" + i + " bb" + j),而是要采用myLogger.logDebug("aa {} bb {}", i, j);的方式。这样做的好处,可以把字符串的拼接操作,放在需要输出之前,这样可以在你想要按level控制输出时,减少那些不必要的字符操作,例如:
public void logDebug(String logMsg, Object... params){
if(currentLevel > DEBUG){
// 当决定不输出debug level的数据时,这里没有发生字符串的拼接操作
return;
}

// 在这里做实际的输出
}

以下是几篇非常不错的关于AsyncLogger和LMAX Disprutor的文章,供大家深入的学习:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章