Java日志性能

在任何系统中,日志都是非常重要的组成部分,它是反映系统运行情况的重要依据,也是排查问题时的必要线索。绝大多数人都认可日志的重要性,但是又有多少人仔细想过该怎么打日志,日志对性能的影响究竟有多大呢?今天就让我们来聊聊Java日志性能那些事。

说到Java日志,大家肯定都会说要选择合理的日志级别、合理控制日志内容,但是这仅是万里长征第一步……哪怕一些DEBUG级别的日志在生产环境中不会输出到文件中,也可能带来不小的开销。我们撇开判断和方法调用的开销,在Log4J 2.x的性能文档中有这样一组对比:

 logger.debug("Entry number: " + i + " is " +  String.valueOf(entry[i]));
 logger.debug("Entry number: {} is {}", i, entry[i]);

上面两条语句在日志输出上的效果是一样的,但是在关闭DEBUG日志时,它们的开销就不一样了,主要的影响在于字符串转换和字符串拼接上,无论是否生效,前者都会将变量转换为字符串并进行拼接,而后者则只会在需要时执行这些操作。Log4J官方的测试结论是两者在性能上能相差两个数量级。试想一下,如果某个对象的toString()方法里用了ToStringBuilder来反射输出几十个属性时,这时能省下多少资源。

因此,某些仍在使用Log4J 1.x或Apache Commons Logging(它们不支持{}模板的写法)的公司都会有相应的编码规范,要求在一定级别的日志(比如DEBUGINFO)输出前增加判断:

if (logger.isDebugEnabled()) {
    logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}

除了日志级别和日志消息,通常在日志中还会包含一些其他信息,比如日期、线程名、类信息、MDC变量等等,根据Takipi的测试,如果在日志中加入class,性能会急剧下降,比起LogBack的默认配置,吞吐量的降幅在6成左右。如果一定要打印类信息,可以考虑用类名来命名Logger

在分布式系统中,一个请求可能会经过多个不同的子系统,这时最好生成一个UUID附在请求中,每个子系统在打印日志时都将该UUID放在MDC里,便于后续查询相关的日志。《The Ultimate Guide: 5 Methods For Debugging Production Servers At Scale》一文中就如何在生产环境中进行调试给出了不少建议,当中好几条是关于日志的,这就是其中之一。另一条建议是记录下所有未被捕获的日志,其实抛出异常有开销,记录异常同样会带来一定的开销,主要原因是Throwable类的fillInStackTrace方法默认是同步的:

public synchronized native Throwable fillInStackTrace();

一般使用logger.error都会打出异常的堆栈,如果对吞吐量有一定要求,在情况运行时可以考虑覆盖该方法,去掉synchronized native,直接返回实例本身。

聊完日志内容,再来看看Appender。在Java中,说起IO操作大家都会想起NIO,到了JDK 7还有了AIO,至少都知道读写加个Buffer,日志也是如此,同步写的Appender在高并发大流量的系统里多少有些力不从心,这时就该使用AsyncAppender了,同样是使用LogBack:

在10线程并发下,输出200字符的INFO日志,AsyncAppender的吞吐量最高能是FileAppender的3.7倍。在不丢失日志的情况下,同样使用AsyncAppender,队列长度对性能也会有一定影响。

如果使用Log4J 2.x,那么除了有AsyncAppender,还可以考虑性能更高的异步Logger,由于底层用了Disruptor,没有锁的开销,性能更为惊人。根据Log4J 2.x的官方测试,同样使用Log4J 2.x:

64线程下,异步Logger比异步Appender快12倍,比同步Logger68倍。

同样是异步,不同的库之间也会有差异:

同等硬件环境下,Log4J 2.x全部使用异步Logger会比LogBack的AsyncAppender快12倍,比Log4J 1.x的异步Appender快19倍。

(点击放大图像)

Log4J 2.x的异步Logger性能强悍,但也有不同的声音,觉得这只是个看上去很优雅,只能当成一个玩具。关于这个问题,还是留给读者自己来思考吧。

如果一定要用同步的Appender,那么可以考虑使用ConsoleAppender,然后将STDOUT重定向到文件里,这样大约也能有10%左右的性能提升。

大部分生产系统都是集群部署,对于分布在不同服务器上的日志,用Logstash之类的工具收集就好了。很多时候还会在单机上部署多实例以便充分利用服务器资源,这时千万不要贪图日志监控或者日志查询方便,将多个实例的日志写到同一个日志文件中,虽然LogBack提供了prudent模式,能够让多个JVM往同一个文件里写日志,但此种方式对性能同样也有影响,大约会使性能降低10%。

如果对同一个日志文件有大量的写需求,可以考虑拆分日志到不同的文件,做法之一是添加多个Appender,同时修改代码,不同的情况使用不同Logger;LogBack提供了SiftingAppender,可以直接根据MDC的内容拆分日志,Jetty的教程中就有根据host来拆分日志的范例,而根据Takipi的测试,SiftingAppender的性能会随着拆分文件数的增长一同提升,当拆分为4个文件时,10并发下SiftingAppender的吞吐量约是FileAppender的3倍多。

看了上面这么多的数据,不知您是否觉得自己的日志有不少改进的余地,您还没有把系统优化到极致,亦或者您还有其他日志优化的方法,不妨分享给大家。

时间: 2025-01-07 13:08:23

Java日志性能的相关文章

Java 日志性能优化

1. 选择合理的日志级别.合理控制日志内容 2. 控制日志的输出内容和格式 logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i])); logger.debug("Entry number: {} is {}", i, entry[i]); 上面两条语句在日志输出上的效果是一样的,但是开销不一样,主要的影响在于字符串转换和字符串拼接上,无论是否生效,前者都会将变量

你的Java日志,有没有用这些改进办法

转自[http://mp.weixin.qq.com/s?__biz=MjM5MDE0Mjc4MA==&mid=207451012&idx=1&sn=de9fba4eda0f221363b6d5ae54243416&scene=2&from=timeline&isappinstalled=0#rd] 摘要:   在任何系统中,日志都是非常重要的组成部分,它是反映系统运行情况的重要依据,也是排查问题时的必要线索.绝大多数人都认可日志的重要性,但是又有多少人仔细

Java GC 专家系列5:Java应用性能优化的原则

本文是GC专家系列中的第五篇.在第一篇理解Java垃圾回收中我们学习了几种不同的GC算法的处理过程,GC的工作方式,新生代与老年代的区别.所以,你应该已经了解了JDK 7中的5种GC类型,以及每种GC对性能的影响. 在第二篇Java垃圾回收的监控中介绍了在真实场景中JVM是如何运行GC,如何监控GC数据以及有哪些工具可用来方便进行GC监控. 在第三篇GC 调优中基于真实案例介绍了可用于GC调优的最佳选项.同时也描述了如何通过降低移动到老年代中对象的数量来缩短Full GC耗时,以及如何设置GC类

为什么使用 SLF4J 而不是 Log4J 来做 Java 日志

转自:为什么使用 SLF4J 而不是 Log4J 来做 Java 日志 英文原文:Why use SLF4J over Log4J for logging in Java 每个Java开发人员都知道日志记录对Java应用的重要性,尤其是对服务端应用,而且其中许多人都已经熟悉了各种记录日志的库,比如java.util.logging,Apache的log4j,logback,然而如果你不知道SLF4J,java的简单记录日志的设计的话 ,那么到了学习并在你的项目中使用它的时候了.在这篇Java文档

2015第30周四Java日志组件

Java 日志 API 从功能上来说,日志 API 本身所需求的功能非常简单,只需要能够记录一段文本即可.API 的使用者在需要进行记录时,根据当前的上下文信息构造出相应的文本信息,调用 API 完成记录.一般来说,日志 API 由下面几个部分组成: 记录器(Logger):日志 API 的使用者通过记录器来发出日志记录请求,并提供日志的内容.在记录日志时,需要指定日志的严重性级别.当 程序中需要记录日志时,首先需要获取一个日志记录器对象.一般的日志记录 API 都提供相应的工厂方法来创建记录器

Java程序性能优化技巧

多线程.集合.网络编程.内存优化.缓冲..spring.设计模式.软件工程.编程思想 1.生成对象时,合理分配空间和大小new ArrayList(100); 2.优化for循环Vector vect = new Vector(1000);for( inti=0; i<vect.size(); i++){ ...}for循环部分改写成:int size = vect.size();for( int i=0; i>size; i++){ ...} 如果size=1000,就可以减少1000次si

JAVA日志库

一.常用日志Jar关系 2015第30周四Java日志组件 接口:将所有日志实现适配到了一起,用统一的接口调用. 实现:目前主流的日志实现 旧日志到slf4j的适配器:如果使用了slf4j,但是只想用一种实现,想把log4j的日志体系也从logback输出,这个是很有用的. slf4j到实现的适配器:如果想制定slf4j的具体实现,需要这些包. slf4J与旧日志框架的关系 slf4j等于commons-logging,是各种日志实现的通用入口,会根据classpath中存在下面哪一个Jar来决

影响Java EE性能的十大问题

本文作者是一名有10多年经验的高级系统架构师,他的主要专业领域是Java EE.中间件和JVM技术.他在性能优化和提升方面也有很深刻的见解,下面他将和大家分享一下常见的10个影响Java EE性能问题. 1.缺乏正确的容量规划 容量规划是一个全面的和发展的过程标准,预测当前和未来的IT环境容量需求.制定合理的容量规划不仅会确保和跟踪当前IT生产能力和稳定性,同时也会确保新项目以最小的风险部署到现有的生产环境中.硬件.中间件.JVM.调整等在项目部署之前就应该准备好. 2.Java EE中间件环境

Java 应用性能调优实践

Java 应用性能优化是一个老生常谈的话题,典型的性能问题如页面响应慢.接口超时,服务器负载高.并发数低,数据库频繁死锁等.尤其是在"糙快猛"的互联网开发模式大行其道的今天,随着系统访问量的日益增加和代码的臃肿,各种性能问题开始纷至沓来.Java 应用性能的瓶颈点非常多,比如磁盘.内存.网络 I/O 等系统因素,Java 应用代码,JVM GC,数据库,缓存等.笔者根据个人经验,将 Java 性能优化分为 4 个层级:应用层.数据库层.框架层.JVM 层,如图 1 所示. 图 1.Ja