前言
在传统的日志系统中,日志的记录往往是同步进行的,这意味着每当应用程序记录一条日志时,都会直接写入到磁盘或者发送至远程日志服务器上,这个过程可能会因为磁盘I/O或网络延迟而变得相对较慢,在高并发的场景下,这类延迟可能会对应用程序的性能产生明显的影响。
目前很多日志框架都已经集成了异步记录日志的功能,例如Logback自带异步日志,而本文将侧重介绍Log4j2日志框架使用Disruptor来完成异步日志的支持,使得Log4j2能够在多线程应用程序中提供更快的日志写入性能,尽可能减少对应用程序性能的影响。
Log4j(即 Log4j 1.x)并没有集成 Disruptor,也没有提供原生的异步日志功能,如果想在 Java 应用程序中使用 Disruptor 来提高日志处理的性能,应该选择使用 Log4j2 并启用其异步日志功能,将Log4j升级为Log4j2的具体思路以及操作如下。
一、使用slf4j作为日志门面+log4j实现
1、思路
如果当前应用使用的是slf4j作为日志门面,而具体实现使用的是Log4j,类似于Logger logger = LoggerFactory.getLogger(XXX.class),则需要将log4j核心包删除并且排除第三方包引入的log4j核心包、排除slf4j与log4j的桥接包;同时新增 log4j2核心包、slf4j与log4j2的桥接包,这种情况无需改动日志打印代码即可。将log4j2.xml配置文件替换log4j.xml,log4j.xml中具体语法可以自行查询。
2、操作
(1)注释log4j核心包和slf4j与log4j的桥接包、排除第三方引入的log4j核心包和slf4j与log4j的桥接包。
<exclusion><artifactId>log4j</artifactId><groupId>log4j</groupId></exclusion><exclusion><artifactId>org.slf4j</artifactId><groupId>slf4j-log4j12</groupId></exclusion>
(2)引入log4j2核心jar包、slf4j与log4j2的桥接包
<!--log4j2核心包 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.18.0-jdsec.rc2</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.18.0-jdsec.rc2</version></dependency><!--slf4j与log4j2的桥接包 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>2.18.0-jdsec.rc2</version></dependency>
二、使用 log4j调用方式
思路
如果当前应用直接使用log4j调用方式,例如Logger logger = Logger.getLogger(XXX.class),直接排除log4j核心包后建议引入slf4j作为日志门面,然后新增log4j2的核心包,这个时候需要改动日志打印的代码,最后将log4j的配置文件替换为log4j2的配置文件。
操作
(1)注释log4j核心包、排除第三方包引入的log4j核心包
(2)引入slf4j核心包、log4j2核心包
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.32</version></dependency>
(3)
importorg.apache.log4j.Logger;privatestaticfinalLogger log =Logger.getLogger(XXX.class);
改为
importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;privatestaticLogger log =LoggerFactory.getLogger(MsgGateway.class);
上面说的建议引入slf4j作为日志门面,这样的好处在于使用简单的日志抽象层而最终用户在部署时选择使用具体的日志框架,保持通用和灵活性,但是弊端是需要改动日志打印的代码,如果不想改动代码,Apache Log4j 2项目提供的一个桥接库,它允许开发者在升级到Log4j 2时,继续使用基于Log4j 1.x版本API编写的代码。这个库的目的是为了提供向后兼容性,使得原有使用Log4j 1.x的项目可以无缝迁移到Log4j 2,而不需要重写大量的日志代码。
(4)log4j与log4j2的桥接包
<!--log4j与log4j2的桥接包--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-1.2-api</artifactId><version>2.18.0-jdsec.rc2</version></dependency>
三、使用JCL作为日志门面+log4j实现
思路
项目中还有一些类中使用了JCL+log4j这种日志打印形式,例如引入org.apache.commons.logging.Log;、org.apache.commons.logging.LogFactory,日志打印格式为Log log = LogFactory.getLog(XXX.class)。这种方式的解决思路与2.1.1和2.1.2差不多,建议将JCL的日志门面改为slf4j,这样整个应用的日志比较整齐简洁,但是弊端是需要改动代码,需要将JCL的日志打印格式改为LoggerFactory.getLogger,但是如果不想改动代码,Apache Log4j 2提供的桥接库通用可以满足直接桥接JCL和log4j2。
操作
<!--jcl与log4j2的桥接包--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-jcl</artifactId><version>2.18.0-jdsec.rc2</version></dependency>
四、Log4j2集成Disruptor
(1)引入disruptor的jar包
<dependency><groupId>com.lmax</groupId><artifactId>disruptor</artifactId><version>3.4.4</version></dependency>
(2)修改log4j2.xml配置文件或增加jvm参数
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
-Dlog4j2.asyncLoggerRingBufferSize=262144
-Dlog4j2.asyncQueueFullPolicy=Discard
-Dlog4j2.DiscardThreshold=ERROR
第一个属性指定了log4j2应该使用异步日志记录器上下文选择器AsyncLoggerContextSelector,将日志消息放在环形队列(上文说的RingBuffer)中,并通过单独的线程异步完成日志操作;同时通过asyncLoggerRingBufferSize属性设置环形队列大小,用于存储待处理的日志事件,队列长度越大则可以缓冲的日志事件越多,但是会消耗更多的内存大小;asyncQueueFullPolicy定义了当环形队列满的时候,Discard意味着新的日志事件将会被丢弃;DiscardThreshold属性则定义了队列满时日志丢弃的最低日志级别,我们配置的是ERROR。
参考文献
https://blog.csdn.net/javahongxi/article/details/85009811
版权归原作者 鹿人藤 所有, 如有侵权,请联系我们删除。