由此可见AsyncAppender虽然是异步的,但是并不能提升性能,为什么呢?何谓异步?异步就是另外开了个线程用来专门记录日志,然而既然引入了多线程,线程间的同步开销就不能不考虑了,看AsyncAppender的源代码中到处充斥着synchronized就能看到了。
用独立线程处理日志会引入以下几个问题:
由此的需要讨论就是,为什么要使用异步?何时使用异步?如何使用异步?我们先来看同步和异步到底有何不同:
各线程直接获得输出流进行输出(线程间不需要同步)。
众所周知,磁盘IO操作、网络IO操作、JDBC操作等都是非常耗时的,日志输出的主要性能瓶颈也就是在写文件、写网络、写JDBC的时候。日志是肯定要记的,而要采用异步方式记,也就只有将这些耗时操作从主线程当中分离出去才能真正的实现性能提升,也只有在线程间同步开销小于耗时操作时使用异步方式才真正有效!现在我们接着分别来看看这几种记录日志的方式。
同样都是写本地文件Log4j本身有一个buffer处理入库,采用异步方式并不一定能提高性能(主要是如何配置好缓存大小);而线程间的同步开销则是非常大的!因此在使用本地文件记录日志时不建议使用异步方式。
JMS本身是支持异步消息的,如果不考虑JMS消息创建的开销,也不建议使用异步方式。
将日志通过Socket发送,纯网络IO操作不需要反馈,因此也不会耗时
众所周知JDBC是几种方式中最耗时的:网络、磁盘、数据库事务,都使JDBC操作异常的耗时,在这里采用异步方式入库倒是一个不错的选择。
同JDBC
在同步和异步方式下,同时起1000个线程,分别测试记录到文件和数据库中的时间消耗,每类测试连测5遍避免误差。
1、WINDOWS XP SP2、JDK 1.5、ORACLE 9i2(本地)、log4j-1.2.14.jar
2、MutiTest.java用来测试同步方式,读取配置文件:log4j.properties
MutiTestAsyncAppender.java用来测试异步方式,读取配置文件:log4j.xml
3、每次测试先删除日志文件或清空表,确保测试独立性
测试结果如下:
FileAppender | ||||
同步方式 | 最晚线程执行时间 | 最早线程执行时间 | 消耗时间 | |
1 | 1.23209E+12 | 1.23209E+12 | 156 | |
2 | 1.23209E+12 | 1.23209E+12 | 172 | |
3 | 1.23209E+12 | 1.23209E+12 | 172 | |
4 | 1.23209E+12 | 1.23209E+12 | 188 | |
5 | 1.23209E+12 | 1.23209E+12 | 157 | |
异步方式 | 最晚线程执行时间 | 最早线程执行时间 | 消耗时间 | |
1 | 1.23209E+12 | 1.23209E+12 | 157 | |
2 | 1.23209E+12 | 1.23209E+12 | 188 | |
3 | 1.23209E+12 | 1.23209E+12 | 156 | |
4 | 1.23209E+12 | 1.23209E+12 | 187 | |
5 | 1.23209E+12 | 1.23209E+12 | 172 | |
JDBCAppender | ||||
同步方式 | 最晚线程执行时间 | 最早线程执行时间 | 消耗时间 | |
1 | 1.23209E+12 | 1.23209E+12 | 281 | |
2 | 1.23209E+12 | 1.23209E+12 | 172 | |
3 | 1.23209E+12 | 1.23209E+12 | 172 | |
4 | 1.2321E+12 | 1.2321E+12 | 171 | |
5 | 1.2321E+12 | 1.2321E+12 | 203 | |
异步方式 | 最晚线程执行时间 | 最早线程执行时间 | 消耗时间 | |
1 | 1.2321E+12 | 1.2321E+12 | 94 | |
2 | 1.2321E+12 | 1.2321E+12 | 94 | |
3 | 1.2321E+12 | 1.2321E+12 | 94 | |
4 | 1.2321E+12 | 1.2321E+12 | 94 | |
5 | 1.2321E+12 | 1.2321E+12 | 125 |
由以上测试结果可以得出异步方式记录日志并不是什么情况下都能提升性能的,相反由于线程间的同步开销,甚至可能降低性能;只有像在JDBC操作或是SMTP之类的记录耗时比较长的情况下,使用异步入库方式才是个好选择。