logback性能优化及源码分析
来源:互联网 发布:指针可以当数组用吗 编辑:程序博客网 时间:2024/05/16 23:57
logback性能测试
logback版本:1.0.13
下图是logback在实际并发环境中的表现,200并发请求,大量blocked:
移除日志输出做对比:
查看thread dump 可以看到blocked是由logback导致。
logback源码分析
logback文件输出类是RollingFileAppender,打印日志的方法是subAppend,rollover是关闭文件切换日志文件。
/** * Implemented by delegating most of the rollover work to a rolling policy. */ public void rollover() { synchronized (lock) { // Note: This method needs to be synchronized because it needs // exclusive // access while it closes and then re-opens the target file. // // make sure to close the hereto active log file! Renaming under // windows // does not work for open files. this.closeOutputStream(); try { super.getRollingPolicy().rollover(); } catch (RolloverFailure rf) { addWarn("RolloverFailure occurred. Deferring roll-over."); // we failed to roll-over, let us not truncate and risk data // loss this.append = true; } try { // update the currentlyActiveFile // http://jira.qos.ch/browse/LBCORE-90 currentlyActiveFile = new File(super.getRollingPolicy().getActiveFileName()); // This will also close the file. This is OK since multiple // close operations are safe. this.openFile(super.getRollingPolicy().getActiveFileName()); } catch (IOException e) { addError("setFile(" + fileName + ", false) call failed.", e); } } } /** * This method differentiates RollingFileAppender from its super class. */ @Override protected void subAppend(E event) { // The roll-over check must precede actual writing. This is the // only correct behavior for time driven triggers. // We need to synchronize on triggeringPolicy so that only one rollover // occurs at a time synchronized (triggeringPolicy) { if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) { rollover(); } } super.subAppend(event); }
再看super.subAppend,代码在父类OutputStreamAppender中:
protected void subAppend(E event) { if (!isStarted()) { return; } try { // this step avoids LBCLASSIC-139 if (event instanceof DeferredProcessingAware) { ((DeferredProcessingAware) event).prepareForDeferredProcessing(); } // the synchronization prevents the OutputStream from being closed while we // are writing. It also prevents multiple threads from entering the same // converter. Converters assume that they are in a synchronized block. synchronized (lock) { writeOut(event); } } catch (IOException ioe) { // as soon as an exception occurs, move to non-started state // and add a single ErrorStatus to the SM. this.started = false; addStatus(new ErrorStatus("IO failure in appender", this, ioe)); } }
跟关闭文件使用同一个锁,保证文件关闭切换不会跟写文件冲突。
结论
使用logback时,每个对象间的锁是独立的,并且必须保留。
优化方案
- 在源码中做改动
- 给logback打补丁,在日志输出上做改动
实际解决方案
使用第一种方案会导致logback升级后代码也要随机更改,并且性能提升不会太大,随着日志量的增加,性能下降会愈加明显。因此本人使用第二种方案。
- 文件只输出error级别
- 业务日志采用kafka+elasticsearch
上代码只输出error:
public class ErrorRollingFileAppender<E> extends RollingFileAppender<E> { File currentlyActiveFile; public void start() { currentlyActiveFile = new File(getFile()); super.start(); } /** * Implemented by delegating most of the rollover work to a rolling policy. */ public void rollover() { synchronized (lock) { // Note: This method needs to be synchronized because it needs // exclusive // access while it closes and then re-opens the target file. // // make sure to close the hereto active log file! Renaming under // windows // does not work for open files. this.closeOutputStream(); try { super.getRollingPolicy().rollover(); } catch (RolloverFailure rf) { addWarn("RolloverFailure occurred. Deferring roll-over."); // we failed to roll-over, let us not truncate and risk data // loss this.append = true; } try { // update the currentlyActiveFile // http://jira.qos.ch/browse/LBCORE-90 currentlyActiveFile = new File(super.getRollingPolicy().getActiveFileName()); // This will also close the file. This is OK since multiple // close operations are safe. this.openFile(super.getRollingPolicy().getActiveFileName()); } catch (IOException e) { addError("setFile(" + fileName + ", false) call failed.", e); } } } /** * This method differentiates RollingFileAppender from its super class. */ @Override protected void subAppend(E event) { if(event instanceof LoggingEvent){ LoggingEvent tempLoggingEvent = (LoggingEvent) event; if(!tempLoggingEvent.getLevel().toString().equals(Level.ERROR.toString())) return; } // The roll-over check must precede actual writing. This is the // only correct behavior for time driven triggers. // We need to synchronize on triggeringPolicy so that only one rollover // occurs at a time// synchronized (super.getTriggeringPolicy()) {// if (super.getTriggeringPolicy().isTriggeringEvent(currentlyActiveFile, event)) {// rollover();// }// } super.subAppend(event); }}
对应配置
<appender name="logfile" class="ErrorRollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>xxx-%d{yyyy-MM-dd}.log</FileNamePattern> </rollingPolicy> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>[%d{HH:mm:ss:SSS}][%5p]- %m%n</pattern> <pattern>[%d{HH:mm:ss:SSS}][%5p][%c:%L] %m%n</pattern> </layout> </appender>
日志使用卡夫卡发送
public class UnblockedKafkaAppender extends UnsynchronizedAppenderBase<ILoggingEvent>{ @SuppressWarnings("rawtypes") @Override protected void append(ILoggingEvent eventObject) { //日志发送 } @SuppressWarnings("static-access") @Override public void start() { super.start(); //启动配置 } @Override public void stop() { super.stop(); }}
logback.xml配置
<appender name="kafka" class="UnblockedKafkaAppender"><!-- 文件输出配置 --><root level="info"> <appender-ref ref="logfile"/> <appender-ref ref="kafka"/> </root>
注意事项
appender 可以继承AppenderBase 但是AppenderBase也是有锁,发kafka不需要加锁,因此使用UnsynchronizedAppenderBase。
源码对比
AppenderBase :
public synchronized void doAppend(E eventObject) { // WARNING: The guard check MUST be the first statement in the // doAppend() method. // prevent re-entry. if (guard) { return; } try { guard = true; if (!this.started) { if (statusRepeatCount++ < ALLOWED_REPEATS) { addStatus(new WarnStatus( "Attempted to append to non started appender [" + name + "].", this)); } return; } if (getFilterChainDecision(eventObject) == FilterReply.DENY) { return; } // ok, we now invoke derived class' implementation of append this.append(eventObject); } catch (Exception e) { if (exceptionCount++ < ALLOWED_REPEATS) { addError("Appender [" + name + "] failed to append.", e); } } finally { guard = false; } }
UnsynchronizedAppenderBase
public void doAppend(E eventObject) { // WARNING: The guard check MUST be the first statement in the // doAppend() method. // prevent re-entry. if (Boolean.TRUE.equals(guard.get())) { return; } try { guard.set(Boolean.TRUE); if (!this.started) { if (statusRepeatCount++ < ALLOWED_REPEATS) { addStatus(new WarnStatus( "Attempted to append to non started appender [" + name + "].", this)); } return; } if (getFilterChainDecision(eventObject) == FilterReply.DENY) { return; } // ok, we now invoke derived class' implementation of append this.append(eventObject); } catch (Exception e) { if (exceptionCount++ < ALLOWED_REPEATS) { addError("Appender [" + name + "] failed to append.", e); } } finally { guard.set(Boolean.FALSE); } }
去锁的关键在guard上,guard的值必须线程间独立,否则会导致日志漏打。
0 0
- logback性能优化及源码分析
- Unit Test中Logback 性能测试及优化
- logback源码分析
- UE3 性能、分析及优化
- UE3 性能、分析及优化
- UE3 性能分析及优化
- Logback及Log4J性能测试
- 网站性能分析及优化方法
- linux系统性能优化及瓶颈分析
- linux系统性能优化及瓶颈分析
- Android界面性能分析及优化
- http请求过程及性能优化分析
- 【MySQL性能优化】MySQL查询优化器源码分析
- 性能优化十八之多线程优化-AsyncTask源码分析
- 源码分析slf4j和logback的关系
- JDK源码分析——性能优化实例
- java中Arraylist源码分析扩容过程和性能优化
- 日志框架分析及logback配置探究
- 多布局的优化
- Spring配置数据源的三种方式 (dbcp,c3p0,jndi).
- 重建二叉树
- python爬虫中的 urllib 模块 浅析
- 搭建DAO层和Service层代码
- logback性能优化及源码分析
- Android判断机器是否联网
- #97 Maximum Depth of Binary Tree
- Swift-- 计算文字尺寸
- HashSet实现原理-源码调试
- C3P0连接池详解及配置
- httpurlconnection_get_post
- Activiti explorer demo 部署无法加载完成问题
- ubuntu 14.04 上 jvpn 使用说明