服务器运行得好好的,突然告警短信就来了,提示连接数飙升到好几万。登录服务器一看,平时只有几百个ESTABLISHED状态的连接,现在直接破万,CPU的软中断占用率也跟着往上窜。这种情况在CentOS系统上其实很常见,但如果不及时处理,轻则服务响应变慢,重则直接耗光内存导致OOM Killer开始杀进程,甚至整个服务器失联。下面直接讲排查步骤和处置方法,不讲虚的。
第一步:快速确认连接数总量和大致分布先别急着查哪个进程,第一件事是把当前连接数的全貌看清楚。执行ss -s可以快速看到TCP连接的总览统计,这个命令比老旧的netstat快得多,在连接数几万的时候优势尤其明显。输出里会直接告诉你已建立的TCP连接有多少,TIME-WAIT有多少。如果TIME-WAIT状态的连接占了绝大多数,那通常是服务器作为客户端发起了大量短连接,并且没有及时回收端口。如果ESTABLISHED数量异常高,那就要往被攻击或者后端服务阻塞的方向去想。
接下来用ss -tan state established列出所有已建立连接,配合awk和sort做聚合分析。比如ss -tan state established | awk '{print $4}' | awk -F: '{print $NF}' | sort | uniq -c | sort -rn这条命令能按本地端口统计连接数,一眼就能看出是哪个服务端口在承受压力。如果某个端口连接数特别突出,比如80或443端口有上万个连接,那问题就锁定在Web服务上了。
第二步:按进程维度定位连接来源知道哪个端口出问题之后,需要把进程揪出来。ss -tnp state established可以直接显示每个连接对应的进程PID和进程名。如果连接数实在太多,ss的输出可能会比较慢,这时候可以先用ss -tnp | wc -l确认一下总量,再用grep过滤出目标端口。比如ss -tnp state established '( dport = :80 or sport = :80 )'就能只看80端口相关的连接,同时显示进程信息。
还有一种情况是连接数暴涨但ss -tnp里看到的进程名是"-",这通常是因为进程权限不够,需要用root权限去查看。另外,如果看到大量连接集中在某个Java或Node.js进程上,而且远程地址来自同一个IP段,那很可能是遭遇了CC攻击或者爬虫抓取。如果远程地址分散但都是正常客户端,那就要怀疑是后端处理能力不足导致连接堆积。
第三步:深入分析连接状态和内核参数连接数暴涨只是表象,背后往往和TCP内核参数配置有关。用ss -tan可以按状态分类查看所有TCP连接。重点关注SYN_RECV状态的数量,如果这个值很高,说明服务器收到了大量SYN包但没完成三次握手,典型的SYN Flood攻击特征。如果是TIME_WAIT状态特别多,比如超过3万,那说明服务器主动关闭了大量连接,需要检查是否频繁创建短连接去访问外部服务,比如数据库、缓存或者第三方API。
这个时候需要检查几个关键的内核参数。sysctl net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle在CentOS 7及以上的内核中,tcp_tw_recycle已经被移除了,因为它在NAT环境下会导致连接失败。所以正确的做法是开启tcp_tw_reuse,同时调整net.ipv4.tcp_fin_timeout,把默认的60秒适当调低,比如改成30秒,让TIME_WAIT状态的连接更快回收。另外net.ipv4.ip_local_port_range这个参数决定了可用端口范围,如果服务器作为客户端发起大量连接,端口范围太小会导致端口耗尽,可以改成1024到65535。
net.core.somaxconn和net.ipv4.tcp_max_syn_backlog这两个参数也很关键。somaxconn控制的是监听队列的最大长度,如果服务端accept的速度跟不上新连接到达的速度,队列满了之后新的SYN包就会被丢弃。很多运维人员只改了应用层的backlog参数,比如Nginx里的backlog配置,却忘了改内核的somaxconn,导致实际生效的队列长度被内核截断。用ss -lnt看监听端口的Send-Q和Recv-Q,Recv-Q代表当前等待accept的连接数,如果这个值持续接近甚至等于Send-Q,那就说明队列快满了,需要加大somaxconn。
第四步:排查应用层和外部依赖内核层面排查完之后,如果没发现明显异常,问题大概率出在应用层。最常见的情况是后端服务处理能力跟不上,比如PHP-FPM的进程数打满了,或者数据库连接池耗尽,导致Web服务器层面的连接无法及时释放。这时候用strace跟踪一下目标进程的系统调用,看是否在read或write上阻塞。比如strace -p PID -e trace=network -c可以统计网络相关的系统调用耗时,如果发现recvfrom或sendto的调用时间很长,说明数据交互环节有延迟。
如果是Nginx作为反向代理的场景,需要检查upstream模块的连接情况。Nginx和上游服务器之间默认使用HTTP/1.1长连接,但如果上游服务器没有正确配置keepalive,或者keepalive_timeout设置不合理,会导致连接频繁重建。查看Nginx的错误日志,如果出现大量"upstream timed out"或者"connect() failed"的记录,那就说明上游扛不住了。这时候可以临时调大Nginx的proxy_connect_timeout和proxy_read_timeout,但根本解决方案还是要扩容上游或者优化代码逻辑。
还有一种容易被忽略的情况是DNS解析导致的连接堆积。如果服务器上运行着大量需要调用外部域名的服务,而DNS服务器响应变慢,每个解析请求都可能阻塞几秒甚至更久,线程或进程全卡在DNS解析上,新的连接自然就堆积起来了。用nslookup或者dig测试一下DNS响应时间,如果超过1秒就不正常。可以考虑在本地部署dnsmasq做DNS缓存,或者把外部域名的解析结果写进/etc/hosts来应急。
第五步:应急止血和流量分析排查的同时不能忘了应急处理。如果确认是攻击流量,最直接的办法是在防火墙层面做限制。用iptables的recent模块可以对单个IP的连接数做限制,比如iptables -I INPUT -p tcp --dport 80 -m connlimit --connlimit-above 50 -j DROP这条规则限制每个IP对80端口的并发连接数不超过50个。但connlimit模块在高并发场景下对性能有一定影响,更推荐用ipset配合iptables来做黑名单管理,效率更高。
如果服务器上装了iftop或者nethogs,可以实时看到是哪些IP在占用大量带宽。iftop -n -P能显示到具体端口的流量,nethogs则直接按进程显示流量。有时候连接数虽然高,但每个连接传输的数据量很小,这种往往是空连接攻击;如果每个连接都在持续传输数据,那可能是正常业务流量增长,需要扩容而不是限流。
tcpdump在关键时刻是终极武器。抓包命令tcpdump -i eth0 -s 0 -w capture.pcap port 80抓取80端口的流量,然后用wireshark或者tcpdump -r capture.pcap | awk '{print $3}' | sort | uniq -c | sort -rn做离线分析。重点看SYN包的比例,如果SYN包占比超过50%,基本可以确定是SYN Flood。如果RST包很多,说明有大量连接被异常重置,可能是应用层主动断开或者中间网络设备在干扰。
第六步:长效优化和监控体系建设问题处理完之后,要把这些排查手段固化成监控指标。连接数相关的监控项至少应该包括:系统总连接数、各状态连接数分布、各端口连接数分布、各进程连接数。这些数据可以通过脚本定期采集,比如每分钟执行一次ss -s和ss -tan state established | wc -l,输出到监控系统里。设置合理的阈值,比如总连接数超过1万就告警,TIME_WAIT超过2万就告警。
应用层面也要做对应的优化。Web服务器尽量使用长连接,减少握手开销。Nginx里keepalive_timeout可以设成60秒,keepalive_requests设成1000。如果用了云平台的负载均衡,注意负载均衡器自身的连接超时时间,避免因为超时时间不一致导致连接状态混乱。后端服务要合理配置连接池大小,数据库连接池不要超过数据库最大连接数的80%,留出余量给管理连接和临时查询。
最后,/etc/sysctl.conf里关于网络优化的参数建议做一次全面梳理。除了前面提到的tcp_tw_reuse和tcp_fin_timeout,net.ipv4.tcp_max_tw_buckets控制的是系统允许的最大TIME_WAIT连接数,默认值通常够用,但如果业务场景特殊可以适当调大。net.ipv4.tcp_syncookies一定要开启,这是防御SYN Flood的有效手段。net.core.netdev_max_backlog控制的是网卡接收队列的长度,如果服务器流量很大,这个值可以调到2000甚至更高。
排查连接数暴涨的问题,本质上是在追查"连接从哪里来、卡在哪里、为什么没释放"这三个问题。只要沿着网络层、内核层、应用层这条链路逐层排查,配合ss、tcpdump、strace这几个核心工具,绝大多数情况都能在几分钟内定位到根因。
