接上文:ELK日志代理上传:Springboot-Kafka启动问题
现状:使用一段时间后,仍然出现Kafka连接不上时,上报日志堵塞主线程60s的情况。
原因:AsyncAppender虽然是异步的,但是使用的是BlockingQueue队列,默认设置队列size:256,在队列满了的情况,ERROR级别的日志不会丢弃,等待到队列有空闲进行处理。
参考以下代码:队列到达阈值时(默认为队列size的一半),调用AsyncAppender类的isDiscardable
分析:在高并发情况下,特别是微服务架构,如果某个服务down,在请求过程中可能产生大量Error日志,此时队列很快被消耗殆尽,产生的堵塞对主流程的影响是致命的。kafka连接异常时,日志基本可以确定为丢失,当然在队列中多等待60s是有可能成功,但结合性能考虑,允许丢失这部分日志。
解决方式:很粗暴,重写AsynAppender,异步阻塞队列到达阈值时,抛弃所有级别日志。同时增加BlockingQueue队列size,尽可能保留多的日志。
不多说,上代码:
/**
* 自定义异步日志附加器,用于包装KafkaAppender
* @see ch.qos.logback.classic.AsyncAppender
*
* @author linxiaofa/pig
* @date 2019-07-17 14:20:30
*/
public class KafkaAsyncAppender extends AsyncAppenderBase<ILoggingEvent> {
private boolean includeCallerData = false;
/**
* 当队列缓冲超过丢弃保留的阈值时,TRACE, DEBUG or INFO级别的日志将被丢弃
* 当队列缓冲达到最大,丢弃所有日志
* @param event 日志事件
* @return true-丢弃日志,false-继续上报日志
*/
@Override
protected boolean isDiscardable(ILoggingEvent event) {
Level level = event.getLevel();
if (level.toInt() <= Level.INFO_INT) {
return true;
}
return this.getRemainingCapacity() == 0;
}
@Override
protected void preprocess(ILoggingEvent eventObject) {
eventObject.prepareForDeferredProcessing();
if (includeCallerData) {
eventObject.getCallerData();
}
}
public boolean isIncludeCallerData() {
return includeCallerData;
}
public void setIncludeCallerData(boolean includeCallerData) {
this.includeCallerData = includeCallerData;
}
}
<!--
使用自定义的KafkaAsyncAppender包装KafkaAppender,避免无法获取Kafka元数据时阻塞主程序.
参考:https://github.com/danielwegener/logback-kafka-appender#note-on-broker-outages
-->
<appender name="AsyncKafKaAppender" class="com.zuzuche.commons.KafkaAsyncAppender">
<!-- 队列长度9999 -->
<queueSize>9999</queueSize>
<appender-ref ref="KafkaAppender" />
</appender>