Tomcat实战中的微调

  Tomcat的缺省配置是不能稳定长期运行的,也就是不适合生产环境,它会死机,让你不断重新启动,甚至在午夜时分唤醒你。呵呵。

  通用的 Tomcat tuning 文章由Mark Thomas编写,见这里 here. 他描述了日志logging, connectors, 内容缓存和JVM (Java Virtual Machine) 如何微调,这里我们根据实战中经验总结如下:

1) JVM微调

  缺省的Tomcat提供的Java heap大小太小了,你可以使用JDK随带的jstate检查Tomcat情况:

tomcat@server$ jstat -gccapacity `ps -ef|grep Bootstrap|grep -v grep|gawk '{print $2}'`  NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       PGCMN    PGCMX     PGC       PC     YGC    FGC   2624.0  28672.0   4800.0  448.0  448.0   3904.0     5312.0    57344.0     9496.0     9496.0   21248.0  86016.0  21248.0  21248.0     19     2 tomcat@server$

  因此需要改变Tomcat的catalina.sh 配置CATALINA_OPTS

CATALINA_OPTS="-XmsNNNN -XmxNNNN -XX:NewSize=NNN -XX:MaxNewSize=NNN -XX:SurvivorRatio=N -XX:PermSize=NNN -XX:MaxPermSize=NNN " 

  有关JVM的说明配置可见:JVM微调,这里提供一个参考:

1GB heap 配置案例

CATALINA_OPTS=" -verbose:gc -XX:+PrintGCTimeStamps -Xms1024m -Xmx1024m -XX:NewSize=341m -XX:MaxNewSize=341m -XX:SurvivorRatio=2 -XX:PermSize=128m -XX:MaxPermSize=128m -XX:ThreadStackSize=512" 

2GB heap 配置案例

CATALINA_OPTS=" -verbose:gc -XX:+PrintGCTimeStamps -Xms2048m -Xmx2048m -XX:NewSize=682m -XX:MaxNewSize=682m -XX:SurvivorRatio=2 -XX:PermSize=128m -XX:MaxPermSize=128m -XX:ThreadStackSize=512" 

4GB heap 配置案例

CATALINA_OPTS=" -verbose:gc -XX:+PrintGCTimeStamps -Xms4096m -Xmx4096m -XX:NewSize=1364m -XX:MaxNewSize=1364m -XX:SurvivorRatio=2 -XX:PermSize=128m -XX:MaxPermSize=128m -XX:ThreadStackSize=512" 

 

2) Logging日志

  Tomcat缺省配置文件在$CATALINA_BASE/conf/logging.properties,这个配置对于生产环境不是很合适,首先我们移除其.handlers行的ConsoleHandler。缺省情况下日志每天大小不能超过2GB,如果超过就没有输出处理,下面是使用AsyncFileHandler 的Tomcat7 logging.properties配置样本:

 

handlers = 1catalina.org.apache.juli.AsyncFileHandler, 2localhost.org.apache.juli.AsyncFileHandler, 3manager.org.apache.juli.AsyncFileHandler, 4host-manager.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler

 

.handlers = 1catalina.org.apache.juli.AsyncFileHandler

 

############################################################

# Handler specific properties.

# Describes specific configuration info for Handlers.

############################################################

 

1catalina.org.apache.juli.AsyncFileHandler.level = FINE

1catalina.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs

1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina.

 

2localhost.org.apache.juli.AsyncFileHandler.level = FINE

2localhost.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs

2localhost.org.apache.juli.AsyncFileHandler.prefix = localhost.

 

3manager.org.apache.juli.AsyncFileHandler.level = FINE

3manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs

3manager.org.apache.juli.AsyncFileHandler.prefix = manager.

 

4host-manager.org.apache.juli.AsyncFileHandler.level = FINE

4host-manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs

4host-manager.org.apache.juli.AsyncFileHandler.prefix = host-manager.

 

java.util.logging.ConsoleHandler.level = FINE

java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

 

 

############################################################

# Facility specific properties.

# Provides extra control for each logger.

############################################################

 

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.AsyncFileHandler

 

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = 3manager.org.apache.juli.AsyncFileHandler

 

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level = INFO

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers = 4host-manager.org.apache.juli.AsyncFileHandler

 

# For example, set the org.apache.catalina.util.LifecycleBase logger to log

# each component that extends LifecycleBase changing state:

#org.apache.catalina.util.LifecycleBase.level = FINE

 

# To see debug messages in TldLocationsCache, uncomment the following line:

#org.apache.jasper.compiler.TldLocationsCache.level = FINE

 

  异步日志也可以在 Java系统文件中配置,可见Apache Tomcat 7 System Properties.下面是加入catalina.sh的CATALINA_OPTS样本:

 

# JVM tuning

CATALINA_OPTS="-Dcom.sun.management.jmxremote -XX:+UseSerialGC -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xms512m -Xmx512m -XX:NewSize=170m -XX:MaxNewSize=170m -XX:SurvivorRatio=2 -XX:PermSize=64m -XX:MaxPermSize=64m"

 

###

# Async logging parameters.

#

# http://tomcat.apache.org/tomcat-7.0-doc/config/systemprops.html#Logging

#

CATALINA_OPTS=${CATALINA_OPTS}" -Dorg.apache.juli.AsyncOverflowDropType=1 -Dorg.apache.juli.AsyncMaxRecordCount=20000 -Dorg.apache.juli.AsyncLoggerPollInterval=2000"

...

3) Connectors

  首先确认你使用Tomcat的哪个连接器,Tomcat提供两个连接器,一个是AJP连接器,缺省监听TCP/8009端口,另外一个是HTTP连接器,缺省端口是*)*),编辑$CATALINA_BASE/conf/server.xml之前,你得确认你的系统是哪个连接器在运行:

user@server$ netstat -ant|grep 8009|grep ESTABLISHED|grep -v grep|wc -l
156

user@server$ user@server$ netstat -ant|grep 8080|grep ESTABLISHED|grep -v grep|wc -l
0

  上面表示有AJP连接156,实际是156/2=78,这是因为上述统计将前端Web服务器Apache的连接重复统计了,这里没有使用HTTP(8080端口)

  下面是server.xml中连接器部分一些配置:

  maxThreads : 最大线程数,缺省是200,这是配置你的应用并发有最大多少连接,越大会耗费内存和CPU,因为CPU疲于线程上下文切换,没有精力提供请求服务了,在200到800之间,推荐400。明智的办法是将图片CSS等静态文件移植到Tomcat以外,使用Apache和Nginx服务,如果静态文件很多很大,配置Tomcat的context.xml,缺省是服务静态文件大小是10M,可以改为60M,刷新时间延迟到60s。

  maxConnections: 最大连接数,这个数值取决于你使用堵塞还是非堵塞连接器,堵塞的是BIO, 那么这个数值是BIO的最大线程数,如果是NIO,Java非堵塞I/O连接器,那么这个数值是1000,如果使用C原生非堵塞连接器APR,那么这个数值是8192。那么如何知道我们系统使用哪个连接器类型呢?缺省是在APR和BIO之间切换,如果在启动运行时,你配置编译了APR,APR库包被Tomcat发现,那么就采取APR连接类型,否则就是要BIO。APR虽然好,但是稳定性不是很强,其SSL使用OpenSSL,而使用NIO,可以支持Java自身的SSL。

  那么如何知道APR被tomcat发现?在日志$CATALINA_BASE/logs/catalina.out可以找到APR found等输出。

  请注意,如果前端使用Apache/Nginx,要保证前端的最大客户端MaxClient等设置大于Tomcat的maxConnection。

  connectionTimeout :缺省是60000毫秒,也就是60秒,这个值是考虑拨号上网情况,太大了,生产环境一般宽带可设置为2000ms=2秒。

  acceptCount :这是Tomcat的线程如果都忙于响应,再来新的连接只好进入队列排队,缺省是100,也就是可以有100个请求排队,如果超过100个就拒绝连接,用户浏览器会得到无响应等问题,建议将这个值设置大。

  maxKeepAliveRequests: 每个TCP连接接受最大的Http请求数目,当处理一个keep alive请求达到这个最大值,Tomcat关闭这个连接,设置1为失效任何keep alive请求,对于BIO高并发,四层负载平衡和NoSSL情况需要失效;对于SSL APR/NIO 7层负载平衡需要激活,设置为-1是不限制,缺省为100。

  现在我们详细描述一下Tomcat连接器在不同模式下工作原理:

  前端Apache/Nginx等客户端向Tomcat连接器提交请求,一个堵塞的连接器BIO,这是最稳定最老的一个连接器,堵塞意味着每个连接线程绑定到每个Http请求,直到获得Http响应返回,如果Http客户端请求的是keep-Alive连接,那么这些连接也许一直保持着直至达到timeout时间,这期间不能用于其它请求。

  另外一个连接器是APR,这是使用原生C语言编写的非堵塞I/O,但是需要编译,但是稳定性不高,另外一个NIO是比原生APR稳定,纯Java解决方案,下面是对照表:

tomcat连机器

 

Java性能微调之数据库性能

Tomcat tuning

Spring Tomcat Tuning

NIO原理