Log4j2 中三种常见 File 类 Appender 对比与选择

打印 上一主题 下一主题

主题 562|帖子 562|积分 1686

在 Log4j2 中,若不考虑 Rolling(支持滚动和压缩)类文件 Appender,则包罗以下三种文件 Appender:FileAppender、RandomAccessFileAppender 和 MemoryMappedFileAppender。接下来将介绍这三种 Appender 的功能、优缺点以及在现实研发中应如何选择与使用。
一、三种 Appender 简介

1. FileAppender

FileAppender 是 Log4j2 中最基础的文件 Appender,它基于传统的 java.io.FileOutputStream 将日志写入文件,简单可靠。
2. RandomAccessFileAppender

RandomAccessFileAppender 基于 ByteBuffer 和 RandomAccessFile 实现,与标准的 FileAppender 类似,不过它始终使用由 bufferSize 参数指定大小的内存缓冲区。从官方文件介绍来看,与使用默认配置的 FileAppender 相比,性能可提高 20% 至 200%。然而,由于使用了内存缓冲区,增长了内存开销,同时也提高了日志丢失的风险,若程序瓦解,缓冲区中的日志将会丢失。
3. MemoryMappedFileAppender

MemoryMappedFileAppender 基于内存映射文件(Memory-Mapped File)实现,文件直接映射到内存中,将日志直接写入这块内存,并依赖操作系统的虚拟内存管理器持久化到存储装备,使得能够在不经过操作系统缓存的情况下直接将日志写入磁盘,淘汰了 I/O 开销,进而性能有明显提升。然而,该 Appender 内存占用较多,且依赖于底层操作系统和硬件支持,若程序瓦解,未持久化的日志大概丢失。
默认内存映射区域大小为 32M,可通过“regionLength”参数进行调整;该 Appender 无对应的 Rolling 类,无法支持日志滚动切换和备份。
二、功能对比

特性FileAppenderRandomAccessFileAppenderMemoryMappedFileAppender底层实现java.io.FileOutputStreamjava.io.RandomAccessFile内存映射文件(Memory-Mapped File)可靠性高较高(缓冲区未刷新时)略低(内存映射未刷新时)性能略低略高高平台兼容性高高略低,依赖操作系统和硬件支持内存占用较低中等较高无 GC(Garbage-free)模式支持支持支持文件 Rolling支持支持不支持日志输出次序性有序有序有序三、同步性能对比

1. 基准环境

(1)硬件:Windows 条记本,配置为 I5-1350P CPU、32G DDR5 5200 内存以及三星 MZVL4512HBLU-00BLL 512G SSD(次序写入速率为 2430MB/s)。
(2)软件:基于 JDK 1.8.171,使用 1.37 版 JMH 和 2.24.3 版 Log4j2。
(3)配置:三种 Appender 均采用默认配置(append 和 immediateFlush 都为 true),使用同步 Logger 进行压测。
(4)参考现实使用情况,输出长度为 100 个的固定字符串,同时,有 20% 的概率输出包罗 100 层堆栈的异常,日志结构为:%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %pid %t %C.%M:%L - %msg %ex{full} %n。
(5)压测三次,取结果均匀值。
2. 配置文件
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <Configuration name="log4j2AppenderTest" status="error">
  3.         <Properties>
  4.                 <Property name="log.pattern">
  5.                         %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %pid %t %C.%M:%L - %msg %ex{full} %n
  6.                 </Property>
  7.         </Properties>
  8.         <Appenders>
  9.                 <Console name="Console">
  10.                         <PatternLayout pattern="${log.pattern}"/>
  11.                 </Console>
  12.                 <File name="File"
  13.                         fileName="target/test-output/log4j2-file.log">
  14.                         <PatternLayout pattern="${log.pattern}"/>
  15.                 </File>
  16.                 <RandomAccessFile name="RandomAccessFile"
  17.                                                   fileName="target/test-output/log4j2-random-access-file.log" >
  18.                         <PatternLayout pattern="${log.pattern}"/>
  19.                 </RandomAccessFile>
  20.                 <MemoryMappedFile name="MemoryMappedFile"
  21.                                                   fileName="target/test-output/log4j2-memory-mapped-file.log" >
  22.                         <PatternLayout pattern="${log.pattern}"/>
  23.                 </MemoryMappedFile>
  24.         </Appenders>
  25.         <Loggers>
  26.                 <Root level="debug">
  27.                         <AppenderRef ref="Console" />
  28.                 </Root>
  29.                 <Logger name="FileLogger" level="debug" additivity="false">
  30.                         <AppenderRef ref="File" />
  31.                 </Logger>
  32.                 <Logger name="RandomAccessFileLogger" level="debug" additivity="false">
  33.                         <AppenderRef ref="RandomAccessFile" />
  34.                 </Logger>
  35.                 <Logger name="MemoryMappedFileLogger" level="debug" additivity="false">
  36.                         <AppenderRef ref="MemoryMappedFile" />
  37.                 </Logger>
  38.         </Loggers>
  39. </Configuration>
复制代码
3. 压测代码
  1. @State(Scope.Benchmark)
  2. public class Log4J2FileAppenderBenchmark {
  3.     static Logger fileLogger;
  4.     static Logger randomAccessLogger;
  5.     static Logger memoryMappedLogger;
  6.     static final Random RANDOM= new Random();
  7.     static final double OUTPUT_EXCEPTION_PROBABILITY = 0.2;
  8.     @Setup(Level.Trial)
  9.     public void setUp() throws Exception {
  10.         System.setProperty("log4j.configurationFile", "log4j2-appender.xml");
  11.         fileLogger = LogManager.getLogger("FileLogger");
  12.         randomAccessLogger = LogManager.getLogger("RandomAccessFileLogger");
  13.         memoryMappedLogger = LogManager.getLogger("MemoryMappedFileLogger");
  14.     }
  15.     @TearDown
  16.     public void tearDown() {
  17.         System.clearProperty("log4j.configurationFile");
  18.     }
  19.                 public void outputLog(Logger logger){
  20.         if (RANDOM.nextDouble() < OUTPUT_EXCEPTION_PROBABILITY) {
  21.             logger.debug(Const.MSG_HAVE_100_CHARS, Const.THROWABLE_HAVE_100_STACK);
  22.         } else {
  23.             logger.debug(Const.MSG_HAVE_100_CHARS);
  24.         }
  25.     }
  26.     @BenchmarkMode(Mode.Throughput)
  27.     @OutputTimeUnit(TimeUnit.MILLISECONDS)
  28.     @Benchmark
  29.     public void syncFileLogger() {
  30.         outputLog(fileLogger);
  31.     }
  32.     @BenchmarkMode(Mode.Throughput)
  33.     @OutputTimeUnit(TimeUnit.MILLISECONDS)
  34.     @Benchmark
  35.     public void syncRandomAccessLogger() {
  36.         outputLog(randomAccessLogger);
  37.     }
  38.     @BenchmarkMode(Mode.Throughput)
  39.     @OutputTimeUnit(TimeUnit.MILLISECONDS)
  40.     @Benchmark
  41.     public void syncMemoryMappedAppendLogger() {
  42.         outputLog(memoryMappedLogger);
  43.     }
  44. }
复制代码
4. JMH 参数

JMH 执行的参数为:-jvmArgs "-Xmx2g -Xms2g" -f 2 -t 4 -w 10 -wi 2 -r 30 -i 2 -to 300 -prof gc -rf json,即设置 JVM 参数为 -Xmx2g -Xms2g(堆内存最大和最小均为 2GB),使用 2 个 fork(-f 2),每个 fork 使用 4 个线程(-t 4),预热阶段每次运行 10 秒(-w 10),预热迭代 2 次(-wi 2),正式测试每次运行 30 秒(-r 30),正式测试迭代 2 次(-i 2),超时时间为 300 秒(-to 300),启用 GC 性能分析(-prof gc),并将测试结果输出为 JSON 格式(-rf json)。
5. 压测结果

Appender 类型均匀吞吐量内存分配速率(MB/sec)垃圾回收次数FileAppender42.5 ops/ms1328.1 MB/sec235RandomAccessFileAppender46.6 ops/ms1498.4 MB/sec266MemoryMappedFileAppender90.9 ops/ms3162.5 MB/sec5616. 压测结论

基于上述基准环境及压测结果,可得出结论:
(1)MemoryMappedFileAppender 性能比 RandomAccessFileAppender 和 FileAppender 高约一倍。
(2)RandomAccessFileAppender 性能稍优于 FileAppender,吞吐量高 8.8%。
此外,从其余 CASE 的压测来看:
(1)日志字段越少、单条日志体积越小,MemoryMappedFileAppender 性能上风越明显,最多可高于 FileAppender 10 倍。
(2)在相同基准环境下,增长压测线程数,三类 Appender 性能均有所提升,RandomAccessFileAppender 和 MemoryMappedFileAppender 提升更为显著。将压测线程增长到 64 时,FileAppender、RandomAccessFileAppender 和 MemoryMappedFileAppender 吞吐量分别为 46.6、56.3 和 138.4 ops/ms,FileAppender 略有提升,相比之下,RandomAccessFileAppender 和 MemoryMappedFileAppender 领先 FileAppender 的上风分别扩大 20.8% 和 197%。
(3)使用 JDK21 对 MemoryMappedFileAppender 进行压测时,会抛出 IllegalAccessException 异常。缘故原由是:自 Java9 起引入了模块系统(JPMS),限定了外部模块访问内部 API 的能力。解决方法:在运行时为 JVM 添加参数--add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/jdk.internal.ref=ALL-UNNAMED。
(4)在相同基准环境下,发现使用 JDK21 压测,三种 Appender 的性能均有较大幅度下降,缘故原由不明,已向社区反馈
五、使用建议

1. 如何选择

(1)通例情况下,推荐优先使用 FileAppender。其性能良好,每毫秒可达 42.5ops(现实生产环境中部署服务器大部分使用 HD 硬盘,其 IO 性能远不如 SSD,因此吞吐量大概会下降),能满足绝大部分业务需求,且简单可靠,支持 Rolling(文件切分、滚动和备份);RandomAccessFileAppender 性能仅比 FileAppender 略高,但存在丢失日志的风险,不太推荐使用;现实使用中,如果采用 FileAppender 或 RandomAccessFileAppender,建议使用其 Rolling 类,即 RollingFileAppender 和 RollingRandomAccessFileAppender,以满足文件切分、轮转和压缩等必备需求。
(2)若对性能要求较高,且能接受大概发生的较多日志丢失,与其考虑采用 MemoryMappedFileAppender,不如考虑使用异步日志(AsyncLogger)加消息队列 Appender 的组合方式,后者性能更好,且内存占用小,与系统平台耦合低。
(3)从压测结果来看,不同配置参数和使用环境,性能会存在一定颠簸。【特别建议】:根据业务现实使用场景、功能需求以及日志配置等情况,提前做好性能测试与分析,以便选择合适的 Appender。
2. 配置建议

现实生产环境中,【禁止】将 append 和 immediateFlush 参数设置为 false。由于前者会使原日志被覆盖,后者大概导致无法查察实时日志以及日志丢失。
六、参考文章

(1)log4j2.x
(2)log4j2 2.12.x

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张裕

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表