系统的性能的好坏由很多因素决定,我从实际项目经验中总结大概有如下几点:
1sql,hql查询语句编写的好坏
2代码中递归,循环嵌套未正确使用
3数据库及连接池设置未进行优化
4tomcat设置未进行优化
5框架的性能制约 如struts2
关于sql语句和代码引起的性能问题,本篇暂且不谈。这里仅谈谈如何优化系统运行的环境,来提高系统的性能。
从我某个项目情况来看,服务器的配置都算还可以的,所以我们在给服务器安装装操作系统时一定要用64位的系统(server 2003或server2008),oracle10.2g 64位 jdk1.6/1.7 64位,tomcat6 64位(暂不推荐使用tomcat7,因为有些工具暂不支持tomcat7)。
为什么使用64位大家应该都懂得,32位的系统和软件支持的最大运行内存4g以下,而现在的服务器内存动辄16g,32g,cpu n核,剩余的内存和cpu不利用太可惜了。而大内存和多cpu意味着更多的缓存和并发。
下面以某项目为例
在测试组配合下对部署好后的项目使用loadrunner11进行系统登录并发测试(2003server 64位tomcat6 32位oracle64,jdk1.6 32位)
2003server 64位注意打上sp2补丁
Oracle64位 安装时注意调整进程大小 在安装第10步,选择调整大小标签,默认150,设置为1500(根据服务器硬件配置来定,比如本服务器内存为32g性能较好,低一点的设为1000)
系统登录看似简单,实际上是一个比较复杂的过程:
1验证用户
2初始化用户 模块 角色 权限等
3记录登陆日志
4跳转首页(首页显示时又会带来很多ajax请求,数据库查询和后台数据处理)
因此测试用户登陆系统的业务场景能够很好的反映系统的性能。闲话少数,进入测试。
用lr加载100用户,同时并发登陆系统,结果系统立刻崩溃,系统后台显示连接池超过最大连接,这个简单,直接修改applicationContext.xml中相关连接池设置
因为oracle已经设置最大连接为1500,所以这里可以放心的调大一些
- <!--初始化连接数-->
- <property name="prototypeCount">
- <value>200</value>
- </property>
- <!--连接池最大连接数,比oracle最大进程数稍微小一些-->
- <property name="maximumConnectionCount">
- <value>1200</value>
- </property>
- <!--最小连接数-->
- <property name="minimumConnectionCount">
- <value>50</value>
- </property>
- <!--这个值和并发有较大关系,可以设置大一点否则在高并发下可能会报这个错误We are already in the process of making 6 connections and the number of simultaneous builds has been throttled to 5-->
- <property name="simultaneousBuildThrottle">
- <value>100</value>
- </property>
以上的值是经过几次测试后定下来的。可以通过,但不一定是最合理的配置
做好这些设置重启项目再继续测试,100用户顺利跑过,系统未报错,并发测试通过。
再测系统的负载能力,就是在高并发下,系统的持续运行能力,反映这个能力的一项重要指标就是系统事件(完成登录全部过程)平均响应时间,测试完后发现系统响应时间较高在30秒以上,这就是说,当100用户在同时登录时,后面的如果还有人再登陆就得等待30秒以上,这个速度可能是大家接受不了的,所以要进行优化。
首先还是考虑是不是数据库的问题,在开发框架里的系统设置里面有个工具是来查看proxool的连接数及执行情况的,这个工具有一个重要的功能就是会记录数据库sql操作的持续时间,打开一看果然有好几处的sql操作时间鲜红超过5秒以上的(正常通常是几十ms),点开一看找到sql语句,是登陆后首页查询语句较多,这些语句都是用jdbctemplate进行查询的,所以可能要对spring里配置的jdbcTemplate优化一下
applicationContext.xml
- <!-- spring jdbcTemplate 配置 -->
- <beanid="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource" />
- </bean>
所以我想到可能得对jdbctemplate进行设置,直接找到
org.springframework.jdbc.core.JdbcTemplate查看代码,发现这个类里面有几个属性,一看就懂,所以修改后配置如下
- <!-- spring jdbcTemplate 配置 -->
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource" />
- <!--fetchSize 一次抓取结果大小-->
- <property name="fetchSize" value="50" />
- </bean>
这个设置完以后,再对首页后台的几处sql进行优化后再次负载测试,查看proxool的sql执行持续时间,全部变为绿色。此时平均响应时间有所降低,但还是比较高,可以肯定不是数据库的原因,具体是什么原因暂且不说,因为当时我也不知道是什么情况。暂且搁下负载,继续并发测试,刚才是100用户,现在加到200用户,一跑,tomcat报错,说tomcat连接达到最大值,这个我有印象
找到tomcat/conf/server.xml 修改
- <Connector
- URIEncoding="UTF8"
- connectionTimeout="20000"
- acceptCount="2000"
- maxSpareThreads="500"
- maxThreads="1500"
- minSpareThreads="100"
- port="8080"
- protocol="HTTP/1.1"
- redirectPort="8443"
- useBodyEncodingForURI="true"/>
改完以后再进行200人测试,tomcat直接报内存溢出:java.lang.OutOfMemoryError: Java heap space
关于tomcat内存溢出及启动内存设置的问题,大家可能在网上能搜到很多结果,
大多给的解决办法就是 在catalina.bat中加入下面的设置
- set JAVA_OPTS=-server -Xms512m -Xmx1024m -XX: PermSize=128M -XX:MaxPermSize=256m
(注意:-XX: PermSize=128M -XX:MaxPermSize=256m这两个值很重要,在ssh项目中由于spring会生成很多代理类,如果这个值不够大的话,会导致项目在tomcat启动时就报java.lang.OutOfMemoryError: PermGen space)
调整完以后,继续200人测试,仍然是内存溢出,将-Xmx1024m再调大一些,发现tomcat直接不能启动,到此我猜想tomcat32位的最大内存也就止步于此了,得换64位tomcat6,一人得道鸡犬升天,jdk也换64位,一狠心直接升级到jdk1.7 64位
升级完以后,加大-Xmx至2048m,tomcat顺利启动,但是有几个警告(connector ssl中的几个参数不对),暂时忽略。用lr200人继续测试,没有内存溢出,但是tomcat又报超过最大200线程的错了,刚才明明已经设置了maxThreads="1500",为何还报这个错呢,注意到刚才tomcat启动的警告,就是说这几个参数不对,并且ssl设置的pki登陆也不能用了,估计是tomcat升级到64位后的问题,查了半天发现tomcat 64位中server.xml配置中的几处协议需要改下
Server.xml 以下两处需要修改
1<Connector port="88" protocol="org.apache.coyote.http11.Http11Protocol" connectionTimeout="20000" ../>
2<Connector port="8443"
protocol="org.apache.coyote.http11.Http11Protocol" SSLEnabled="true" ...../>
修改后重启tomcat继续压力测试,200人全部通过,250人全部通过,但是系统平均响应时间仍然较长20-30秒。检查数据库没什么问题,tomcat也升级了,那怎么还慢呢,系统也没报错,怎么办?
继续查资料,发现一款神器:Jprofiler7一款能够检测java项目性能的工具,和lr不同,lr能测出性能不好,但不知道是哪里的出的原因,而jprofiler可以告诉你是哪里卡住了,里面有一项重要的指标就是Thread的bolcked Time(阻塞时间)。
直接在jprofiler里跑tomcat,同时运行lr200负载测试,发现所有的线程阻塞都指向了同一个类(Struts2中的一个类),以前一直传说struts2的性能有问题,看来这回出现了,百度,没有资料,google天朝屏蔽。最后直接上struts2项目的jira,搜索bug,居然真搜到了,显然官方承认,在版本2.2.x中存在这个问题,说在2.3.1中解决了这个问题。于是升级struts2,直接升级到最新版本2.3.8,换了系列struts2 jar包后,启动项目各种报错(你懂得),一一解决,比较棘手的一个问题就是,struts2.3.8和我们开发工具jrebel有冲突(不知道最新版的有没有这个问题),这也是偶然发现的,一开始升级sturts2后在myeclipse里跑怎么也启动不了,以为是jar包冲突,后来发现直接跑tomcat就可以,我就怀疑是jrebel的问题,去掉jrebel果然可以了,但是去掉了亲爱的jrebel怎么行呢,他节约了我们程序猿们多少宝贵的时间……(何止事半功倍),直接研究下jrebel jar包,用winrar打开jar包,在plugin目录下有struts2-jr-plugin-5.0.0.jar 和struts2-jr-plugin-5.0.0.jar.sig两个文件,猜测可能是这两个包不匹配最新的struts2,于是直接从jar包中将两个文件删掉,myeclipse继续加载jrebel,项目正常启动,修改类后热部署正常。(有没有其他影响暂时不得而知,改天去查下jrebel最新版)至此升级完毕。
继续用lr+jprofiler测试,发现刚才阻塞在秒级以上的线程不复存在,各个线程都在平缓的变换着,阻塞时间变为毫秒级。最后测试在200人的压力测试下,系统平均响应时间在5秒左右,250人在9秒。想进一步优化,发现阻塞时间仍有一部分是在struts2上,这些阻塞没什么太大异常,可能struts2的性能确实只有这样了。
在性能优化上光依靠64位系统带来的大内存和多核是不够的,网上说采用32位tomcat集群的部署方式大于单个64位tomcat6扩展大内存的部署方式,由此我得出,真正的系统性能提高不是靠优化一台服务器,一个tomcat能搞定的,而是需要采用服务器+tomcat集群的方式来部署系统,也即负载均衡。关于集群和负载均衡不在本篇讨论。
以上是本人在项目性能优化中的一些心得体会,难免有表达不妥和错误遗漏之处,请大家不吝指出!