J2EE/Oracle性能调优实录
这两天,有一个新接手的项目性能表现较差,不能满足要求。这个项目在这之前已经写完很久了,但是一些关键部署点一直没有跟上进度。此系统是用Java编写的多线程程序的,主要是从Oracle中读取数据,然后根据业务进行统计运行,在将计算结果放回Oracle中。
这个系统的性能的直观指标,就是每天能处理多少数据量。这个指标很不理想,速度很慢,拖后与现场数据生成的速度。调优的标准,就是每日处理量。
第一次优化: 提高JDBC的fetchSize
首先,分析系统各个环节的时间消耗,弄清瓶颈在哪里。
在进行了一些综合分析后,得出主要时间耗费在数据库的查询上。而从v$session_wait中查询,发现有很多 SQL*Net message from client 事件的等待,时间很长,有数百秒之多。 显然是客户端慢了。但是因为应用端是有连接池的,是不是空闲连接导致的呢? 有一些怀疑。
接下来,在数据库上用 sqlplus 执行了相同的SQL,测量其时间其实是很快的,远远小于应用消耗在数据获取的时间。联系其上面提到的等待事件,事情变逐渐明朗: 网络传输慢了。
于是检查了代码,发现大批量的数据查询竟然没有提升fetchSize属性。在调整该属性为500以后,很快收到了正面反馈。处理速度提升了将近一倍。
回去在检查Oracle中的等待事件,发现已经降到200名以外了(是与其他应用共用数据库的)。
有关 sqlnet message from client 事件的分析,可以参考 Asktom 上的文章。 AskTom上的另一篇文章,则描述了类似于连接池等待,或者是在sqlplus中打开会话不操作也会导致该事件。所以在见到这个事件时还是需要仔细甄别一下的。
结论: 适当调整 FetchSize是很有必要的。Oracle JDBC默认的fetchSize为10。FetchSize的提升,一方面是减少了客户端与服务器之间的来回交互次数;另一方面,也会有效减少数据库服务器逻辑读的数量(记得是《Troubleshooting Oracle Performance》中有讲到)。
修改完成后,除了观测到 sqlnet message from client 事件消失外,发现 sys cpu 也有小幅增加。
第二次优化:减小锁定(lock)范围
但是,接下来的观测,证明这个速度还是不合意,达不到要求。于是继续分析。
仍然是分析其主要的时间消耗在什么地方。这次发现时间主要是消耗在程序对数据的处理过程中。数据的处理大部分都是在Java进程中的运算,处理过程中还可能调用到Memcached。是不是网络速度比较慢呢?尝试测试了一下网速,千兆网络的吞吐率在应用并发时还能挤出三分之一左右用来做测试文件的传输,用ping发现其latency也不算高。
在此前的观测中,发现应用服务器上的 user cpu 比较好,在30%~70%之间,主要是被JVM进程消耗了,系统负载在5.0左右。觉得有些奇怪的,因为一般这样的应用瓶颈都出在IO上。于是做了一次代码复查,很快发现有一个在方法签名上的synchronize 关键字。这个类是一个服务bean, 在spring中管理的。这时问题变得清晰起来,所有的处理任务线程都要在这里排队。这样正好符合了此前观测到的cpu现象。
改掉这个关键字,缩小锁定范围,速度又上升了将近一倍。user cpu很快降下来,系统负载也下来了,稍稍大于1。
此时,速度虽能勉强应付,但还是不够快。此时更多的时间消耗在数据的写回上面。处理速度提升了,写回阶段的压力便也迅速提升,每单位数据的写回时间有20%左右的上浮。于是,需要第三次优化,在数据写回方面提升速度。
第三次优化
(Update Apr 6th):
没心思再仔细写过程了,简略如下:
1) Oracle是多个系统共享的,数据写入成为瓶颈。在内存问题解决后,在本地内存中缓存结果,最后再集中提交,减少更新次数。数度大增2.5倍左右。
2) 将从数据库中的查询和在内存中的处理划分为两个线程并发处理,速度提高50-70%。
3) 然后,Memcached集群成为瓶颈,内网延迟成为问题。但此时速度已经足够快了,因此暂时搁置下一步优化。如果要继续提速,则可能在本地建立一个Memcached实例,或者在程序中使用一个足够大的LRU缓存以减少与Memcached交互的次数。
至此,数据后台处理速度完全达到并超过预期。
Recent Comments