那个视频网站可以做gif,外链推广网站,百度网站是什么,电子商务网站建设与策划异步日志打印模型概述
在高并发、高流量并且响应时间要求比较小的系统中同步打印日志已经满足不了需求了#xff0c;这是因为打印日志本身是需要写磁盘的#xff0c;写磁盘的操作会暂时阻塞调用打印日志的业务线程#xff0c;这会造成调用线程的rt增加。
如图所示为同步日…异步日志打印模型概述
在高并发、高流量并且响应时间要求比较小的系统中同步打印日志已经满足不了需求了这是因为打印日志本身是需要写磁盘的写磁盘的操作会暂时阻塞调用打印日志的业务线程这会造成调用线程的rt增加。
如图所示为同步日志打印模型。 同步日志打印模型的缺点是将日志写入磁盘的操作是业务线程同步调用完成的那么是否可以让业务线程把要打印的日志任务放入一个队列后直接返回然后使用一个线程专门负责从队列中获取日志任务并将其写入磁盘呢?这样的话业务线程打印日志的耗时就仅仅是把日志任务放入队列的耗时了其实这就是logback提供的异步日志打印模型要做的事具体如图所示。 其实logback的异步日志模型是一个多生产者-单消费者模型其通过使用队列把同步日志打印转换为了异步业务线程只需要通过调用异步appender把日志任务放入日志队列而日志线程则负责使用同步的appender进行具体的日志打印。
日志打印线程只需要负责生产日志并将其放入队列而不需要关心消费线程何时把日志具体写入磁盘。
异步日志与具体实现
异步日志 AsyncAppender 继承自 AsyncAppenderBase,其中后者具体实现了异步日志模型的主要功能前者只是重写了其中的一些方法。
由该图可知logback 中的异步日志队列是一个阻塞队列其实就是有界阻塞队列ArrayBlockingQueue,其中 queueSize表示有界队列的元素个数默认为256个。
worker是个线程也就是异步日志打印模型中的单消费者线程。
aai 是一个 appender的装饰器里面存放同步日志的appender,其中 appenderCount 记录 aai里面附加的同步 appender 的个数。
neverBlock用来指示当日志队列满时是否阻塞打印日志的线程。
discardingThreshold 是一个阈值当日志队列里面的空闲元素个数小于该值时新来的某些级别的日志会被直接丢弃下面会具体讲。
首先我们来看何时创建日志队列以及何时 启动消费线程这需要看AsyncAppenderBase 的 start方法。该方法在解析完配置 AsyncAppenderBase 的 xml 的节点元素后被调用。 由以上代码可知logback使用的是有界队列 ArrayBlockingQueue,之所以使用有界队列是考虑内存溢出问题。
在高并发下写日志的QPS会很高如果设置为无界队列队列本身会占用很大的内存很可能会造成OOM。
这里消费日志队列的 worker线程被设置为守护线程这意味着当主线程运行结束并且当前没有用户线程时该worker线程会随着JVM的退出而终止而不管日志队列里面是否还有日志任务未被处理。
另外这里设置了线程的名称这是个很好的习惯因为在查找问题时会很有帮助根据线程名字就可以定位线程。
既然是有界队列那么肯定需要考虑队列满的问题是丢弃老的日志任务还是阻塞日志打印线程直到队列有空余元素呢?要回答这个问题我们需要看看具体进行日志打印的AsyncAppenderBase的append方法。 其中代码(5)调用了AsyncAppender重写的isDiscardable方法该方法的具体内容为 结合代码(5)和代码(7)可知如果当前日志的级别小于等于INFO_INT并且当前队列的剩余容量小于discardingThreshold则会直接丢弃这些日志任务。
下面看具体代码(6)中的put方法。 如果neverBlock被设置为false(默认为false)则会调用阻塞队列的put方法而put是阻塞的也就是说如果当前队列满则在调用put方法向队列放入一个元素时调用线程会被阻塞直到队列有空余空间。
这里可以看下put方法的实现。 这里有必要解释下代码(9),当日志队列满时put方法会调用await()方法阻塞当前线程而如果其他线程中断了该线程那么该线程会抛出InterruptedException异常并且当前的日志任务就会被丢弃。
在logback-classic的1.2.3版本中则添加了不对中断进行响应的方法。 如果当前日志打印线程在调用blockingQueue.put时被其他线程中断则只是记录中断标志然后继续循环调用blockingQueue.put,尝试把日志任务放入日志队列。
新版本的这个实现通过使用循环保证了即使当前线程被中断日志任务最终也会被放入日志队列。
如果neverBlock被设置为true则会调用阻塞队列的offer方法而该方法是非阻塞的所以如果当前队列满则会直接返回也就是丢弃当前日志任务。这里回顾下offer方法的实现。 最后来看addAppender方法都做了什么。 由如上代码可知一个异步appender只能绑定一个同步appender。这个appender会被放到AppenderAttachableImpl的appenderList列表里面。
到这里我们已经分析完了日志生产线程把日志任务放入日志队列的实现下面一起来看消费线程是如何从队列里面消费日志任务并将其写入磁盘的。
由于消费线程是一个线程所以就从worker的run方法开始。