logback异步记录日志
异步记录日志
工作原理:
从上面的描述中可以看出,AsyncAppender并不处理日志,只是将日志缓冲到一个BlockingQueue里面去,并在内部创建一个工作线程从队列头部获取日志,之后将获取的日志循环记录到附加的其他appender上去,从而达到不阻塞主线程的效果。因此AsynAppender仅仅充当事件转发器,必须引用另一个appender来做事。
在使用AsyncAppender的时候,有些选项还是要注意的。由于使用了BlockingQueue来缓存日志,因此就会出现队列满的情况。正如上面原理中所说的,在这种情况下,AsyncAppender会做出一些处理:默认情况下,如果队列80%已满,AsyncAppender将丢弃TRACE、DEBUG和INFO级别的event,从这点就可以看出,该策略有一个惊人的对event丢失的代价性能的影响。另外其他的一些选项信息,也会对性能产生影响,下面列出常用的几个属性配置信息:
属性名 | 类型 | 描述 |
queueSize | int | BlockingQueue的最大容量,默认情况下,大小为256。 |
discardingThreshold | int | 默认情况下,当BlockingQueue还有20%容量,他将丢弃TRACE、DEBUG和INFO级别的event,只保留WARN和ERROR级别的event。为了保持所有的events,设置该值为0。 |
includeCallerData | boolean | 提取调用者数据的代价是相当昂贵的。为了提升性能,默认情况下,当event被加入到queue时,event关联的调用者数据不会被提取。默认情况下,只有"cheap"的数据,如线程名。 |
下面给出一个使用的配置示例:
- <appender name="FILE" class= "ch.qos.logback.core.rolling.RollingFileAppender">
- <!-- 按天来回滚,如果需要按小时来回滚,则设置为{yyyy-MM-dd_HH} -->
- <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
- <fileNamePattern>/opt/log/test.%d{yyyy-MM-dd}.log</fileNamePattern>
- <!-- 如果按天来回滚,则最大保存时间为1天,1天之前的都将被清理掉 -->
- <maxHistory>30</maxHistory>
- <!-- 日志输出格式 -->
- <layout class="ch.qos.logback.classic.PatternLayout">
- <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</Pattern>
- </layout>
- </appender>
- <!-- 异步输出 -->
- <appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender">
- <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
- <discardingThreshold >0</discardingThreshold>
- <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
- <queueSize>512</queueSize>
- <!-- 添加附加的appender,最多只能添加一个 -->
- <appender-ref ref ="FILE"/>
- </appender>
-
- <root level ="trace">
- <appender-ref ref ="ASYNC"/>
- </root>
转自:https://blog.****.net/chenjie2000/article/details/8902727
日志打印模型
- 同步日志模型
如上图,多个业务线程打印日志时候要等把内容写入磁盘后才会返回,所以打日志的rt就是写入磁盘的耗时。
- 异步日志模型
如上图多个业务线程打印日志时候是把打印任务放入内存队列后就直接返回了,而具体打印日志是有日志系统的一个日志线程去队列里面获取然后执行,可见这种打印rt就是写入内存队列的耗时。
关于异步日志的一些事
- 异步日志设置
对于logback来说异步日志里面的队列是一个有界ArrayBlockingQueue,其中queueSize是队列大小,taskLogAppender是引用的普通同步日志
discardingThreshold是一个阈值,通过下面代码看他的作用:
当队列的剩余容量小于这个阈值并且当前日志level TRACE, DEBUG or INFO ,则丢弃这些日志。
在压测时候代码配置如上,也就是配置了异步日志,但是还是出现了线程阻塞在打日志的地方了,经查看是阻塞到了日志队列ArrayBlockingQueue的put方法:
可知put方法在队列满时候会挂起当前线程。那么如何解那?
上面介绍了discardingThreshold,可知本文设置为0说明永远不会丢弃日志level TRACE, DEBUG or INFO的日志,只要discardingThreshold>0则当队列快满时候level TRACE, DEBUG or INFO的日志就会丢弃掉,这个貌似可以解决问题。但是如果打印的是warn级别的日志那?还是会在put的时候阻塞。
通过看代码发现最终写日志时候有个判断:
如果设置了neverBlock=true则写日志队列时候会调用ArrayBlockingQueue对的offer方法而不是put,而offer是非阻塞的:
可知如果队列满则直接返回,而不是被挂起当前线程。
所以配置异步appender时候如下:
总结
在高并发低延迟要求的系统里面不重要的日志可以设置为异步并且要注意设置队列满则丢弃策略,防止业务线程被挂起从而影响rt。
转自:http://www.importnew.com/27247.html