经常听到不少人说一句话:“tomcat性能差,不如去用weblogic,websphere,jboss”,我想说,用什么东西得根据实际情况来吧,如果给一个小公司的外包开发一个普通的项目,给她用Oracle,Weblogic?在高速公路上,拖拉机是没奔驰快,但是到了乡下,奔驰可就不行喽。看完本文,您将对tomcat的使用有个新的认识。
一.影响性能的几个重要指标
在开始动手优化小猫的性能之前,我们务必要先了解几个概念。
1.1吞吐量
吞吐量是指在一次性能测试过程中网络上传输的数据量的总和。
对于交互式应用来说,吞吐量指标反映的是服务器承受的压力,在容量规划的测试中,吞吐量是一个重点关注的指标,因为它能够说明系统级别的负载能力。另外,在性能调优过程中,吞吐量指标也有重要的价值。
1.2吞吐率
单位时间内网络上传输的数据量,也可以指单位时间内处理客户请求数量。它是衡量网络性能的重要指标,通常情况下,吞吐率用“字节数/秒”来衡量,当然,你可以用“请求数/秒”和“页面数/秒”来衡量。其实,不管是一个请求还是一个页面,它的本质都是在网络上传输的数据,那么来表示数据的单位就是字节数。
1.3事物
用户某一步或几步操作的集合。不过,我们要保证它有一个完整意义。比如用户对某一个页面的一次请求,用户对某系统的一次登录,淘宝用户对商品的一次确认支付过程,这些我们都可以看作一个事务。那么如何衡量服务器对事务的处理能力,又引出一个概念——TPS。
1.4 TPS
每秒钟系统能够处理事务或交易的数量。
1.5点击率
点击率可以看做是TPS的一种特定情况,点击率更能体现用户端对服务器的压力,TPS更能体现服务器对客户请求的处理能力。
每秒钟用户向web服务器提交的HTTP请求数,这个指标是web应用特有的一个指标;web应用是“请求-响应”模式,用户发一个申请,服务器就要处理一次,所以点击是web应用能够处理的交易的最小单位。如果把每次点击定义为一个交易,点击率和TPS就是一个概念。容易看出,点击率越大,对服务器的压力也越大,点击率只是一个性能参考指标,重要的是分析点击时产生的影响。
需要注意的是,这里的点击不是指鼠标的一次“单击”操作,因为一次“单击”操作中,客户端可能向服务器发现多个HTTP请求。
1.6平均响应时间
也称为系统响应时间,它一般指在指定数量的VU情况下,每笔交易从mouse的click到IE的数据刷新与展示之间的间隔,比如说:250个VU下每笔交易的响应时间不超过2秒。
二.我们要优化tomcat的目标
对以上几个性能方面的重要概念有了大致的了解后,我们要清楚优化tomcat的目标,我认为大体可以概括为2个目标:
1)承受更大并发用户数
2)性能方面取得大幅改善(系统平均性能提升至少20倍,甚至60倍)
三. 从多方面来优化tomcat的性能
Tomcat的优化分为两块: JVM的优化,容器自身参数的优化。
3.1 JVM的优化
3.1.1 32位操作系统和64位中JVM的比较
32位系统下JVM对内存的限制:不能突破2GB内存,即使在Win2003 Advanced Server下你的机器装有8GB-16GB的内存,而你的JAVA,只能用到2GB的内存,而在64位操作系统上无论是系统内存还是JVM都没有受到2GB这样的限制。
3.1.2 tomcat启动行参数的优化
Tomcat首先跑在JVM之上的,因为它的启动其实也只是一个java命令行,首先我们需要对这个JAVA的启动命令行进行调优,本文是基于jdk 1.6环境。
Tomcat 的启动参数位于tomcat的安装目录\bin目录下,如果你是Linux操作系统就是catalina.sh文件,如果你是Windows操作系统那么你需要改动的就是catalina.bat文件。本文以linux环境为例来讲解,打开该文件,一般该文件头部是一堆的由##包裹着的注释文字,找到注释文字的最后一段。
敲入一个回车,加入如下的参数
export JAVA_OPTS="-server -Xms1400M-Xmx1400M -Xss512k
-XX:+AggressiveOpts-XX:+UseBiasedLocking -XX:PermSize=128M -XX:MaxPermSize=256M-XX:+DisableExplicitGC -XX:MaxTenuringThreshold=31
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC-XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection-XX:LargePageSizeInBytes=128m
-XX:+UseFastAccessorMethods-XX:+UseCMSInitiatingOccupancyOnly -Djava.awt.headless=true "
参数解释:
-server
只要tomcat是运行在生产环境中的,这个参数必须加上。
tomcat默认是以一种叫java –client的模式来运行的,server即意味着你的tomcat是以真实的production的模式在运行的,这也就意味着你的tomcat以server模式运行时将拥有:更大、更高的并发处理能力,更快更强捷的JVM垃圾回收机制,可以获得更多的负载与吞吐量。
-Xms–Xmx
即JVM内存设置了,把Xms与Xmx两个值设成一样是最优的做法,有人说Xms为最小值,Xmx为最大值不是挺好的,这样设置还比较人性化,科学化。事实是如此吗?
大家想一下这样的场景:
一个系统随着并发数越来越高,它的内存使用情况逐步上升,上升到最高点不能上升了,开始回落,你们不要认为这个回落就是好事情,由其是大起大落,在内存回落时它付出的代价是CPU高速开始运转进行垃圾回收,此时严重的甚至会造成你的系统出现“卡壳”就是你在好好的操作,突然网页像死在那边一样几秒甚至十几秒时间,因为JVM正在进行垃圾回收。
因此一开始我们就把这两个设成一样,使得Tomcat在启动时就为最大化参数充分利用系统的效率,这个道理和jdbc pool里的minpool size与maxpool size的需要设成一个数量是一样的原理。
如何知道我的JVM能够使用最大值啊呢?
在设这个最大内存即Xmx值时请先打开一个命令行,键入如下的命令:
如果是在32位系统下,我们试试2G内存行不行:
试试1700m
连1700m都不可以,更不要说2048m了,2048m只是一个理论数值。这个跟机器也有关,有的能到1700m
–Xmn
设置年轻代大小为512m。整个堆大小=年轻代大小+年老代大小+ 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
-Xss
是指设定每个线程的堆栈大小。这个就要依据你的程序,看一个线程大约需要占用多少内存,可能会有多少线程同时运行等。一般不易设置超过1M,要不然容易出现out ofmemory
-XX:+AggressiveOpts
启用这个参数,则每当JDK版本升级时,你的JVM都会使用最新加入的优化技术(如果有的话)。
-XX:+UseBiasedLocking
启用一个优化了的线程锁,在我们的appServer,每个http请求就是一个线程,有的请求短有的请求长,就会有请求排队的现象,甚至还会出现线程阻塞,这个优化了的线程锁使得你的appServer内对线程处理自动进行最优调配。
-XX:PermSize=128M-XX:MaxPermSize=256M
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;
在数据量的很大的文件导出时,一定要把这两个值设置上,否则会出现内存溢出的错误。
由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
那么,如果是物理内存4GB,那么64分之一就是64MB,这就是PermSize默认值,也就是永生代内存初始大小;
四分之一是1024MB,这就是MaxPermSize默认大小。
-XX:+DisableExplicitGC
在程序代码中不允许有显示的调用”System.gc()”。看到过有两个极品工程中每次在DAO操作结束时手动调用System.gc()一下,觉得这样做好像能够解决它们的out ofmemory问题一样,付出的代价就是系统响应时间严重降低。
-XX:+UseParNewGC
对年轻代采用多线程并行回收,这样收得快。
-XX:+UseConcMarkSweepGC
即CMS gc,这一特性只有jdk1.5即后续版本才具有的功能,它使用的是gc估算触发和heap占用触发。
我们知道频频繁的GC会造面JVM的大起大落从而影响到系统的效率,因此使用了CMS GC后可以在GC次数增多的情况下,每次GC的响应时间却很短,比如说使用了CMS GC后经过jprofiler的观察,GC被触发次数非常多,而每次GC耗时仅为几毫秒。
-XX:MaxTenuringThreshold
设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。
这个值的设置是根据本地的jprofiler监控后得到的一个理想的值,不能一概而论原搬照抄。
-XX:+CMSParallelRemarkEnabled
在使用UseParNewGC 的情况下, 尽量减少mark的时间
-XX:+UseCMSCompactAtFullCollection
在使用concurrent gc的情况下, 防止memoryfragmention,对live object进行整理, 使memory 碎片减少。
-XX:LargePageSizeInBytes
指定Java heap的分页页面大小。
-XX:+UseFastAccessorMethods
get,set 方法转成本地代码
-XX:+UseCMSInitiatingOccupancyOnly
指示只有在oldgeneration在使用了初始化的比例后concurrent collector启动收集
-XX:CMSInitiatingOccupancyFraction=70
CMSInitiatingOccupancyFraction,这个参数设置有很大技巧,基本上满足(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn就不会出现promotion failed。在我的应用中Xmx是6000,Xmn是512,那么Xmx-Xmn是5488兆,也就是年老代有5488兆,CMSInitiatingOccupancyFraction=90说明年老代到90%满的时候开始执行对年老代的并发垃圾回收(CMS),这时还剩10%的空间是5488*10%=548兆,所以即使Xmn(也就是年轻代共512兆)里所有对象都搬到年老代里,548兆的空间也足够了,所以只要满足上面的公式,就不会出现垃圾回收时的promotion failed;
因此这个参数的设置必须与Xmn关联在一起。
-Djava.awt.headless=true
这个参数一般我们都是放在最后使用的,这全参数的作用是这样的,有时我们会在我们的J2EE工程中使用一些图表工具如:jfreechart,用于在web网页输出GIF/JPG等流,在winodws环境下,一般我们的appserver在输出图形时不会碰到什么问题,但是在linux/unix环境下经常会碰到一个exception导致你在winodws开发环境下图片显示的好好可是在linux/unix下却显示不出来,因此加上这个参数以免避这样的情况出现。
上述这样的配置,基本上可以达到:
1.系统响应增快
2. JVM回收速度增快同时又不影响系统的响应率
3. JVM内存最大化利用
4. 线程阻塞情况最小化
3.2 Tomcat容器内的优化
前面我们对Tomcat启动时的命令进行了优化,增加了系统的JVM可使用数、垃圾回收效率与线程阻塞情况、增加了系统响应效率等还有一个很重要的指标,我们没有去做优化,就是吞吐量。
打开tomcat安装目录\conf\server.xml文件,定位到这一行:
这一行就是我们的tomcat容器性能参数设置的地方,它一般都会有一个默认值,这些默认值是远远不够我们的使用的,我们来看经过更改后的这一段的配置:
又是一大坨。。。
解释一下:
URIEncoding=”UTF-8”
使得tomcat可以解析含有中文名的文件的url,真方便,不像apache里还有搞个mod_encoding,还要手工编译。
maxSpareThreads
maxSpareThreads 的意思就是如果空闲状态的线程数多于设置的数目,则将这些线程中止,减少这个池中的线程总数。
minSpareThreads
最小备用线程数,tomcat启动时的初始化的线程数。
enableLookups
这个功效和Apache中的HostnameLookups一样,设为关闭。
connectionTimeout
connectionTimeout为网络连接超时时间毫秒数。
maxThreads
maxThreads Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数,即最大并发数。
acceptCount
acceptCount是当线程数达到maxThreads后,后续请求会被放入一个等待队列,这个acceptCount是这个队列的大小,如果这个队列也满了,就直接refuse connection。
maxProcessors与minProcessors
在Java中线程是程序运行时的路径,是在一个程序中与其它控制线程无关的、能够独立运行的代码段。它们共享相同的地址空间。多线程帮助程序员写出CPU最大利用率的高效程序,使空闲时间保持最低,从而接受更多的请求。
通常Windows是1000个左右,Linux是2000个左右。
useURIValidationHack
对于这个参数,我们要看一下tomcat的一段源码再说。
security
if (connector.getUseURIValidationHack()) {
Stringuri = validate(request.getRequestURI());
if(uri == null) {
res.setStatus(400);
res.setMessage("Invalid URI");
throw new IOException("InvalidURI");
} else {
req.requestURI().setString(uri);
// Redoing the URI decoding
req.decodedURI().duplicate(req.requestURI());
req.getURLDecoder().convert(req.decodedURI(),true);
}
}
可以看到如果把useURIValidationHack设成"false",可以减少它对一些url的不必要的检查从而减省开销。
enableLookups="false"
为了消除DNS查询对性能的影响我们可以关闭DNS查询
disableUploadTimeout
类似于Apache中的keeyalive一样
compression="on"compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
给Tomcat配置gzip压缩(HTTP压缩)功能。
HTTP 压缩可以大大提高浏览网站的速度,它的原理是,在客户端请求网页后,从服务器端将网页文件压缩,再下载到客户端,由客户端的浏览器负责解压缩并浏览。相对于普通的浏览过程HTML,CSS,Javascript , Text,它可以节省40%左右的流量。更为重要的是,它可以对动态生成的,包括CGI、PHP , JSP , ASP ,Servlet,SHTML等输出的网页也能进行压缩,压缩效率惊人。
1)compression="on" 打开压缩功能
2)compressionMinSize="2048" 启用压缩的输出内容大小,这里面默认为2KB
3)noCompressionUserAgents="gozilla,traviata" 对于以下的浏览器,不启用压缩
4)compressableMimeType="text/html,text/xml" 压缩类型
最后不要忘了把8443端口的地方也加上同样的配置,如果我们走https协议的话,我们将会用到8443端口这个段的配置,参数跟以上一样,这里就不再写出来了。
好了,我们的tomcat优化到这里就完成了,相信这样做下来,优化过的tomcat要比未经过优化的,性能提高20~60倍,有兴趣的童鞋可以照着做一下,顺便用LR或者ab测一下效果。
四小猫飞起来了吗
为了简便,这里用轻量级测试工具Jmeter模拟150000个线程请求,试一下。
优化之前
优化之后
真变成飞猫了!
在生产环境可以结合apache也做次优化,如果再使用了负载均衡,那么系统的整体性能就更高了。
之前使用tomcat方面是不是弱爆了?