对于Linux服务器以及云主机的运维实践而言,“假死”这种现象,真可谓是众多极易使人心烦意乱,极为棘手并让人头疼不已的问题当中的其中一个呀。

服务器看上去运行得蛮平稳的,进程是存在的,CPU以及内存的占用情况也正常,然而业务就是没办法响应,特别是那基于Java/Tomcat或者Nginx的Web服务,常常会出现浏览器一直不停地转圈,却又没有任何报错的状况。

结合二零二三年年末的实战经历,本文会深度解析这类故障的根源,并且围绕Linux内核参数优化、云服务器设置、以及Docker容器化部署环境当中的解决办法展开,给出一套能够实际应用的优化策略。

一、故障复盘:现象与初步排查

于一回高并发的搜人项目进行压测期间,服务器呈现出了典型的“假死”状况:

1. 进程所处状态为存活,借助通过执行命令ps aux | grep java这一方式来进行检查,发现Java进程,像Tomcat这类的,仍旧处于运行的状态。

2. 资源闲置:通过top命令显示,CPU消耗处于低位。通过free -m命令显示,内存消耗处于低位。如此的消耗情况,远未达到物理瓶颈。

3. 端口堆积:运用 netstat -antp | grep 8080 来查看,发觉大量连接正处于 CLOSE_WAIT 状态,并且存在众多 TIME_WAIT 或者 ESTABLISHED 连接出现堆积现象。

4. 访问异常: 在前端页面进行刷新,浏览器那里却不存在4xx以及5xx错误代码,只是单单提示加载失败,或者一直处于等待响应的状态。

5. 日志截断:去查看应用日志来着,像 catalina.out 这种 ,出现了 java.lang.OutOfMemoryError: unable to create new native thread 这种情况 ,或者出现了与句柄相关的错误 ,这就暗示着连接数已经满了。

重新启动服务器,或者重新启动应用之后,服务马上就恢复了,可是,伴随着访问数量的增多,问题又一次出现了。

这充分说明是软件配置层面的瓶颈,而非硬件故障。

二、核心原因:Linux与Web容器的连接数之争

Linux是一切服务的基石。

传统的Linux服务器里,或者云主机当中,每一个TCP连接,都会耗费一个文件描述符,也就是File Descriptor,外加一个本地端口。

在我们把应用放置于Tomcat里,并且运用Nginx来进行反向代理之际,默认的配置常常采用HTTP/1.1协议,而且开启了长连接(Keep - Alive)。

对于Tomcat来说,它的默认里最大连接数也就是maxConnections,在BIO模式下,这个数值通常是大概200左右,而在NIO模式中,虽然这个数值是10000,不过它会受到操作系统的限制。

要是连接相关的生命周期被设置得过长,举例来说,像Tomcat默认的connectionTimeout是20秒,而Nginx的keepalive_timeout为65秒甚至还要更高,一旦在短时间之内涌入了超过最大承载数量的请求数量,并且这些连接并没有及时地被回收掉,那么新的请求就会被阻塞在队列之外,呈现出一种“假死”的状态。

三、落地实操:三层优化方案

朝着上述问题,我们得从操作系统内核这个方面,以及中间件配置这个方面,还有Docker容器环境这个方面,这三个维度去开展深度调优。

方案一:优化Linux内核参数(云服务器通用)

服务器Tomcat排查_Tomcat假死解决方案_Tomcat假死原因分析

/etc/sysctl.conf 文件予以编辑,针对TCP/IP协议栈参数作出调整,达成连接回收以及复用速度的加快。

# 开启重用。
允许将TIME-WAIT sockets重新用于新的TCP连接
net.ipv4.tcp_tw_reuse = 1
# 开启TCP连接中TIME-WAIT sockets的快速回收(注:Linux 4.12内核后已移除该参数,改用tcp_tw_reuse即可)
# net.ipv4.tcp_tw_recycle = 0  # 不建议开启,尤其在NAT环境下容易导致问题
# 降低FIN-WAIT-2状态的超时时间,让系统更快关闭无效连接
net.ipv4.tcp_fin_timeout = 15
# 增加本地端口范围,为高并发连接提供更多可用端口
net.ipv4.ip_local_port_range = 1024 65000
# 增大系统文件描述符限制,支持更大并发
fs.file-max = 1000000
# 保存后执行生效
sysctl -p
方案二:调整Tomcat/SpringBoot内嵌容器连接参数

无论是不是传统情况下的Tomcat部署,又或者是SpringBoot(其内置Tomcat),都得去修改最大连接数,以及超时时间。

server.xml 进行修改,或者 对 application.yml 进行修改


<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="5000"  
maxConnections="1000"      
maxThreads="400"            <!-- 最大工作线程数,通常maxThreads 
acceptCount="200"           
keepAliveTimeout="5000"     
maxKeepAliveRequests="100"/> 

对于SpringBoot这个项目而言,在名为application.properties的文件里进行配置:

server.tomcat.max-connections=1000
server.tomcat.max-threads=400
server.tomcat.accept-count=200
server.tomcat.connection-timeout=5000
server.tomcat.keep-alive-timeout=5000

protocol="org.apache.coyote.http11.Http11NioProtocol"

方案三:Docker容器部署的特别注意事项

要是处在依照Docker环境来实施运行的状况下,除掉应用配置这方面之外,还得去留意关注容器的资源限制相关问题,以及宿主机端口映射方面的问题。


有这样一种情况,即“ulimit限制”,在运行容器这个行为发生的时候,一定要加上“--ulimit”这个参数,不然的话,容器内部默认的“nofile”(也就是文件描述符),它有可能会过小。

docker run -d --name myapp 
--ulimit nofile=65535:65535 
-p 8080:8080 
your-image:latest

“内核参数继承”,Docker容器会共享宿主机内核,因此,“方案一”的调优对于容器而言同样是有效的。

四、总结

Linux服务器“假死”的本质是连接资源枯竭。

vi /etc/sysctl.conf
# Decrease the time default value for tcp_fin_timeout connection
net.ipv4.tcp_fin_timeout = 30
# Decrease the time default value for tcp_keepalive_time connection
net.ipv4.tcp_keepalive_time = 1800
# 探测次数
net.ipv4.tcp_keepalive_probes=2
# 探测间隔秒数
net.ipv4.tcp_keepalive_intvl=2
 
编辑完 /etc/sysctl.conf,要重启network 才会生效
[root@temp /]# /etc/rc.d/init.d/network restart

解题的思路不应当仅仅只是停留在重新启动服务之上,而是应当以一种系统的方式去进行调整:从Linux内核端口所处范围及回收机制开始着手,接着深入到中间件的连接池以及超时策略方面,最后再涉及到容器环境的资源限制之上。

经过上述三个步骤的优化,不管是安放在裸金属服务器之中,或者是置于云主机之上,又或者是处于K8s/Docker环境里面,均能够切实规避因连接堆积而引发的“假死”故障,从而保障业务具备高可用性。