背景
之前接手了前同事维护的一个网站服务,下午收到通知说是网站无法访问,测试访问现象:长时间无响应之后报错502,502报错是后端处理不了请求,架构是nginx+tomcat,登陆排查
- catalina.out无明显报错
- 系统日志message没有报错
- jstack命令不存在所以看不到jvm里线程的状态
- 内存、cpu、io、磁盘空间都是正常
- ping服务器也是正常,网络没问题
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
查看tcp连接状态发现出现大量CLOSE_WAIT
CLOSE_WAIT
所谓 CLOSE_WAIT,借用某位大牛的话来说应该倒过来叫做 WAIT_CLOSE,也就是说「等待关闭」,TCP关闭连接时的图例:
首先主动关闭的一方发出 FIN 包,被动关闭的一方响应 ACK 包,此时,被动关闭的一方就进入了 CLOSE_WAIT 状态。如果一切正常,稍后被动关闭的一方也会发出 FIN 包,然后迁移到 LAST_ACK 状态。
通常,CLOSE_WAIT 状态在服务器停留时间很短,如果你发现大量的 CLOSE_WAIT 状态,那么就意味着被动关闭的一方没有及时发出 FIN 包,一般有如下几种可能:
- 程序问题:如果代码层面忘记了 close 相应的 socket 连接,那么自然不会发出 FIN 包,从而导致 CLOSE_WAIT 累积;或者代码不严谨,出现死循环之类的问题,导致即便后面写了 close 也永远执行不到。
- 响应太慢或者超时设置过小:如果连接双方不和谐,一方不耐烦直接 timeout,另一方却还在忙于耗时逻辑,就会导致 close 被延后。响应太慢是首要问题,不过换个角度看,也可能是 timeout 设置过小。
- BACKLOG 太大:此处的 backlog 不是 syn backlog,而是 accept 的 backlog,如果 backlog 太大的话,设想突然遭遇大访问量的话,即便响应速度不慢,也可能出现来不及消费的情况,导致多余的请求还在队列里就被对方关闭了。
解决办法
看到服务器之前并没有做过优化,决定先优化下再观察一段时间
1、优化文件打开数
调整系统最大文件打开数
- 临时生效:ulimit -n 65535
- 永久生效:
1
2
3
cat /etc/security/limits.conf
* soft nofile 32768 #限制单个进程最大文件句柄数
* hard nofile 65536 #限制单个进程最大文件句柄数
- 修改整个系统最大文件句柄数:
1
2
vim /etc/sysctl.conf
fs.file-max=655350 #限制整个系统最大文件句柄数
2、优化tomcat配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
maxConnections="3000"
maxThreads="400"
minSpareThreads="200"
redirectPort="8443"
enableLookups="false"
acceptCount="100"
maxPostSize="10485760"
compression="on"
disableUploadTimeout="true"
compressionMinSize="2048"
acceptorThreadCount="8"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript"
URIEncoding="utf-8" />
- connnectionTimeout:网络连接超时,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为20000毫秒
- maxConnections:当Tomcat接收的连接数达到maxConnections时,Acceptor线程不会读取accept队列中的连接;这时accept队列中的线程会一直阻塞着,直到Tomcat接收的连接数小于maxConnections
- maxThreads:Tomcat线程池最多能起的线程数
- minSpareThreads:Tomcat初始化的线程池大小或者说Tomcat线程池最少会有这么多线程
- enableLookups:是否允许DNS查询,为了提高处理能力,应设置为false
- acceptCount:指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,就是被排队的请求数,超过这个数的请求将拒绝连接。
- maxPostSize:对post请求参数大小做出限制
- compression:打开压缩功能
- disableUploadTimeout:对POST请求发送数据超时使用其他参数来设置,这样在发送数据的过程中最大可以等待的时间间隔就不再由connectionTimeout决定,而是由connectionUploadTimeout决定
- compressionMinSize:启用压缩的输出内容大小,默认为2KB
- acceptorThreadCount:接收Socket连接的线程数。默认值是1,这个值不需要太大,最大值与CPU核心数一样就行了,没有必要太大
- compressableMimeType:压缩的类型
3、调整内核参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
net.ipv4.ip_local_port_range = 1024 65535
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0
net.core.netdev_max_backlog = 30000
net.ipv4.tcp_no_metrics_save = 1
net.core.somaxconn = 22144
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2
vm.overcommit_memory = 1
fs.file-max = 2000000
fs.nr_open = 2000000
net.ipv4.tcp_syncookies = 1
net.ipv4.ip_forward=1
- net.ipv4.ip_local_port_range:定义了网络连接可用作其源(本地)端口的最小和最大端口
- net.core.rmem_max:表示接受套接字缓冲区大小的最大值
- net.core.wmem_max:表示发送套接字缓冲区大小的最大值
- net.ipv4.tcp_rmem:自动调优所使用的接收缓冲区的值
- net.ipv4.tcp_wmem:自动调优所使用的发送缓冲区的值
- net.ipv4.tcp_fin_timeout:对于本端断开的socket连接,TCP保持在FIN_WAIT_2状态的时间
- net.ipv4.tcp_tw_recycle:启用TIME-WAIT状态sockets的快速回收,这个选项不推荐启用。在NAT(Network Address Translation)网络下,会导致大量的TCP连接建立错误
- net.ipv4.tcp_timestamps:不再检查socket时间戳
- net.ipv4.tcp_sack:启用有选择的应答(1表示启用),通过有选择地应答乱序接收到的报文来提高性能,让发送者只发送丢失的报文段,(对于广域网通信来说)这个选项应该启用,但是会增加对CPU的占用
- net.core.netdev_max_backlog:在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目
- net.ipv4.tcp_no_metrics_save:TCP 默认缓存了很多连接指标,如果设置的话,TCP 在关闭的时候不缓存这些指标