你的服务器做过体检么?一份真实的 CentOS 高并发服务器体检与优化实录

你的服务器做过体检么?一份真实的 CentOS 高并发服务器体检与优化实录

服务器跑起来之后,你多久会去看一次它?

很多人的答案可能是:只要服务没挂,我就不看。

我以前也差不多。CPU 没爆、内存没 OOM、磁盘还有空间、接口能正常访问,就觉得服务器状态应该没什么问题。

直到最近对几台线上 CentOS 服务器做了一次比较完整的"体检",我才发现一个很容易被忽略的问题:

服务器现在正常,不代表它在业务高峰期也正常。

有一台服务器检查时 CPU Load 只有 0.01 左右,TCP 状态看起来也很平稳,但继续往底层翻统计数据,却发现系统历史上已经出现了接近 30 万次 SYN 丢弃,内核甚至明确记录过:

text 复制代码
TCP: request_sock_TCP: Possible SYN flooding on port 80.
Sending cookies. Check SNMP counters.

也就是说,服务器表面风平浪静,底层其实早就"咳嗽"过很多次了。

这篇文章就拿这次真实排查过程,聊聊一台 Linux 服务器到底应该怎么体检,以及发现问题之后哪些参数值得改,哪些参数不要跟着网上所谓的"百万并发模板"乱调。


一、服务器体检,到底应该检查什么?

平时大家登录服务器,最常执行的大概就是:

bash 复制代码
top
free -h
df -h

这三个命令当然重要,但如果服务器承担 Web API、Nginx、Java、PHP、Redis、MySQL 等服务,仅看这些远远不够。

我这次把检查内容大致分成了六类:

  1. 文件句柄和进程线程限制
  2. TCP 内核参数
  3. 当前 TCP 连接状态
  4. 网卡和网络流量
  5. CPU、内存、进程资源
  6. TCP 丢包、Overflow、Reset、OOM 等历史异常

真正有意思的往往是最后一项。

因为 CPU 和内存告诉你的更多是:

服务器现在怎么样。

netstat -sdmesg 这些数据告诉你的则可能是:

服务器过去到底发生过什么。

这两件事差别很大。


二、第一眼看,这台服务器甚至非常"健康"

这台机器配置不算特别高:

text 复制代码
CentOS 7
Kernel 3.10
内存:7.4G
Swap:0

检查时 CPU Load:

text 复制代码
load average: 0.01, 0.04, 0.08

这是什么概念?

基本没什么 CPU 压力。

内存情况:

text 复制代码
total        7.4G
used         5.5G
available    1.6G

虽然内存余量不算特别宽裕,但也没有发生 OOM。

文件句柄:

text 复制代码
ulimit -n = 65535
fs.file-max = 759020
file-nr = 3232

系统最多允许 75 万左右文件句柄,实际才用了三千多个,这里同样没有瓶颈。

如果检查到这里就结束,很容易得出一个结论:

服务器挺健康,不需要优化。

但继续往 TCP 层看,情况就完全不一样了。


三、真正的问题藏在 TCP 历史统计里

当时这台服务器实时 TCP 状态大概是:

text 复制代码
TCP: 1076
ESTABLISHED: 1046
TIME_WAIT: 8
CLOSE_WAIT: 7

一千多个 ESTABLISHED 对线上服务器并不是什么特别离谱的数据。

TIME_WAIT 只有个位数,也完全正常。

但继续执行:

bash 复制代码
netstat -s

然后筛选 TCP 异常:

bash 复制代码
netstat -s | grep -iE "overflow|drop|reset|listen"

结果就比较有意思了:

text 复制代码
743705 connection resets received
4984066 resets sent
367106 resets received for embryonic SYN_RECV sockets
299729 SYNs to LISTEN sockets dropped
709862 connections reset due to early user close
TCPBacklogDrop: 2

其中最值得关注的是:

text 复制代码
299729 SYNs to LISTEN sockets dropped

接近 30 万次

再看 dmesg,直接找到了:

text 复制代码
TCP: request_sock_TCP:
Possible SYN flooding on port 80.
Sending cookies.
Check SNMP counters.

这下问题就很明确了。

这台服务器虽然检查的时候很闲,但历史业务高峰期间,80 端口的新 TCP 连接曾经给服务器造成过明显压力。

这就是为什么我现在越来越觉得:

服务器体检不能只看实时 CPU 和内存。

很多问题都是瞬时发生的。你晚上八点检查服务器可能一切正常,但下午两点业务高峰的时候,TCP 队列可能已经爆过一次了。


四、继续往下查,问题就找到根了

检查服务器 TCP 参数:

bash 复制代码
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.core.netdev_max_backlog

结果:

text 复制代码
net.core.somaxconn = 1024
net.ipv4.tcp_max_syn_backlog = 1024
net.core.netdev_max_backlog = 1000

这些参数简单理解:

tcp_max_syn_backlog

客户端访问:

text 复制代码
客户端
   ↓ SYN
服务器
   ↓
SYN_RECV
   ↓ ACK
ESTABLISHED

三次握手还没有完成的时候,连接需要暂存在 SYN 队列。

这个队列太小,而瞬间又来了大量新连接,就可能出现:

text 复制代码
SYNs to LISTEN sockets dropped

这次服务器正好已经留下了大量历史记录。

somaxconn

somaxconn 控制的是应用 listen() 队列能够使用的系统级上限。

服务器原来:

text 复制代码
somaxconn = 1024

对普通小网站其实不一定有问题。

但这台服务器同时运行:

text 复制代码
Nginx
PHP-FPM
Java
Redis
MySQL

并且历史上已经明确发生过 SYN drop,那么继续保持 1024 就没有什么必要了。

最终我把这几个参数调整为:

text 复制代码
net.core.somaxconn = 8192
net.ipv4.tcp_max_syn_backlog = 8192
net.core.netdev_max_backlog = 8192

这里我没有照着网上一些文章直接搞:

text 复制代码
65535
262144
500000

原因很简单:

系统优化不是数字越大越好。

从 1024 调整到 8192,是因为已经有真实数据证明队列曾经承受过压力。

如果服务器一天只有几百个访问,把参数调到几十万并不会让接口突然快十倍。


五、TIME_WAIT 参数也发现了一个坑

服务器原来的配置:

text 复制代码
tcp_max_tw_buckets = 5000
tcp_fin_timeout = 60
tcp_tw_reuse = 0

tcp_max_tw_buckets=5000 对这种线上混合服务器明显偏保守。

于是调整为:

text 复制代码
net.ipv4.tcp_max_tw_buckets = 65536
net.ipv4.tcp_fin_timeout = 30

但是有一个参数我没有动:

text 复制代码
tcp_tw_reuse = 0

原因是这台服务器运行的是:

text 复制代码
CentOS 7
Linux Kernel 3.10

而另外一台新服务器运行的是 Linux 5.14,tcp_tw_reuse 在不同年代内核中的行为和可用值不能简单照搬。

尤其是网上经常能看到这种"祖传 Linux 优化配置":

text 复制代码
tcp_tw_reuse = 1
tcp_tw_recycle = 1

看到这种配置最好先停一下。

特别是 tcp_tw_recycle,在 NAT、负载均衡等环境下曾经带来过很多莫名其妙的连接问题,后来的 Linux 内核也已经将其移除。

所以我的原则一直是:

不知道一个参数为什么要改,就先不要改。


六、临时端口范围也值得注意

服务器原来的:

text 复制代码
ip_local_port_range = 32768 60999

大约只有 2.8 万个临时端口。

很多人认为 Web 服务器只负责"接收连接",其实现在的业务服务器往往同时也是客户端。

例如:

text 复制代码
用户
 ↓
Nginx
 ↓
Java
 ↓
Redis / MySQL / 其他微服务 / 第三方API

Java 调第三方 HTTP API、PHP 请求内部服务、Nginx 反向代理后端,本质上都可能产生主动 TCP 连接。

因此最终调整:

text 复制代码
net.ipv4.ip_local_port_range = 10000 65535

把可使用范围扩大到 5 万多个。

当然,真正解决大量短连接问题,优先级更高的依然应该是:

text 复制代码
HTTP KeepAlive
数据库连接池
Redis连接池
RPC长连接
连接复用

而不是只想着靠 Linux 参数兜底。


七、KeepAlive 默认 7200 秒,也有点太佛系了

原来的参数:

text 复制代码
tcp_keepalive_time = 7200
tcp_keepalive_intvl = 75
tcp_keepalive_probes = 9

简单理解就是:

一个 TCP 连接长时间没有数据,Linux 默认要等很久才开始主动探测它是不是已经失效。

这台服务器长期存在一千多个 ESTABLISHED,所以我最终调整为:

text 复制代码
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

也就是更加积极地清理真正失效的长连接。

但这里同样要提醒:

Linux KeepAlive 不是万能药。

如果你发现:

text 复制代码
CLOSE_WAIT

持续几百、几千地上涨,那么更应该检查应用程序有没有正确关闭 socket,而不是继续修改 sysctl。

这次服务器 CLOSE_WAIT 只有 7 个,所以暂时没有这个问题。


八、Socket Buffer 也顺手调整了

服务器默认:

text 复制代码
net.core.rmem_max = 212992
net.core.wmem_max = 212992

也就是大约 208KB。

最终调整:

text 复制代码
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 16384 16777216

最大提高到 16MB。

这里很容易产生一个误区:

1000 个 TCP × 16MB,那不是直接吃掉 16GB 内存?

并不是。

这里更多是给 TCP 自动调节提供一个更高的上限,不意味着每建立一个连接就立即分配 16MB。


九、最有意思的问题:参数明明改了,为什么还是 1024?

配置写完后,我执行:

bash 复制代码
sysctl --system

明明看到:

text 复制代码
net.core.somaxconn = 8192
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_max_tw_buckets = 65536

但重新检查:

bash 复制代码
sysctl net.core.somaxconn

居然还是:

text 复制代码
1024

一开始很容易怀疑:

是不是 CentOS 7 内核不支持?

是不是参数超过上限?

是不是需要重启?

后来仔细看 sysctl --system 的输出才发现,真正的问题非常简单:

配置被后面的文件覆盖了。

执行顺序里先加载:

text 复制代码
/etc/sysctl.d/99-high-concurrency.conf

设置:

text 复制代码
somaxconn = 8192

随后又加载:

text 复制代码
/etc/sysctl.d/99-sysctl.conf

重新变成:

text 复制代码
somaxconn = 1024

最后又加载:

text 复制代码
/etc/sysctl.conf

于是最终结果还是 1024。

继续检查:

bash 复制代码
ls -l /etc/sysctl.d/99-sysctl.conf

发现:

text 复制代码
/etc/sysctl.d/99-sysctl.conf -> ../sysctl.conf

原来 99-sysctl.conf 本身就是指向 /etc/sysctl.conf 的软链接。

再查:

bash 复制代码
grep -nE 'somaxconn|tcp_max_syn_backlog|tcp_max_tw_buckets|swappiness' \
/etc/sysctl.conf \
/etc/sysctl.d/*.conf

马上看出来 /etc/sysctl.conf 里面同时存在:

text 复制代码
somaxconn = 1024

和:

text 复制代码
somaxconn = 8192

两套参数。

最终把旧配置清理掉,只保留新的值,再重新加载:

bash 复制代码
sysctl -p

问题解决。

这个小插曲其实特别有代表性。

Linux 调优最怕的不是不会改,而是你以为自己改了。

任何 sysctl 修改完成之后,都应该重新执行:

bash 复制代码
sysctl net.core.somaxconn

确认当前内核真正使用的值。

不要只看到配置文件写成功就认为结束了。


十、最终优化后的服务器是什么状态?

再次运行体检脚本,核心参数已经变成:

text 复制代码
net.core.somaxconn = 8192
tcp_max_syn_backlog = 8192
net.core.netdev_max_backlog = 8192

tcp_fin_timeout = 30
tcp_max_tw_buckets = 65536

tcp_keepalive_time = 600
tcp_keepalive_intvl = 30
tcp_keepalive_probes = 5

rmem_max = 16777216
wmem_max = 16777216

ip_local_port_range = 10000 65535

kernel.pid_max = 262144

更值得关注的是优化前:

text 复制代码
299729 SYNs to LISTEN sockets dropped

优化之后再次检查:

text 复制代码
299729 SYNs to LISTEN sockets dropped

没有继续增长。

当然,这并不能仅凭十几分钟就宣布问题彻底解决,因为 netstat -s 统计的是系统启动以来的累计数据。

正确观察方法应该是:

bash 复制代码
date
netstat -s | grep -iE "SYNs to LISTEN|listen queue|TCPBacklogDrop|reset"

记录下来。

等业务高峰过去,再执行一次。

如果:

text 复制代码
299729
↓
299729

说明这段时间没有新增。

如果:

text 复制代码
299729
↓
310000

那说明仍然存在问题。

这时候就不能继续无脑:

text 复制代码
8192 → 16384 → 65535

而应该继续向应用层排查。


十一、Linux 调完以后,别忘了 Nginx

这次检查还有一个很典型的问题。

Linux 已经:

text 复制代码
somaxconn = 8192

但:

bash 复制代码
ss -lntp

看到 Nginx:

text 复制代码
LISTEN 0 511 *:80
LISTEN 0 511 *:443

也就是说操作系统允许 8192,但 Nginx 实际监听 backlog 还是 511。

继续检查:

bash 复制代码
nginx -T | grep -E "worker_processes|worker_connections|multi_accept"

发现:

text 复制代码
worker_processes auto;
worker_connections 51200;
multi_accept on;

Nginx worker 本身其实已经配置得不错。

下一步真正值得关注的是:

nginx 复制代码
listen 80 backlog=4096;
listen 443 ssl backlog=4096;

但这类配置不要直接用 sed 批量替换。

因为一台服务器往往存在多个虚拟主机,同时还有:

text 复制代码
listen 80;
listen [::]:80;
listen 443 ssl;

多个监听配置。

应该先:

bash 复制代码
nginx -T

找到真正的配置文件,再针对性调整。

否则性能没优化多少,反而先把 Nginx 配挂了。


十二、Java、PHP、Redis、MySQL同样存在自己的"队列"

这也是这次体检之后最大的一个感受:

高并发优化从来不是一个 sysctl.conf 就能解决。

比如这台机器的 Java:

text 复制代码
8080  LISTEN backlog=1000
18080 LISTEN backlog=100

8080 已经比较合理。

18080 如果是一个高流量 Spring Boot 服务,就应该继续检查:

properties 复制代码
server.tomcat.accept-count=1000
server.tomcat.threads.max=300
server.tomcat.threads.min-spare=20

但是线程也不是越多越好。

假如:

text 复制代码
Tomcat线程 = 1000
数据库连接池 = 30

大量线程最终还是堵在数据库连接池前面。

真正合理的链路应该是整体匹配:

text 复制代码
Linux TCP
     ↓
Nginx
     ↓
Tomcat
     ↓
业务线程
     ↓
数据库连接池
     ↓
MySQL

其中任何一层特别小,都可能成为整个系统的瓶颈。


十三、这次还顺便发现 Redis 监听所有网卡

体检结果:

text 复制代码
*:6379
redis-server

这意味着 Redis 监听所有 IPv4 地址。

它不一定真的暴露公网,因为外面可能还有安全组、防火墙。

但至少值得继续检查:

bash 复制代码
redis-cli CONFIG GET protected-mode
redis-cli CONFIG GET requirepass

如果 Redis 只给本机程序使用,完全可以考虑:

text 复制代码
bind 127.0.0.1
protected-mode yes

有时候服务器体检的价值就在这里。

本来是来查性能的,顺手把安全隐患也翻出来了。


十四、还有一个我最终没有忽略的问题:Swap

这台服务器:

text 复制代码
总内存:7.4G
已使用:5.5G
available:1.6G
Swap:0

而机器上同时跑:

text 复制代码
Nginx
PHP-FPM
Redis
MySQL
Java
宝塔

CPU 很闲,但内存其实没有想象中宽裕。

因此我的建议是增加 2GB 左右 Swap:

bash 复制代码
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

然后:

bash 复制代码
echo '/swapfile swap swap defaults 0 0' >> /etc/fstab

同时:

text 复制代码
vm.swappiness = 10

这里不是让 Java、MySQL 平时靠 Swap 跑。

Swap 在这种服务器上更像一道保险。

平时不用最好,但某个进程突然出现内存尖峰的时候,至少不要让 OOM Killer 第一时间出来随机"枪毙"进程。


十五、最后整理一份服务器体检清单

以后再检查 CentOS/Linux 服务器,我觉得至少可以关注下面这些东西。

文件句柄

bash 复制代码
ulimit -n
ulimit -Hn
cat /proc/sys/fs/file-nr
sysctl fs.file-max

重点防:

text 复制代码
Too many open files

TCP 连接

bash 复制代码
ss -s
ss -ant

重点关注:

text 复制代码
ESTABLISHED
TIME_WAIT
CLOSE_WAIT
SYN_RECV

TCP 队列

bash 复制代码
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.core.netdev_max_backlog

TCP 历史异常

bash 复制代码
netstat -s | grep -iE "overflow|drop|reset|listen"

这条我认为特别重要。

内核异常

bash 复制代码
dmesg -T | grep -iE "oom|drop|overflow|tcp|buffer|too many open"

系统资源

bash 复制代码
free -h
uptime
df -h

应用监听队列

bash 复制代码
ss -lntp

不要只看 Linux 参数。

重点看看:

text 复制代码
Nginx
Java
PHP-FPM
MySQL
Redis

真正申请的 backlog 是多少。


写在最后

这次服务器体检最大的收获,并不是把:

text 复制代码
1024

改成:

text 复制代码
8192

而是让我重新确认了一件事:

服务器优化应该从证据出发,而不是从网上复制参数出发。

如果没有:

text 复制代码
SYN drop
TCPBacklogDrop
TIME_WAIT Overflow
OOM
Too many open files

这些真实迹象,那么很多所谓"高并发参数优化"其实没有必要。

但如果像这次一样,服务器已经留下:

text 复制代码
299729 SYNs to LISTEN sockets dropped

甚至:

text 复制代码
Possible SYN flooding on port 80

那就说明系统已经明确告诉你:

这里以前扛不住。

这时候再根据连接队列、Nginx backlog、Tomcat accept-count、文件句柄、内存和连接池逐层排查,优化才真正有意义。

所以,如果你的线上服务器已经稳定运行了一两年,我反而建议找个时间登录上去看看。

不要只执行:

bash 复制代码
top

然后看到 CPU 5% 就退出。

看看:

bash 复制代码
netstat -s
dmesg
ss -s
ss -lntp

可能会发现一些平时完全注意不到的东西。

服务器和人其实有点像。

平时能跑、能吃、能睡,不代表体检报告一定没有异常。

你的服务器,上一次做"体检"是什么时候?

相关推荐
SimonKing1 小时前
AI逆向实战:一个壁纸网站被我5分钟摸透了,你也能
java·后端·程序员
IT_陈寒1 小时前
Vite打包时踩了个坑,static资源去哪了?
前端·人工智能·后端
学心理学的程序员1 小时前
anydoc:Firecrawl 出的 Rust 文档转 Markdown,4ms 转换、14 种格式干翻 MarkItDown
开发语言·后端·rust·开源·firecrawl·claude code·anydoc
Python私教1 小时前
创业团队做 App,第一版千万别做“大而全”:MVP 到底该留下什么
后端·python·架构
FfHUCisI1 小时前
Golang SSA 中间表示与优化 Pass
开发语言·后端·golang
武子康1 小时前
我让 Qwen3.6-27B 真改了一次 Git 仓库:工具调用怎样形成 Agent 闭环
人工智能·后端·agent
云浪1 小时前
如何让大模型操作 MySQL 数据库?
javascript·人工智能·后端
SamChan901 小时前
PDF 翻译服务的全链路可观测性设计:OpenTelemetry + Jaeger + Loki 实战方案
后端·python·pdf·机器翻译
我的xiaodoujiao1 小时前
Django 基础知识详细图文教程 1-Django 介绍
后端·python·学习·测试工具·django