zoukankan      html  css  js  c++  java
  • log4j2异步日志解读(二)AsyncLogger

    前文已经讲了log4j2的AsyncAppender的实现【log4j2异步日志解读(一)AsyncAppender】,今天我们看看AsyncLogger的实现。

    看了这个图,应该很清楚AsyncLogger调用Disruptor,然后直接返回。至于高性能队列 这里已经展开讲了是如何实现的。

    AsyncLogger的调用流程

    我们来看看AsyncLogger的调用流程,log.info()首先会调用抽象类AbstractLogger,然后调用了Logger的logMessage。

    //Logger.java
        @Override
        public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,
                final Throwable t) {
            final Message msg = message == null ? new SimpleMessage(Strings.EMPTY) : message;
            final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy();
            strategy.log(this, getName(), fqcn, marker, level, msg, t);
        }

    strategy.log是调用了ReliabilityStrategy接口,日志事件传递到适当的appender的对象的接口,然后调用了LoggerConfig.log()方法,来创建有关记录消息的上下文信息。

    //LoggerConfig.java
        @PerformanceSensitive("allocation")
        public void log(final String loggerName, final String fqcn, final Marker marker, final Level level,
                final Message data, final Throwable t) {
            List<Property> props = null;
            if (!propertiesRequireLookup) {
                props = properties;
            } else {
                if (properties != null) {
                    props = new ArrayList<>(properties.size());
                    final LogEvent event = Log4jLogEvent.newBuilder()
                            .setMessage(data)
                            .setMarker(marker)
                            .setLevel(level)
                            .setLoggerName(loggerName)
                            .setLoggerFqcn(fqcn)
                            .setThrown(t)
                            .build();
                    for (int i = 0; i < properties.size(); i++) {
                        final Property prop = properties.get(i);
                        final String value = prop.isValueNeedsLookup() // since LOG4J2-1575
                                ? config.getStrSubstitutor().replace(event, prop.getValue()) //
                                : prop.getValue();
                        props.add(Property.createProperty(prop.getName(), value));
                    }
                }
            }
            final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
            try {
                log(logEvent, LoggerConfigPredicate.ALL);
            } finally {
                // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
                ReusableLogEventFactory.release(logEvent);
            }
        }

    接着我们来看AsyncLoggerConfig.logToAsyncDelegate()方法,首先会调用Disruptor,放入环形队列。如果环形队列阻塞,则执行等待策略。

    //AsyncLoggerConfig.java
        private void logToAsyncDelegate(LogEvent event) {
            if (!isFiltered(event)) {
                // Passes on the event to a separate thread that will call
                // asyncCallAppenders(LogEvent).
                populateLazilyInitializedFields(event);
                if (!delegate.tryEnqueue(event, this)) {
                    //如果获取Disruptor队列需要等待则执行等待策略,这里类似AsyncAppender等待策略
                    handleQueueFull(event);
                }
            }
        }
    
        private void handleQueueFull(final LogEvent event) {
            if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031
                // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock
                AsyncQueueFullMessageUtil.logWarningToStatusLogger();
                logToAsyncLoggerConfigsOnCurrentThread(event);
            } else {
                // otherwise, we leave it to the user preference
                final EventRoute eventRoute = delegate.getEventRoute(event.getLevel());
                // 1、DefaultAsyncQueueFullPolicy---等待队列,转为同步操作策略
                // 2、DiscardingAsyncQueueFullPolicy---按照日志等级抛弃日志策略
                eventRoute.logMessage(this, event);
            }
        }

    然后再来看看Disruptor写入 的过程。LogEvent是记录消息的上下文信息的接口,然后调用tryPublishEvent去获取环形队列的位置,然后发布数据到环形队列上。这一块具体可以看笔者前文Disruptor源码分析,这里就不展开讨论。

    //AsyncLoggerConfigDisruptor.java
        @Override
        public boolean tryEnqueue(final LogEvent event, final AsyncLoggerConfig asyncLoggerConfig) {
            final LogEvent logEvent = prepareEvent(event);
            return disruptor.getRingBuffer().tryPublishEvent(translator, logEvent, asyncLoggerConfig);
        }

    日志的消费过程,定义RingBufferLogEventHandler类实现Disruptor的SequenceReportingEventHandler的onEvent方法,从ringbuffer读取事件进行处理。最后会调用该logger绑定的默认appender输出。

    最后提供下笔者测试demo

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN" monitorInterval="30">
        <Appenders>
            <RollingRandomAccessFile name="applicationAppender" fileName="./log/application.log"
                                     filePattern="./log/$${date:yyyy-MM}/common-%d{yyyy-MM-dd}.log.gz"
                                     append="false">
                <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%p] - %l - %m%n"/>
                <Policies>
                    <TimeBasedTriggeringPolicy/>
                </Policies>
            </RollingRandomAccessFile>
    
            <Console name="CONSOLE" target="SYSTEM_OUT">
                <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%p] %t - %l - %m%n"/>
            </Console>
    
            <!-- AsyncAppender配置 -->
            <!--<Async name="asyncTest" blocking="true">-->
                <!--<AppenderRef ref="applicationAppender"/>-->
            <!--</Async>-->
    
        </Appenders>
    
        <Loggers>
            <!-- AsyncLogger配置 -->
            <AsyncLogger name="log4j2" >
                <AppenderRef ref="applicationAppender"/>
            </AsyncLogger>
    
            <Root level="info">
                <!--<AppenderRef ref="CONSOLE"/>-->
                <AppenderRef ref="applicationAppender"/>
            </Root>
    
            <!--<Logger name="log4j2" level="debug" additivity="false" >-->
                <!--<AppenderRef ref="CONSOLE"/>-->
                <!--<AppenderRef ref="applicationAppender"/>-->
            <!--</Logger>-->
    
        </Loggers>
    </Configuration>

    总结

    1、Log4j 2的异步记录日志在一定程度上提供更好的吞吐量,但是一旦队列已满,appender线程需要等待,这个时候就需要设置等待策略,AsyncAppender是依赖于消费者最序列最后的消费者,会持续等待。至于异步性能图可以看下官方提供的吞吐量比较图,差异很明显。

    2、因为AsyncAppender是采用Disruptor,通过环形队列无阻塞队列作为缓冲,多生产者多线程的竞争是通过CAS实现,无锁化实现,可以降低极端大的日志量时候的延迟尖峰,Disruptor 可是号称一个线程里每秒处理600万订单的高性能队列。

  • 相关阅读:
    Java.io.outputstream.PrintStream:打印流
    Codeforces 732F. Tourist Reform (Tarjan缩点)
    退役了
    POJ 3281 Dining (最大流)
    Light oj 1233
    Light oj 1125
    HDU 5521 Meeting (最短路)
    Light oj 1095
    Light oj 1044
    HDU 3549 Flow Problem (dinic模版 && isap模版)
  • 原文地址:https://www.cnblogs.com/lewis09/p/10004117.html
Copyright © 2011-2022 走看看