目录
log4j2的异步日志输出方式
使用log4j2的同步日志进行日志输出,日志输出语句与程序的业务逻辑语句将在同一个线程运行。
而使用异步日志进行输出时,日志输出语句与业务逻辑语句并不是在同一个线程中运行,而是有专门的线程用于进行日志输出操作,处理业务逻辑的主线程不用等待即可执行后续业务逻辑。
Log4j2中的异步日志实现方式有AsyncAppender和AsyncLogger两种。
其中:
AsyncAppender
采用了ArrayBlockingQueue来保存需要异步输出的日志事件;AsyncLogger
则使用了Disruptor框架来实现高吞吐。
第一种实现异步方式AsyncAppender
AsyncAppender直接在log4j2的xml的配置文件中配置,注意下面代码的注释位置
<?xml version=\"1.0\" encoding=\"UTF-8\"?> <Configuration status=\"warn\"> <Appenders> <!--正常的Appender配置,此处配置的RollingFile会在下面AsyncAppender被通过name引用--> <RollingFile name=\"RollingFileError\" fileName=\"${Log_Home}/error.${date:yyyy-MM-dd}.log\" immediateFlush=\"true\" filePattern=\"${Log_Home}/$${date:yyyy-MM}/error-%d{MM-dd-yyyy}-%i.log.gz\"> <PatternLayout pattern=\"%d{yyyy-MM-dd \'at\' HH:mm:ss z} %-5level %logger{36} : %msg%xEx%n\"/> <ThresholdFilter level=\"error\" onMatch=\"ACCEPT\" onMismatch=\"DENY\"/> <Policies> <TimeBasedTriggeringPolicy modulate=\"true\" interval=\"1\"/> <SizeBasedTriggeringPolicy size=\"10MB\"/> </Policies> </RollingFile> <!--一个Appender配置完毕--> <!--异步AsyncAppender进行配置直接引用上面的RollingFile的name--> <Async name=\"Async\"> <AppenderRef ref=\"MyFile\"/> </Async> <!--异步AsyncAppender配置完毕,需要几个配置几个--> </Appenders> <Loggers> <Root level=\"error\"> <!--此处如果引用异步AsyncAppender的name就是异步输出日志--> <!--此处如果引用Appenders标签中RollingFile的name就是同步输出日志--> <AppenderRef ref=\"Async\"/> </Root> </Loggers> </Configuration>
重点内容全在上面代码的注释中,AsyncAppender的配置就在xml文件中实现,无需单独引用包来支持.配置AsyncAppender后,日志事件写入文件的操作将在单独的线程中执行。
AsyncAppender的常用参数
参数名 | 类型 | 说明 |
---|---|---|
name | String | Async Appender的名字 |
AppenderRef | String | 异步调用的Appender的名字,可以配置多个 |
blocking | boolean | 默认为true。如果为true,appender将一直等待直到queue中有空闲;如果为false,当队列满的时候,日志事件将被丢弃。(如果配置了error appender,要丢弃的日志事件将由error appender处理) |
bufferSize | integer | 队列中可存储的日志事件的最大数量,默认为128 |
第二种实现异步方式AsyncLogger
Log4j2中的AsyncLogger的内部使用了Disruptor框架。
Disruptor简介
Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,基于Disruptor开发的系统单线程能支撑每秒600万订单。
目前,包括Apache Strom、Log4j2在内的很多知名项目都应用了Disruptor来获取高性能。
Disruptor框架内部核心数据结构为RingBuffer,其为无锁环形队列。
Disruptor为什么这么快?
- lock-free-使用了CAS来实现线程安全
- 使用缓存行填充解决伪共享问题
首先在pom单中应用相关的包
<dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.4.2</version> </dependency>
第二步在log4j2的xml文件中配置AsyncLogger
log4j2.xml配置如下:
<?xml version=\"1.0\" encoding=\"UTF-8\"?> <Configuration status=\"debug\" name=\"MyApp\" packages=\"\"> <Appenders> <Console name=\"Console\" target=\"SYSTEM_OUT\"> <PatternLayout pattern=\"%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n\" /> </Console> <RollingFile name=\"RollingFile\" fileName=\"logs/app.log\" filePattern=\"logs/app-%d{yyyy-MM-dd HH}.log\"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m%n</Pattern> </PatternLayout> <Policies> <SizeBasedTriggeringPolicy size=\"500MB\"/> </Policies> </RollingFile> <RollingFile name=\"RollingFile2\" fileName=\"logs/app2.log\" filePattern=\"logs/app2-%d{yyyy-MM-dd HH}.log\"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m%n</Pattern> </PatternLayout> <Policies> <SizeBasedTriggeringPolicy size=\"500MB\"/> </Policies> </RollingFile> </Appenders> <Loggers> <!--上面的配置都和原配置一样,就是在下方这直接定义AsyncLogger,他的name在java类中被引用即可--> <AsyncLogger name=\"com.meituan.Main\" level=\"trace\" additivity=\"false\"> <appender-ref ref=\"RollingFile\"/> </AsyncLogger> <AsyncLogger name=\"RollingFile2\" level=\"trace\" additivity=\"false\"> <appender-ref ref=\"RollingFile2\"/> </AsyncLogger> <Root level=\"debug\"> <AppenderRef ref=\"Console\"/> <AppenderRef ref=\"RollingFile\"/> </Root> </Loggers> </Configuration>
java代码如下:
public class Main { public static void main(String args[]) { //引用com.meituan.Main日志输出器 Logger logger = LogManager.getLogger(Main.class); //引用的名为RollingFile2的异步AsyncLogger Logger logger2 = LogManager.getLogger(\"RollingFile2\"); Person person = new Person(\"Li\", \"lei\"); logger.info(\"hello, {}\", person); logger2.info(\"good bye, {}\", person); }
上述log4j2.xml中配置了两个AsyncLogger,名字分别为com.meituan.Main和RollingFile2。
并且,在main方法中分别使用两个logger来输出两条日志。
在加载log4j2.xml的启动阶段,如果检测到配置了AsyncRoot或AsyncLogger,将启动一个disruptor实例。
log4j2异步注意事项
log4j2异步类型
1) 使用<Async>标签
示例:
<Async name=\"asyncKafkaLog\"> <AppenderRef ref=\"Failover\" /> </Async>
注意事项: 此类异步队列是BockingQueue,队列默认大小是128
2) 使用<AsyncLogger>标签
示例:
<AsyncLogger name=\"kafkaLogger\" level=\"trace\" includeLocation=\"false\"> <AppenderRef ref=\"Failover\"/> </AsyncLogger>
注意事项: 此类异步队列是Disruptor队列默认大小是4096
3) 使用 JVM参数
示例:
#启动参数方式 -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector #代码方式 System.setProperty(\"Log4jContextSelector\", \"org.apache.logging.log4j.core.async.AsyncLoggerContextSelector\");
注意事项: 此类异步是全量异步,log4j配置文件里所有logger都自动异步,使用异步队列为Disruptor,队列默认大小4096
小提示
① Disruptor队列性能远胜于BlockingQueue,这也是log4j2性能提升的重要原因之一
② 如果启用了全量异步,又使用了<AsyncLogger>会如何?
- log4j2会新建两个Disruptor队列,<AsyncLogger>之流使用一个,其他的使用另外一个,所以建议将可能发生阻塞的logger归类使用一个Disruptor,毕竟是队列,一个阻塞了其他的得乖乖等着
③ 如果默认队列长度不足咋办?
#第一步:加大两个Disruptor队列的长度 -DAsyncLogger.RingBufferSize=262144 -DAsyncLoggerConfig.RingBufferSize=262144 #第二步:设置队列满了时的处理策略:丢弃,否则默认blocking,异步就与同步无异了 -Dlog4j2.AsyncQueueFullPolicy=Discard
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
做猪小侠源码的代理,提供一站式服务
如果你不懂得搭建网站或者服务器,小程序,源码之类的怎么办? 第一通过本站学习各种互联网的技术 第二就是联系客服,我帮帮你搭建(当然要收取部分的费用) 第三成为我们的代理,我们提供整套的服务。