CodeWalk

Log4j2异步日志之无锁架构Disruptor原理

作者:古法程序员 · 2026-05-30 12:55

请详细解释Log4j2的异步日志(Async Logger)的核心原理和使用配置。相比于Logback的AsyncAppender,Log4j2的异步日志基于LMAX Disruptor无锁队列,为什么性能更高?如何配置Log4j2异步日志?有哪些注意事项?

回答

古法程序员

Log4j2异步日志架构

Log4j2提供三种异步方式:

  1. AsyncAppender:基于ArrayBlockingQueue的有界队列
  2. Async Logger:基于LMAX Disruptor的无锁环形缓冲区
  3. 混合模式:部分Logger同步,部分Logger异步

核心原理:LMAX Disruptor

传统队列问题

  • ArrayBlockingQueue/LinkedBlockingQueue:锁竞争 + CAS + 伪共享

Disruptor优势

  1. 无锁:使用CAS+内存屏障替代锁,没有线程挂起/唤醒开销
  2. 预分配内存:环形缓冲区预先分配Event对象,避免GC
  3. 缓存行填充:通过@Contended/填充字节避免伪共享(False Sharing)
  4. 单线程消费者:同一个Appender的日志写磁盘是单线程,无锁竞争

性能数据(官方基准): | 配置 | 吞吐量(TPS) | GC压力 | |------|-------------|--------| | 同步 | ~100万 | 高 | | Logback AsyncAppender | ~200万 | 中 | | Log4j2 AsyncAppender | ~400万 | 低 | | Log4j2 Async Logger | ~1700万 | 接近零GC |

配置示例

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <File name="FILE" fileName="/var/log/app.log">
            <PatternLayout pattern="%d %p %c{1.} %X{traceId} - %m%n" />
        </File>
    </Appenders>
    
    <Loggers>
        <!-- Async Logger:所有Logger都异步 -->
        <AsyncRoot level="INFO">
            <AppenderRef ref="FILE" />
        </AsyncRoot>
        
        <!-- 特定Logger仍同步 -->
        <AsyncLogger name="com.example" level="DEBUG" additivity="false">
            <AppenderRef ref="FILE" />
        </AsyncLogger>
    </Loggers>
</Configuration>

系统属性

# Disruptor等待策略
log4j2.asyncLoggerWaitStrategy=Yield
# Disruptor环形缓冲区大小(必须2的幂)
log4j2.ringBufferSize=262144

注意事项

  1. 异步日志可能丢日志:应用崩溃时缓冲区中未刷盘的日志丢失(使用AsyncAppender配置immediateFlush=true缓解)
  2. 需要log4j-core + log4j-async + disruptor依赖
  3. MDC传递:异步线程自动传递MDC(Log4j2已封装)
  4. 上下文数据拷贝:Location信息(行号)获取有额外开销,酌情配置