Tomcat优化

更新:建议部署APM工具Pinpoint,比较直观分析性能。

分析

排查JVM

1
2
3
4
# jstat -gcutil $PID            注意GC次数,过多的GC会影响延迟
# jmap -heap $PID               观察各个分区的内存占用情况
# jmap -histo $PID | more       注意大对象或者对象实例较多的情况
# jstack $PID > jstack.txt      观察线程执行状态,出现线程Block,需要特别注意

ThreadDump没什么问题,GC没发现严重问题,只是觉得Eden区内存设置有点紧张

排查负载

1
2
3
4
5
6
# uptime        观察最近的负载
# free -g       还有可用内存,注意swap
# ps auxxwf     各个进程,包括JVM占用资源,可能其他进程的CPU负载过高也会影响响应的延迟,比如日志压缩脚本
# top           注:按下数字1,观察每个CPU核的负载
# ss -an | grep :8080 | wc -l   当前端口下的连接数,不区分状态
# netstat -natl | awk '{print $6}' | sort | uniq -c | sort -n   注意各个状态下的连接数,过多的wait连接影响新的请求进来

发现TIME_WAIT状态的连接比较多,接近配置的连接数上限了,怀疑没有配置keep-alive导致Tomcat主动关闭Connection。

执行主动关闭的一端进入TIME_WAIT,需要等2倍的MSL时间(通常1分钟)之后才能把状态改为CLOSED。具体参考HTTP连接状态总结

再结合Nginx和Tomcat的日志,开始着手调整。

优化

Nginx配置keep-alive

1
2
3
4
5
6
location / {
    proxy_pass tomcat-upstream;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    ...
}

JVM参数

在脚本中头部增加一行参数,这里针对JDK8版本的JVM,具体参考JVM参数设置及优化

1
2
3
# vim /usr/local/tomcat/bin/catalina.sh
JAVA_OPTS="$JAVA_OPTS -Xms4g -Xmx4g -Xmn2g -XX:SurvivorRatio=16 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m"
JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:MaxTenuringThreshold=4"

Connector协议由BIO调整为NIO

配置Connector

同时也参考 Tomcat 8 Configuration Reference 修改了线程数和Logs格式,便于统计平均响应时长。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# vim /usr/local/tomcat/conf/server.xml
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
            keepAliveTimeout="60000"
            maxKeepAliveRequests="1024"
            <!-- 非异步Servlet时与maxThreads保持一致 -->
            processorCache="128"
            maxThreads="128"
            <!-- 队列大小 -->
            acceptCount="64"
            <!-- 单位毫秒,请求连接超时,keepAliveTimeout参数未设置按此参数 -->
            connectionTimeout="2000"
            redirectPort="8443" />

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
            prefix="localhost_access_log" suffix=".txt"
            pattern="%h %l %u %t "%r" %s %D" />

这里通过jstack结果分析,决定线程maxThreads参数配置,同时也需要考虑栈空间的分配问题。
调整过程中也建议使用ab简单压测下,及时观察调整效果。