优化Web应用的性能绝不象有些人想象的那样简单易行,它涉及到诸多技术,从最简单的Html代码修改,到复杂的EJB改造,无不涉及性能问题。但有一点是非常清楚的:要想找出和解决Web应用的性能瓶颈,就必须深入全面地了解信息在Web应用中的流程。
改善Web应用的性能不一定要局限于Web应用的Java代码,例如有些时候,简单地改动一下HTML页面的质量、减少其传输频度和数据量就可以有效地提高应用的性能表现;有时提高性能的要害却在于修改Web应用的数据库访问部分——这只是Java代码之外影响性能的两个因素,其他还有许多因素会影响到Web应用的整体性能表现。另一方面,就Java程序本身而言,其性能优化又可以分成三个领域:基本的Java代码优化,jsp/Servlet优化,EJB优化。
一、表现层优化
Web应用的最大性能瓶颈经常不在其他地方,而在于最基本的网络带宽限制。假如你的Web应用也面临这类问题,提高性能最简单的办法是减少HTTP传输,例如用javascript实现客户端编辑功能以减少数据传输次数,避免将数据发送到服务器端再执行合法性验证之类的编辑操作。
应当采用一切可能措施减少通过网络传输的数据。例如,你可以要求浏览器缓冲模块化的JavaScript文件,在SCRIPT标记的SRC中指定:
SCRIPT LANGUAGE="JavaScript" SRC="FormChek.js"。
其他减少网络传输应当注重的地方还包括:避免过度使用隐藏域,减少超长Cookie值,在RADIO、CHECKBOX和SELECT域中用代码来替代长长的字符串,等等。不过在HTML优化方面本文不预备作全面的讨论,因为WebSphere应用的开发者一般不会担负设计表现层的责任,只要了解下面这个原理就足够了:
性能技巧之一:尽可能减少HTTP数据传输的总量和频度
二、数据库访问
朋友小A对Java的了解极为有限,但他却成功地改进了许多WebSphere应用的性能。他是怎么做到的呢?原来,小A是一个数据库专家,他通过优化数据库访问有效地改进了整个应用的性能,但对于Java,他只是略微了解一些有关JDBC的知识。在优化数据库访问时,小A做的第一件事情总是检查数据库的设计,有时他会建议重新构造数据库的结构(必须指出的是,为了提高性能而重新构造数据库结构有时可能使数据库反规格化(De-Normalization),从而带来维护方面的问题)。
性能技巧之二:规格化(Normalization)数据库结构
小A做的第二件事情是执行数据库分析,根据分析结果提出增加某个索引、减少某个索引的建议。完成这一步骤后,小A通常可以让应用有令人满足的性能表现,根本不必去查看应用的Java代码。
性能技巧之三:针对常用的SQL操作建立索引,删除多余的索引
有时,为了进一步优化应用的性能,小A会检查Java(也许应该说是SQL)代码,经常找到Java程序没有合理运用PreparedStatement和连接缓冲池的情形。只要把Statement类的动态SQL替换成PreparedStatement类的静态SQL,从连接池提取SQL连接(而不是直接创建连接),应用的性能将得到显著的改善。注重DB2 UDB(包括其他一些数据库)的PreparedStatement是可调整和配置的。
性能技巧之四:合理运用PreparedStatement和连接池
进一步分析应用的工作流程之后,小A有时会建议批量执行某些SQL命令,这样就只需一个对数据库服务器的请求就可以运行大量的SQL命令。
性能技巧之五:考虑批量执行SQL命令
既然如此,小A有时还会指出,假如应用中有些SQL命令可以组合成单个事务逻辑,那么应该可以用一个存储过程来替代。DB2 UDB的存储过程语言(SPL,Stored Procedure Language)非常强大,假如把数据库操作逻辑从Web应用转移到数据库,一般总是对性能有益。不过需要注重的是,虽然批量执行SQL命令或使用存储过程会提高性能,但就象重新构造数据库结构一样,有时会带来维护方面的困难。
性能技巧之六:考虑使用数据库存储过程
检查JDBC代码的时候,小A总是留意对象有没有及时正确释放。这一点其实很重要。
性能技巧之七:及时关闭不用的Statement、ResultSet、Connection等对象(但不是在finalize方法内)
三、Java代码
前面我们以小A的经验为例,探讨了Web应用中数据库访问性能的重要性。
调整好数据库之后,接下来要做的自然是深入分析应用的Java代码。从哪里入手呢?你最好使用Java分析工具来找出性能问题的焦点所在。优化Java代码的性能是一个艰苦的过程,因此一个重要的原则是把精力集中到那些可能引起性能问题的代码上。换句话说,就是要尊重80/20规则:利用Java分析工具的结果,调整带来80%性能开销的那20%代码。
性能技巧之八:用Java分析工具清楚地界定性能问题所在
目前市场上已经有许多优秀的Java分析工具,例如ej-technologie的JProfile(http://www.ej-technologies.com),Klgroup的Jprobe(http://www.klg.com),以及Intuitive Systems的OptimizeIt(http://www.optimizeit.com)。不过不要忘记WebSphere Studio Application Developer(WSAD)本身也集成了一个优秀的分析器,有条件的话,最好多用几种分析工具分析Java代码。
考虑到资金问题,你不一定乐意购买昂贵的分析软件,但你可以用Java本身的命令行工具生成分析信息。例如,在JDK 1.3中,你可以用下面的命令将TestOrderProcessing类的CPU使用情况保存到java.hprof文件:java -Xrunhprof:cpu=times,format=a,file=java.hprof TestOrderProcessing。
这种办法的缺点是它提供的信息条理不够清楚,比较繁杂;也许可以找到一些源代码开放的工具辅助分析,但一般不如使用WSAD本身的分析工具或商业化的分析工具方便。另外,假如你已经了解哪些代码块可能引起性能问题,可以通过保存系统时间的方式获得分析信息,例如:
long startTime = System.currentTimeMillis();
// 执行某些操作
long endTime = System.currentTimeMillis();
3.1 基本篇
有人建议“稳定性第一,速度第二”,一般而言遵从这个建议是不会错的,但这并不妨碍我们在编写代码的同时运用某些已经证实的性能技巧。例如,我们都知道String类是不可变的,连接两个String是一项开销很大操作。
性能技巧之九:用StringBuffer来连接两个字符串
也许你已经注重到,Sun的许多标准Java类是线程安全的,这些类内部的同步机制实际上很轻易造成性能问题。例如,Vector类就是一个线程安全的类,除非确实要用到同步机制,否则使用Vector是不值得的,如有可能,应当尽量改用非线程安全的类如ArrayList。
性能技巧之十:只有在必要时才运用线程安全的类
许多人习惯使用System.out.println来输出跟踪信息,但println要占用不少资源,所以输出跟踪信息最好使用专用日志记录框架,如IBM的JRas或Apache的Log4j。
性能技巧之十一:用日志记录框架类输出跟踪信息,而不是使用System.out.println
最后一个提高代码性能的简单技巧是清除类里面的调试信息,减小类的体积。IBM有一个WSAD插件,它提供了一个叫做setDebugInfo的任务,可以从Ant脚本调用。
性能技巧之十二:从正式发行的软件中删除调试信息