Java日志性能那些事(转)
2015-10-08 16:14
190 查看
在任何系统中,日志都是非常重要的组成部分,它是反映系统运行情况的重要依据,也是排查问题时的必要线索。绝大多数人都认可日志的重要性,但是又有多少人仔细想过该怎么打日志,日志对性能的影响究竟有多大呢?今天就让我们来聊聊Java日志性能那些事。
说到Java日志,大家肯定都会说要选择合理的日志级别、合理控制日志内容,但是这仅是万里长征第一步……哪怕
一些
我们撇开判断和方法调用的开销,在Log4J 2.x的性能文档中有这样一组对比:
上面两条语句在日志输出上的效果是一样的,但是在关闭
无论是否生效,前者都会将变量转换为字符串并进行拼接,而后者则只会在需要时执行这些操作。Log4J官方的测试结论是两者在性能上能相差两个数量级。试想一下,如果某个对象的
因此,某些仍在使用Log4J 1.x或Apache Commons Logging(它们不支持
除了日志级别和日志消息,通常在日志中还会包含一些其他信息,比如日期、线程名、类信息、MDC变量等等,根据Takipi的测试,如果在日志中[b]加入
在分布式系统中,一个请求可能会经过多个不同的子系统,这时最好生成一个UUID附在请求中,每个子系统在打印日志时都将该UUID放在MDC里,便于后续查询相关的日志。《The Ultimate Guide: 5 Methods For Debugging Production Servers At Scale》一文中就如何在生产环境中进行调试给出了不少建议,当中好几条是关于日志的,这就是其中之一。另一条建议是记录下所有未被捕获的日志,其实抛出异常有开销,记录异常同样会带来一定的开销,主要原因是
一般使用
聊完日志内容,再来看看
在Java中,说起IO操作大家都会想起NIO,到了JDK 7还有了AIO,至少都知道读写加个
在10线程并发下,输出200字符的
如果使用Log4J 2.x,那么除了有
64线程下,异步
同样是异步,不同的库之间也会有差异:
同等硬件环境下,Log4J 2.x全部使用异步
(点击放大图像)
Log4J 2.x的异步
如果一定要用同步的
大部分生产系统都是集群部署,对于分布在不同服务器上的日志,用Logstash之类的工具收集就好了。很多时候还会在单机上部署多实例以便充分利用服务器资源,这时千万不要贪图日志监控或者日志查询方便,将多个实例的日志写到同一个日志文件中,虽然LogBack提供了
如果对同一个日志文件有大量的写需求,可以考虑拆分日志到不同的文件,做法之一是添加多个
看了上面这么多的数据,不知您是否觉得自己的日志有不少改进的余地,您还没有把系统优化到极致,亦或者您还有其他日志优化的方法,不妨分享给大家。
感谢丁晓昀对本文的审校。
http://www.infoq.com/cn/articles/things-of-java-log-performance?utm_campaign=rightbar_v2&utm_source=infoq&utm_medium=articles_link&utm_content=link_text
说到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(它们不支持
{}模板的写法)的公司都会有相应的编码规范,要求在一定级别的日志(比如
DEBUG和
INFO)输出前增加判断:
if (logger.isDebugEnabled()) { logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i])); }
除了日志级别和日志消息,通常在日志中还会包含一些其他信息,比如日期、线程名、类信息、MDC变量等等,根据Takipi的测试,如果在日志中[b]加入
class,性能会急剧下降,比起LogBack的默认配置,吞吐量的降幅在6成左[/b]右。如果一定要打印类信息,可以考虑用类名来命名
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倍,比同步
Logger快68倍。
同样是异步,不同的库之间也会有差异:
同等硬件环境下,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倍多。
看了上面这么多的数据,不知您是否觉得自己的日志有不少改进的余地,您还没有把系统优化到极致,亦或者您还有其他日志优化的方法,不妨分享给大家。
感谢丁晓昀对本文的审校。
http://www.infoq.com/cn/articles/things-of-java-log-performance?utm_campaign=rightbar_v2&utm_source=infoq&utm_medium=articles_link&utm_content=link_text
相关文章推荐
- 在eclipse中增加配置文件方式
- Java异常处理
- 在Eclipse中遇到The type XXX cannot be resolved. It is indirectly referenced from required .class files错误
- JAVA IO流使用
- ADT如法自动生成R.java文件
- 你的Java代码对JIT编译友好么?(转)
- java中,如何安全的结束一个正在运行的线程?
- Java虚拟机垃圾回收的几个关键问题
- Java时间日期格式转换
- 使用Java编写GUI对话框的教程
- java设计模式之——策略模式
- 关于java中调用Rserve包的eval函数中的奇葩bug
- Java继承和接口
- Java制作证书的工具keytool用法总结
- java nio的一个严重BUG(转)
- java输出调试技巧及Eclipse快捷键使用方法
- Java序列化的几种方式以及序列化的作用
- java标准标签库JSTL详解
- Java和MySQL数据库的连接
- java的动态代理机制详解