很多时候,日志的记录都经常被大家忽略,因为对于性能要求不高的场景中,记录日志确实是可以忽略的。
但是在很多核心业务链路中,1-2ms的影响都很大,所以就会考虑对写日志环境进行性能优化。希望这个技术大家都能用的上,哈哈哈哈。
解决日志慢的问题,主要有两种方案,一个是异步,一个是降级。
因为日志写入操作通常是一个相对耗时的IO操作,如果每次日志记录都同步写入磁盘,可能会导致主线程阻塞,影响应用程序的性能。通过使用异步日志,主线程可以继续执行其他任务,而日志写入操作则由后台线程负责处理,提高了应用程序的响应性和吞吐量。
目前常用的日志框架,如Log4j2、Logback等都是支持异步日志的。都提供了AsyncAppender来实现异步的日志写入。
如logback中做如下配置即可实现异步日志:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>application.log</file>
<encoder>
<pattern>%logger{35} - %m%n</pattern>
</encoder>
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<neverBlock>true</neverBlock>
</appender>
<root level="DEBUG">
<appender-ref ref="ASYNC" />
</root>
</configuration>
其中queueSize为BlockingQueue的最大容量,默认大小为256,如果请求量大的话,可以适当调大这个queue的size。
discardingThreshold,日志丢弃阈值, 默认情况下,当队列还有20%容量,他将丢弃trace、debug和info级别的日志,只保留warn和error级别的日志。
neverBlock,在队列放满的情况下是否阻塞线程,默认是false,如果配置为true,当队列满了之后,后面阻塞的线程想要输出的消息就直接被丢弃,从而线程不会阻塞。适合在一些性能要求高,并且日志可降级的场景。
说到降级,日志降级一般用的也不多,但是在大促场景中,也是会用到的,一般是配合预案一起做,就是在极端情况下,通过开关来调节, 让日志直接不输出。或者只打印ERROR级别的日志。
或者做的更加精细一点,就是采样打印日志,比如采样1%进行日志输出,但是其实用的比较少,很多时候,采样和直接不打差别也不大。
关于日志降级,我之前做过一个工具,可以动态调整日志级别,后面我把主要思想简单说一下。
在很多时候,我们会在日志中打印一个链路追踪的信息,如traceid,但是因为这里是异步打印的,ThreadLocal中存储的traceid就无法获取了,就会导致日志中无法记录trace_id了。
所以需要特殊解决一下,方法也比较简单,就是自己定义一个Appender的实现类,在这各类里面进行设置这个trace_id:
MDC.put("traceId", threadPoolTaskData.toString());
或者使用 https://github.com/ofpay/logback-mdc-ttl 来解决。
✅有了InheritableThreadLocal为啥还需要TransmittableThreadLocal?