云计算百科
云计算领域专业知识百科平台

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

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

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

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

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

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

服务器现在正常,不代表它在业务高峰期也正常。
在这里插入图片描述

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

TCP: request_sock_TCP: Possible SYN flooding on port 80.
Sending cookies. Check SNMP counters.

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

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


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

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

top
free -h
df -h

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

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

  • 文件句柄和进程线程限制
  • TCP 内核参数
  • 当前 TCP 连接状态
  • 网卡和网络流量
  • CPU、内存、进程资源
  • TCP 丢包、Overflow、Reset、OOM 等历史异常
  • 真正有意思的往往是最后一项。

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

    服务器现在怎么样。

    而 netstat -s、dmesg 这些数据告诉你的则可能是:

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

    这两件事差别很大。


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

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

    CentOS 7
    Kernel 3.10
    内存:7.4G
    Swap:0

    检查时 CPU Load:

    load average: 0.01, 0.04, 0.08

    这是什么概念?

    基本没什么 CPU 压力。

    内存情况:

    total 7.4G
    used 5.5G
    available 1.6G

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

    文件句柄:

    ulimit -n = 65535
    fs.file-max = 759020
    file-nr = 3232

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

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

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

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


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

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

    TCP: 1076
    ESTABLISHED: 1046
    TIME_WAIT: 8
    CLOSE_WAIT: 7

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

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

    但继续执行:

    netstat -s

    然后筛选 TCP 异常:

    netstat -s | grep -iE "overflow|drop|reset|listen"

    结果就比较有意思了:

    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

    其中最值得关注的是:

    299729 SYNs to LISTEN sockets dropped

    接近 30 万次。

    再看 dmesg,直接找到了:

    TCP: request_sock_TCP:
    Possible SYN flooding on port 80.
    Sending cookies.
    Check SNMP counters.

    这下问题就很明确了。

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

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

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

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


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

    检查服务器 TCP 参数:

    sysctl net.core.somaxconn
    sysctl net.ipv4.tcp_max_syn_backlog
    sysctl net.core.netdev_max_backlog

    结果:

    net.core.somaxconn = 1024
    net.ipv4.tcp_max_syn_backlog = 1024
    net.core.netdev_max_backlog = 1000

    这些参数简单理解:

    tcp_max_syn_backlog

    客户端访问:

    客户端
    ↓ SYN
    服务器

    SYN_RECV
    ↓ ACK
    ESTABLISHED

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

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

    SYNs to LISTEN sockets dropped

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

    somaxconn

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

    服务器原来:

    somaxconn = 1024

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

    但这台服务器同时运行:

    Nginx
    PHP-FPM
    Java
    Redis
    MySQL

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

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

    net.core.somaxconn = 8192
    net.ipv4.tcp_max_syn_backlog = 8192
    net.core.netdev_max_backlog = 8192

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

    65535
    262144
    500000

    原因很简单:

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

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

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


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

    服务器原来的配置:

    tcp_max_tw_buckets = 5000
    tcp_fin_timeout = 60
    tcp_tw_reuse = 0

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

    于是调整为:

    net.ipv4.tcp_max_tw_buckets = 65536
    net.ipv4.tcp_fin_timeout = 30

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

    tcp_tw_reuse = 0

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

    CentOS 7
    Linux Kernel 3.10

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

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

    tcp_tw_reuse = 1
    tcp_tw_recycle = 1

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

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

    所以我的原则一直是:

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


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

    服务器原来的:

    ip_local_port_range = 32768 60999

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

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

    例如:

    用户

    Nginx

    Java

    Redis / MySQL / 其他微服务 / 第三方API

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

    因此最终调整:

    net.ipv4.ip_local_port_range = 10000 65535

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

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

    HTTP KeepAlive
    数据库连接池
    Redis连接池
    RPC长连接
    连接复用

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


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

    原来的参数:

    tcp_keepalive_time = 7200
    tcp_keepalive_intvl = 75
    tcp_keepalive_probes = 9

    简单理解就是:

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

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

    net.ipv4.tcp_keepalive_time = 600
    net.ipv4.tcp_keepalive_intvl = 30
    net.ipv4.tcp_keepalive_probes = 5

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

    但这里同样要提醒:

    Linux KeepAlive 不是万能药。

    如果你发现:

    CLOSE_WAIT

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

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


    八、Socket Buffer 也顺手调整了

    服务器默认:

    net.core.rmem_max = 212992
    net.core.wmem_max = 212992

    也就是大约 208KB。

    最终调整:

    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?

    配置写完后,我执行:

    sysctl –system

    明明看到:

    net.core.somaxconn = 8192
    net.ipv4.tcp_max_syn_backlog = 8192
    net.ipv4.tcp_max_tw_buckets = 65536

    但重新检查:

    sysctl net.core.somaxconn

    居然还是:

    1024

    一开始很容易怀疑:

    是不是 CentOS 7 内核不支持?

    是不是参数超过上限?

    是不是需要重启?

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

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

    执行顺序里先加载:

    /etc/sysctl.d/99-high-concurrency.conf

    设置:

    somaxconn = 8192

    随后又加载:

    /etc/sysctl.d/99-sysctl.conf

    重新变成:

    somaxconn = 1024

    最后又加载:

    /etc/sysctl.conf

    于是最终结果还是 1024。

    继续检查:

    ls -l /etc/sysctl.d/99-sysctl.conf

    发现:

    /etc/sysctl.d/99-sysctl.conf -> ../sysctl.conf

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

    再查:

    grep -nE 'somaxconn|tcp_max_syn_backlog|tcp_max_tw_buckets|swappiness' \\
    /etc/sysctl.conf \\
    /etc/sysctl.d/*.conf

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

    somaxconn = 1024

    和:

    somaxconn = 8192

    两套参数。

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

    sysctl -p

    问题解决。

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

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

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

    sysctl net.core.somaxconn

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

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


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

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

    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

    更值得关注的是优化前:

    299729 SYNs to LISTEN sockets dropped

    优化之后再次检查:

    299729 SYNs to LISTEN sockets dropped

    没有继续增长。

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

    正确观察方法应该是:

    date
    netstat -s | grep -iE "SYNs to LISTEN|listen queue|TCPBacklogDrop|reset"

    记录下来。

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

    如果:

    299729

    299729

    说明这段时间没有新增。

    如果:

    299729

    310000

    那说明仍然存在问题。

    这时候就不能继续无脑:

    8192 → 16384 → 65535

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


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

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

    Linux 已经:

    somaxconn = 8192

    但:

    ss -lntp

    看到 Nginx:

    LISTEN 0 511 *:80
    LISTEN 0 511 *:443

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

    继续检查:

    nginx -T | grep -E "worker_processes|worker_connections|multi_accept"

    发现:

    worker_processes auto;
    worker_connections 51200;
    multi_accept on;

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

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

    listen 80 backlog=4096;
    listen 443 ssl backlog=4096;

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

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

    listen 80;
    listen [::]:80;
    listen 443 ssl;

    多个监听配置。

    应该先:

    nginx -T

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

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


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

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

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

    比如这台机器的 Java:

    8080 LISTEN backlog=1000
    18080 LISTEN backlog=100

    8080 已经比较合理。

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

    server.tomcat.accept-count=1000
    server.tomcat.threads.max=300
    server.tomcat.threads.min-spare=20

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

    假如:

    Tomcat线程 = 1000
    数据库连接池 = 30

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

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

    Linux TCP

    Nginx

    Tomcat

    业务线程

    数据库连接池

    MySQL

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


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

    体检结果:

    *:6379
    redis-server

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

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

    但至少值得继续检查:

    redis-cli CONFIG GET protected-mode
    redis-cli CONFIG GET requirepass

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

    bind 127.0.0.1
    protected-mode yes

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

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


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

    这台服务器:

    总内存:7.4G
    已使用:5.5G
    available:1.6G
    Swap:0

    而机器上同时跑:

    Nginx
    PHP-FPM
    Redis
    MySQL
    Java
    宝塔

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

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

    fallocate -l 2G /swapfile
    chmod 600 /swapfile
    mkswap /swapfile
    swapon /swapfile

    然后:

    echo '/swapfile swap swap defaults 0 0' >> /etc/fstab

    同时:

    vm.swappiness = 10

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

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

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


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

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

    文件句柄

    ulimit -n
    ulimit -Hn
    cat /proc/sys/fs/file-nr
    sysctl fs.file-max

    重点防:

    Too many open files

    TCP 连接

    ss -s
    ss -ant

    重点关注:

    ESTABLISHED
    TIME_WAIT
    CLOSE_WAIT
    SYN_RECV

    TCP 队列

    sysctl net.core.somaxconn
    sysctl net.ipv4.tcp_max_syn_backlog
    sysctl net.core.netdev_max_backlog

    TCP 历史异常

    netstat -s | grep -iE "overflow|drop|reset|listen"

    这条我认为特别重要。

    内核异常

    dmesg -T | grep -iE "oom|drop|overflow|tcp|buffer|too many open"

    系统资源

    free -h
    uptime
    df -h

    应用监听队列

    ss -lntp

    不要只看 Linux 参数。

    重点看看:

    Nginx
    Java
    PHP-FPM
    MySQL
    Redis

    真正申请的 backlog 是多少。


    写在最后

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

    1024

    改成:

    8192

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

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

    如果没有:

    SYN drop
    TCPBacklogDrop
    TIME_WAIT Overflow
    OOM
    Too many open files

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

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

    299729 SYNs to LISTEN sockets dropped

    甚至:

    Possible SYN flooding on port 80

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

    这里以前扛不住。

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

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

    不要只执行:

    top

    然后看到 CPU 5% 就退出。

    看看:

    netstat -s
    dmesg
    ss -s
    ss -lntp

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

    服务器和人其实有点像。

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

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

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 你的服务器做过体检么?一份真实的 CentOS 高并发服务器体检与优化实录
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!