在第二篇里面我们实现了一个非常轻量级的异步刷盘的AsyncFlushRollingFileAppender,但是功能非常有限,在logback里面已经提供了一个现成的异步缓冲刷盘的Appender AsyncAppender,这个Appender其实就是一个包装类,典型的装饰模式的应用。其继承的类图如下
image.png
核心的功能都在AsyncAppenderBase里面进行了实现。
在AsyncAppenderBase里面定义了一个 BlockingQueue 用于缓冲消息事件,在start方法里面默认是new了个
blockingQueue = new ArrayBlockingQueue<E>(queueSize);
当然我们可以重新这个start方法,自定义一个无锁堵塞队列。比如很火的无锁框架 jctools包里面提供的数据结构。
定义了一个异步的线程 Worker,核心代码如下
class Worker extends Thread {
public void run() {
AsyncAppenderBase<E> parent = AsyncAppenderBase.this;
AppenderAttachableImpl<E> aai = parent.aai;
// loop while the parent is started
while (parent.isStarted()) {
try {
E e = parent.blockingQueue.take();
aai.appendLoopOnAppenders(e);
} catch (InterruptedException ie) {
break;
}
}
addInfo("Worker thread will flush remaining events before exiting. ");
for (E e : parent.blockingQueue) {
aai.appendLoopOnAppenders(e);
parent.blockingQueue.remove(e);
}
aai.detachAndStopAllAppenders();
}
}
核心代码就两句
//通过阻塞方法去拿事件
E e = parent.blockingQueue.take();
//拿到知道,调用装饰的Appender的doAppend方法,将事件写到outputStream里面去。
aai.appendLoopOnAppenders(e);
在这个Appender里面有几个参数
queueSize 队列大小,默认是256
discardingThreshold 丢弃的阈值,默认到80%才考虑是否丢弃消息,需要子类重写如下的方法,比如实现发现log的日志等级小于INFO直接的丢弃
protected boolean isDiscardable(E eventObject) {
return false;
}
neverBlock 是否堵塞 默认不堵塞,意思就是堵塞队列满了,消息就丢了,你可以设置成true,这样当队列满了之后,一直堵塞到消息丢进去为止。
进行如下的配置
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myapp.log</file>
<encoder>
<pattern>%logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<root level="DEBUG">
<appender-ref ref="ASYNC" />
</root>
</configuration>
这样写日志的时候会先写到ASYNC这个内存Appender,然后ASYNC再调用FILE这个Appender异步的写到outputStream了,上面的这个异步写日志貌似很完美了,但是由于我们使用的数据结构ArrayBlockingQueue是一个有锁的数据结构,性能上面还是有提升的空间的,后面我们会实现一个无锁的队列来实现日志的异步刷盘。
网友评论