Log4J2的异步日志记录器在高并发下填满了LMAX disruptor的环形缓冲区

5
我在基于Java Play框架的应用程序中使用Log4J2 AsyncLogger。在高并发情况下(每个服务器约3000个用户),环形缓冲区的剩余大小很快就会降至零,并且我的请求开始失败。我的环形缓冲区大小为1048576(512 *2048)。应用程序线程数为8(如Play官方文档中所述,每个核心1个线程)。
我的问题是:
1> 如何增加消费者将日志写入文件的速度? 2> 我可以显式地使用多个消费者来写入我的日志文件吗?如果可以,在哪里指定这些消费者?
我已经包含了我的log4j2.xml文件。非常感谢任何帮助 :)
我尝试过的事情:
1> 增加环形缓冲区大小(始终是2的幂)。但最终会出现同样的问题,而且使用太多内存似乎不太优雅。这个选项在生产中将被否决。我错过了什么吗? 2> 尝试了不同的等待策略,但没有运气。
<?xml version="1.0" encoding="UTF-8"?>

<Configuration>
<!--  status="trace" attribute for Configuration tag prints in logs [ Starting AsyncLoggerConfig disruptor for this configuration with ringbufferSize=262144] -->
<!-- default log file names in case it fails to read it from property file -->
<Properties>
    <Property name="FILE_NAME">/opt/lol/logs/meh.log</Property>
    <Property name="FILE_PATTERN">/opt/lol/logs/meh_%d{yyyy-MM-dd_HH}.log</Property>
</Properties>

<Appenders>
    <RollingRandomAccessFile name="ASYNCFILE" fileName="${sys:FILE_NAME}" filePattern="${sys:FILE_PATTERN}">
        <PatternLayout pattern="[meh:%d{yyyy-MM-dd HH:mm:ss},%d{SSS}] %-5level[%thread][%C{1}:%L] %msg%n"/>
        <Policies>
            <TimeBasedTriggeringPolicy />
        </Policies>
    </RollingRandomAccessFile>

    <Console name="CONSOLE" target="SYSTEM_OUT">
        <PatternLayout pattern="[meh:%d{yyyy-MM-dd HH:mm:ss},%d{SSS}] %-5level[%thread][%C{1}:%L] %msg%n"/>
    </Console>
</Appenders>

<Loggers>
    <logger name="akka" level="INFO" />
    <AsyncRoot level="INFO" includeLocation="true">
       <!-- <AppenderRef ref="CONSOLE"/>-->
        <AppenderRef ref="ASYNCFILE"/>
    </AsyncRoot>
</Loggers>
</Configuration>

我希望每个盒子的并发数至少达到4000(与没有记录器时相同)。但是我卡在了大约2500个左右。


你能告诉我如何知道环形缓冲区的剩余大小是否为零吗?我刚刚配置了异步模式的log4j,但不知道环形缓冲区是如何使用的。 - timesking
1个回答

7
  1. 通过不使用location:replace,将[%C{1}:%L]替换为简单的%c,可以提高速度。Log4j 2的性能文档显示,记录位置会慢100倍。
  2. 多个消费者不会加速日志记录。硬盘仍然是单一的最终消费者,如果多个线程同时尝试写入(使用锁定来防止数据损坏),实际上会更慢。这就是Disruptor设计的原因。

谢谢Remko!目前由于开发限制,我们需要保留位置。如果我们按照您建议的路线走,那么我们需要更改应用程序中的每个日志消息,以便它还打印出所在类的名称。现在,有没有其他方法来提高性能?也许不是100倍,但仍然有显著的改进吗? - user1676426
1
不,你不应该改变你的代码。如何在你的代码中创建一个Logger?如果你按照“标准”的方式创建它们:private static Logger logger = LogManager.getLogger(); 那么每个logger的名称都是声明logger的完全限定类名。因此,%c(小写)将显示执行日志记录的类。通常,日志消息足够独特,以至于您可以从日志消息中确定哪个日志记录语句在您的代码中生成了该消息,所以我从来没有需要知道位置。(目前Log4j正在为_每个单独的日志消息_创建堆栈跟踪...) - Remko Popma
1
非常感谢!这确实提高了性能。我还注意到使用内存映射文件可以极大地提高性能,但写入磁盘需要相当长的时间。此外,内存映射文件无法被追加。内存映射文件最终是一致的吗?如果我在生产中使用它们,它们是否保证其内容最终会被写入磁盘?在这种情况下,我们可以考虑在系统中使用它们。 - user1676426
1
是的,使用内存映射文件时,操作系统负责将内存刷新到磁盘,因此即使Java进程崩溃,日志也会被写入磁盘。但是,如果您记录的内容太多,导致物理内存已满,那么您的计算机可能会在多个分钟内冻结,而操作系统则会加班工作以将内存内容写入磁盘。(我曾经遇到过这种情况,甚至无法登录机器10分钟。一旦内存被写入磁盘,一切都恢复正常...)因此,需要注意权衡利弊。 - Remko Popma
@RemkoPopma:在log4j2中,我们有多个生产者向环形缓冲区写入日志事件,而单个消费者则将它们拉取并写入文件。在这种情况下,日志事件在写入磁盘时如何按时间顺序排列? - user1676426
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接