用于低延迟日志的异步日志记录器

异步日志记录可以通过在单独的线程中执行I/O操作来提高应用程序的性能。

Log4j 2在这方面做了许多改进。

异步记录器是Log4j 2中新增的功能。它们的目的是尽快从对Logger.log的调用返回到应用程序。

您可以选择让所有的logger都是异步的,或者混合使用同步和异步的logger。将所有记录器设置为异步将提供最佳性能,而混合将为您提供更多灵活性。

LMAX破坏者技术。异步记录器在内部使用无锁的线程间通信库Disruptor,而不是队列,从而获得更高的吞吐量和更低的延迟。

作为异步日志记录器工作的一部分,异步追加器已被增强为在批处理结束时(当队列为空时)刷新到磁盘。这将产生与配置“immediateFlush=true”相同的结果,即所有接收到的日志事件始终在磁盘上可用,但效率更高,因为它不需要在每个日志事件上都访问磁盘。(Async Appenders在内部使用ArrayBlockingQueue,不需要在类路径中使用disruptor jar。)

权衡

尽管异步日志记录可以提供显著的性能优势,但在某些情况下,您可能希望选择同步日志记录。本节描述异步日志记录的一些优缺点。

好处

更高的峰值吞吐量。使用异步记录器,应用程序可以以同步记录器的6 - 68倍的速率记录消息。

这对于偶尔需要记录突发消息的应用程序来说特别有趣。异步日志记录可以通过缩短记录下一条消息之前的等待时间来帮助防止或抑制延迟峰值。如果队列大小配置得足够大,可以处理突发事件,那么异步日志记录将有助于防止应用程序在活动突然增加时(尽可能多地)落后。

更低的日志响应时间延迟。响应时间延迟是在给定工作负载下调用Logger.log返回所花费的时间。

异步记录器的延迟始终低于同步记录器,甚至低于基于队列的异步追加器。

缺点

错误处理。如果在日志记录过程中发生问题并抛出异常,异步日志记录器或追加程序就不太容易向应用程序发出此问题的信号。

这可以通过配置ExceptionHandler部分缓解,但这可能仍然不能涵盖所有情况。由于这个原因,如果日志记录是业务逻辑的一部分,例如,如果您使用Log4j作为审计日志记录框架,我们建议同步记录这些审计消息。(注意,您仍然可以将它们组合起来,除了使用同步日志记录审计跟踪之外,还可以使用异步日志记录调试/跟踪日志。)

在某些罕见的情况下,必须小心处理可变消息。大多数时候你不需要担心这个。Log4将确保日志消息(如logger.debug(“My object is {}”, myObject))在调用logger.debug()时使用myObject参数的状态。即使后来修改了myObject,日志消息也不会改变。异步记录可变对象是安全的,因为Log4j内置的大多数Message实现都会获取参数的快照。但是也有一些例外:MapMessage和StructuredDataMessage在设计上是可变的:在创建消息对象之后,可以向这些消息添加字段。这些消息在被异步记录器或异步追加器记录之后不应该被修改;您可能会在结果日志输出中看到修改,也可能不会看到。类似地,自定义消息实现在设计时应该考虑异步使用,并在构造时获取其参数的快照,或者记录其线程安全特征。

如果您的应用程序运行在CPU资源稀缺的环境中,例如只有一个单核CPU的机器,那么启动另一个线程不太可能提供更好的性能。

如果应用程序记录消息的持续速度快于底层appender的最大持续吞吐量,则队列将被填满,应用程序最终将以最慢的appender的速度进行日志记录。

如果发生这种情况,请考虑选择更快的appender,或者减少日志记录。如果这两种方法都不可行,那么通过同步记录可以获得更好的吞吐量和更少的延迟峰值。

使所有记录器异步

Log4j-2.9及更高版本需要类路径上的disruptor-3.3.4.jar或更高版本。在Log4j-2.9之前,需要disruptor-3.0.0.jar或更高版本。

这是最简单的配置,并提供最好的性能。要使所有记录器异步,请将中断jar添加到类路径中,并设置系统属性 log4j2.contextSelectororg.apache.logging.log4j.core.async.AsyncLoggerContextSelectororg.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector

默认情况下,异步日志记录器不会将位置传递给I/O线程。如果您的布局或自定义过滤器之一需要位置信息,您需要在所有相关记录器(包括根记录器)的配置中设置“inclelocation =true”。

不需要位置的配置可能如下所示:

<?xml version="1.0" encoding="UTF-8"?>
 
<!-- Don't forget to set system property
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
or
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector
     to make all loggers asynchronous. -->
 
<Configuration status="WARN">
  <Appenders>
    <!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
    <RandomAccessFile name="RandomAccessFile" fileName="async.log" immediateFlush="false" append="false">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m %ex%n</Pattern>
      </PatternLayout>
    </RandomAccessFile>
  </Appenders>
  <Loggers>
    <Root level="info" includeLocation="false">
      <AppenderRef ref="RandomAccessFile"/>
    </Root>
  </Loggers>
</Configuration>

当使用AsyncLoggerContextSelector或BasicAsyncLoggerContextSelector使所有记录器异步时,请确保在配置中使用正常的 <root><logger> 元素。

上下文选择器将确保所有日志记录器都是异步的,使用的机制不同于配置 <asyncRoot><asyncLogger> 时发生的情况。

后一种元素用于混合异步和同步日志记录器。如果您同时使用这两种机制,您最终将拥有两个后台线程,其中应用程序将日志消息传递给线程A,线程A将消息传递给线程B,然后线程B最终将消息记录到磁盘上。这是可行的,但是中间会有一个不必要的步骤。

您可以使用一些系统属性来控制异步日志子系统的各个方面。其中一些可用于调优日志记录性能。

还可以通过创建一个名为log4j2.component.properties的文件并将该文件包含在应用程序的类路径中来指定以下属性。

注意,在Log4j 2.10.0中,系统属性被重命名为更一致的样式。所有旧的属性名仍然被支持,在这里有文档说明。

混合同步和异步记录器

Log4j-2.9及更高版本需要类路径上的disruptor-3.3.4.jar或更高版本。

在Log4j-2.9之前,需要disruptor-3.0.0.jar或更高版本。不需要将系统属性“Log4jContextSelector”设置为任何值。

同步和异步记录器可以在配置中组合使用。这为您提供了更大的灵活性,但代价是性能上的轻微损失(与使所有记录器都异步相比)。

使用 <asyncRoot><asyncLogger> 配置元素来指定需要异步的日志记录器。

一个配置只能包含一个根日志记录器( <root><asyncRoot> 元素),否则可以将异步和非异步日志记录器组合在一起。例如,包含 <asyncLogger> 元素的配置文件也可以包含用于同步日志记录器的 <root><logger> 元素。

默认情况下,异步日志记录器不会将位置传递给I/O线程。如果您的布局或自定义过滤器之一需要位置信息,您需要在所有相关记录器(包括根记录器)的配置中设置“inclelocation =true”。

混合异步日志记录器的配置可能是这样的:

<?xml version="1.0" encoding="UTF-8"?>
 
<!-- No need to set system property "log4j2.contextSelector" to any value
     when using <asyncLogger> or <asyncRoot>. -->
 
<Configuration status="WARN">
  <Appenders>
    <!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
    <RandomAccessFile name="RandomAccessFile" fileName="asyncWithLocation.log"
              immediateFlush="false" append="false">
      <PatternLayout>
        <Pattern>%d %p %class{1.} [%t] %location %m %ex%n</Pattern>
      </PatternLayout>
    </RandomAccessFile>
  </Appenders>
  <Loggers>
    <!-- pattern layout actually uses location, so we need to include it -->
    <AsyncLogger name="com.foo.Bar" level="trace" includeLocation="true">
      <AppenderRef ref="RandomAccessFile"/>
    </AsyncLogger>
    <Root level="info" includeLocation="true">
      <AppenderRef ref="RandomAccessFile"/>
    </Root>
  </Loggers>
</Configuration>

自定义WaitStrategy

上面提到的系统属性只允许从一组固定的预定义WaitStrategies中进行选择。

在某些情况下,您可能希望配置一个不在此列表中的自定义WaitStrategy。这可以通过在Log4j配置中使用AsyncWaitStrategyFactory元素来实现。

配置自定义WaitStrategy的配置如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
 
  <AsyncWaitStrategyFactory
      class="my.custom.AsyncWaitStrategyFactory" />
 
  <Appenders>
    <File name="MyFile" fileName="logs/app.log">
      <PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
    </File>
  </Appenders>
  <Loggers>
    <AsyncRoot level="info">
      <AppenderRef ref="MyFile"/>
    </AsyncRoot>
  </Loggers>
</Configuration>

自定义的实现,需要实现对应的接口:

public interface AsyncWaitStrategyFactory {
  /**
  * Returns a non-null implementation of the LMAX Disruptor's WaitStrategy interface.
  * This WaitStrategy will be used by Log4j Async Loggers and Async LoggerConfigs.
  *
  * @return the WaitStrategy instance to be used by Async Loggers and Async LoggerConfigs
  */
  WaitStrategy createWaitStrategy();
}

Location 位置,位置,位置……

如果其中一个布局配置了与位置相关的属性,如HTML locationInfo,或模式%C或$class、%F或%file、%l或%location、%l或%line、%M或%method之一,Log4j将获取堆栈的快照,并遍历堆栈跟踪以查找位置信息。

这是一个代价高昂的操作:对于同步记录器来说速度要慢1.3 - 5倍。

同步记录器在获取堆栈快照之前尽可能等待较长的时间。如果不需要指定位置,则永远不会快照。

然而,异步日志记录器需要在将日志消息传递给另一个线程之前做出这个决定;在此之后,位置信息将丢失。

对异步记录器来说,堆栈跟踪快照对性能的影响甚至更高:有位置记录比没有位置记录慢30-100倍。

因此,默认情况下,异步记录器和异步追加器不包含位置信息。

您可以通过指定inclelocation =”true”来覆盖日志记录器或异步appender配置中的默认行为。

chat

log4j2 的 async logger

Log4j2 是一个 Java 日志框架,它提供了灵活的配置和高性能的日志记录功能。其中,async loggerLog4j2 中的一个特性,它允许在日志记录时使用异步方式,以提高性能。下面是对 Log4j2 中的 async logger 的详细介绍:

1. 异步日志简介

在传统的同步日志系统中,当应用程序记录日志时,它会等待日志事件完全写入日志文件或其他目标后再继续执行。这可能会导致性能问题,特别是在高负载的生产环境中。

异步日志就是为了解决这个问题而设计的。异步日志系统允许应用程序在记录日志时继续执行,而不必等待日志事件完全写入。这样,应用程序的性能不会受到日志写入的影响。

2. Log4j2 的 Async Logger 特性

Log4j2 中,async logger 是通过使用异步日志 Appender 实现的。以下是一些关键特性:

2.1 配置 Async Logger

log4j2.xml 或其他配置文件中,可以配置一个异步 logger,例如:

<AsyncLogger name="com.example.MyClass" level="info" additivity="false">
    <AppenderRef ref="AsyncFile"/>
</AsyncLogger>

这里,AsyncFile 是一个异步文件 Appender 的引用。

2.2 异步 Appender

Log4j2 提供了一些异步 Appender,例如 AsyncFileAppender,用于将日志事件异步写入文件。你可以配置这些 Appender 来适应你的需求。

2.3 配置异步队列

Async Logger 使用一个异步队列来缓冲日志事件,然后在后台线程中处理这些事件。你可以配置队列的大小、事件丢弃策略等参数,以满足性能和可靠性需求。

2.4 性能优势

通过使用异步日志,应用程序可以在写入日志时继续执行,而不必等待 I/O 操作完成。这在高负载环境中可以显著提高性能。

3. 示例配置

下面是一个简单的 log4j2.xml 配置文件,演示了如何配置异步 logger:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <AsyncFile name="AsyncFile" fileName="logs/app.log" immediateFlush="false">
            <PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>
        </AsyncFile>
    </Appenders>
    <Loggers>
        <AsyncLogger name="com.example.MyClass" level="info" additivity="false">
            <AppenderRef ref="AsyncFile"/>
        </AsyncLogger>
        <Root level="error">
            <AppenderRef ref="AsyncFile"/>
        </Root>
    </Loggers>
</Configuration>

这个配置文件中定义了一个异步文件 Appender,将日志写入 logs/app.log 文件。然后配置了一个异步 logger,将特定包下的日志级别为 info 的日志记录到异步文件中。

总体而言,使用 Log4j2 的异步 logger 可以显著提高日志记录的性能,特别是在高并发和高负载的情况下。

log4j2 async logger 有哪些优缺点

Log4j2 的异步日志(async logger)在提高性能方面具有一些优点,但也有一些缺点。下面是一些主要的优缺点:

优点:

  1. 性能提升: 异步日志允许应用程序在记录日志时继续执行,而不必等待 I/O 操作完成。这可以显著提高应用程序的性能,特别是在高并发和高负载的情况下。

  2. 降低延迟: 异步日志的使用可以降低由于同步日志操作引起的延迟。应用程序不再被阻塞等待日志写入完成,从而提高了响应性。

  3. 并发处理: 异步日志系统使用后台线程来处理日志事件,允许并发处理多个事件。这有助于更有效地管理和处理大量日志事件。

  4. 配置灵活性: 异步 logger 具有灵活的配置选项,可以调整队列大小、事件丢弃策略等参数以适应不同的性能和可靠性需求。

缺点:

  1. 内存开销: 异步日志需要维护一个日志事件队列,这可能导致一些额外的内存开销。队列的大小和事件复杂性会影响内存使用情况。

  2. 配置复杂性: 对于初学者而言,配置异步 logger 可能会相对复杂。了解和调整异步队列、事件丢弃策略等参数需要一定的经验和了解。

  3. 日志丢失风险: 在极端的高负载情况下,异步日志系统可能会因为队列满或其他原因导致部分日志事件被丢弃。这可能会使一些日志消息无法被完全记录。

  4. 对于低负载应用不一定有益: 在低负载的情况下,异步日志可能会增加复杂性而带来较小的实际性能提升。因此,在一些轻量级的应用中,异步日志可能并不总是必要的。

总体而言,使用异步日志要根据具体的应用场景和需求来决定。

在高并发、高负载的环境中,异步日志通常是一个有益的优化手段,但在一些简单和低负载的场景中,可能并不是必须的。

参考资料

https://logging.apache.org/log4j/2.x/manual/async.html