首页 / 资讯中心 / 文章详情

Linux服务爆满排查实战:从进程数、文件描述符到systemd调优

Linux服务爆满排查实战:从进程数、文件描述符到systemd调优 ★ FEATURED ARTICLE
“服务爆满”这四个字很多人一听就头大。我处理过的线上故障里至少有三分之一都能归到这一类systemd 服务状态异常、进程数莫名其妙冲到几千、端口探针一抓一大把、文件句柄报错刷满日志。表面看是资源被打爆实际根因往往藏在几个不起眼的配置里。这篇文章就把这些年踩过的坑掰开揉碎了讲从现象到原理再到操作命令一条线直接拉到底适合刚接手服务器运维、或者已经在排查但总找不到头的同学做参考。先说说适合什么场景你发现服务器卡死、SSH 连不上、业务接口超时、systemctl执行报错——这些都有可能是“服务爆满”的连锁反应。我会先讲清楚“爆满”到底指什么再讲排查思路然后是实战命令和常见问题的避坑记录。全程用我自己在测试环境反复演练过的方案你可以直接照着敲命令。1. 先把“爆满”这个词拆透1.1 服务爆满在不同人嘴里是完全不同的意思运维群里喊一句“服务爆满了”不同人会想到完全不同的画面。我最初排查这类问题走了不少弯路就是因为没先确认对方说的“爆满”是哪一种。后来我把常见的“爆满”分成了四类排查思路也就清晰了。第一类是进程数爆满。ps -ef一刷出来几百上千个相同名字的进程top里 CPU 占用倒不一定高但系统负载快速飙到几十甚至上百。这类问题的典型原因是某个服务反复重启、定时任务失控派生出大量子进程、或者代码里 fork 之后没有回收。第二类是端口和连接数爆满。ss -tlnp一看监听端口没剩几个TCP 连接数堆积到几万。最明显的表现是新的访问请求迟迟无法建连业务侧报“connect timeout”。第三类是文件描述符和句柄爆满。日志里开始刷Too many open files服务进程突然起不来systemctl start提示资源问题。这类问题特别隐蔽因为平时不太会关注ulimit -n但高并发环境下句柄耗尽非常常见。第四类是systemd 管理单元本身爆满。服务没启动几个但systemctl list-units能看到一大堆 dead 或 failed 的残留记录甚至执行systemctl命令都慢得让人抓狂。把“爆满”的类型分清楚之后排查的路径才能对得上。否则光是盯着 CPU 看半天问题在句柄上方向就完全错了。1.2 用停车场的例子理解资源上限把服务器想象成一个停车场。CPU、内存、磁盘就是车位数进程就是要求进场的车。正常情况下车来车走车位一直够用。但如果一个保安忘了闸门或者一堆车堵在出口不走车位就会越来越少。进程表task list就是停车场门口那块登记板。内核给登记板画了上限这个上限由pid_max决定。登记板满了任何新进程都创建不了shell 都启动不起来。文件描述符表就像是停车场的闸机卡。每次服务打开文件、建立网络连接都会领一张卡。卡的额度由ulimit -n以及 systemd 服务文件里的LimitNOFILE共同决定。卡发完了再正常的请求也兑现不了。cgroup 里的tasks限制则相当于停车场分区管理一个区域最多停多少辆车。systemd 管理服务时经常自动套一个 cgroup里面有TasksMax限制到了阈值就拒绝新进程。理解了这层映射关系看到“爆满”就不会慌——你只是在跟内核的计数器和资源配置博弈。2. 从“水土不服”到“根因定位”排查思路总览2.1 先看现象定位层再顺藤摸瓜排查服务爆满我的习惯是分三层去看主机层、OS资源层、服务管理层。主机层先看四个维度的实时数据CPU、内存、磁盘空间、inode 用量。很多人只看磁盘空间百分比没注意 inode 已经耗尽——文件挤占了所有索引节点新文件根本创建不了服务自然频繁报错。OS资源层看的是pid_max、当前进程数、线程数、文件描述符分配情况、TCP连接状态分布。这一层的数据最能直接解释“到底满在哪里”。服务管理层则聚焦 systemd服务是否处于 activating/auto-restart 状态失败次数是不是累计到了阈值unit 文件的资源限制跟系统默认的差距有没有异常排查顺序我一般是这样先top看负载和CPU再free -h看内存然后df -i看 inode。如果负载很高但 CPU 不高那就去看 IO 或 D 状态进程。如果进程数上万直接查pid_max。如果连接数异常ss -s看总量ss -state established按状态分类。2.2 关键命令的“组合拳”用法单独敲一条命令往往看不出名堂组合起来才有意义。我常用的四条命令几乎能应付大部分场景# 看系统整体的运行队列和负载 uptime # 看进程状态分布D不可中断和 Z僵尸尤其要注意 ps -eo stat,pid,ppid,comm | awk $1 ~ /D|Z/ {print} | head -50 # 看当前系统总进程数和线程数 ps -eLf | wc -l # 看 PID 空间最大值和当前溢出情况 cat /proc/sys/kernel/pid_max然后进入服务侧# 列出异常状态的服务 systemctl list-units --statefailed --no-pager systemctl list-units --stateactivating --no-pager # 查看某个服务的详细状态和重启计数 systemctl status rsyslog.service -l --no-pager这里要提醒一句systemctl list-units默认不显示 inactive 的单元但“爆满”场景下恰恰是那些残留的 inactive/dead unit 在拖垮性能。排查时要加上--all参数。2.3 一个完整的定时收集方法线上故障最怕“事后诸葛”。如果手上有一个历史基线排查会快很多。我习惯写一个小脚本丢在 cron 里跑每分钟采集一次关键指标#!/bin/bash LOG_DIR/var/log/sysdiag mkdir -p $LOG_DIR TS$(date %Y%m%d-%H%M%S) { echo $TS uptime echo --- loadavg --- cat /proc/loadavg echo --- process count --- ps -eLf | wc -l echo --- fd count --- cat /proc/sys/fs/file-nr echo --- zombie count --- ps -eo stat | grep -c ^Z echo --- systemd failed units --- systemctl list-units --statefailed --no-legend --no-pager | wc -l echo --- top memory consumers --- ps -eo pid,ppid,%mem,%cpu,comm --sort-%mem | head -20 } $LOG_DIR/sysdiag.log这个脚本不到二十行但爆满前半小时的系统数据几乎都能覆盖。有过几次故障之后你就知道这个习惯有多救命了。3. 常见根因逐一拆解3.1 杀手一PID 空间被耗尽Linux 下每个进程都有一个唯一 PID内核通过pid_max来控制 PID 编号的上限。默认值通常是 32768 或 4194304看起来不小但某些环境下会被调得很低。为什么会爆满大量短生命周期进程被循环创建比如代码里不断 fork 子进程完成任务后没有正常 wait子进程变成僵尸进程。僵尸进程不消耗 CPU 和内存但会占 PID直到父进程把它们回收。还有一种情况是 systemd 服务的Restartalways搭配频繁崩溃每次崩溃都拉起新进程旧进程如果没完全释放PID 消耗就会加速。怎么确认# 当前已用 PID 在 /proc 下的目录数 ls /proc | grep -E ^[0-9]$ | wc -l # 看僵尸进程数量 ps -eo stat | grep -c ^Z # 看 PID 上限 cat /proc/sys/kernel/pid_max怎么处理如果确认僵尸进程来自某个服务先重启服务试试因为重启会让父进程重新接管子进程。如果父进程就是残留进程本身比如已经变成 defunct 的 init 子进程可能需要重启整个系统或在维护窗口内处理。调pid_max只能缓解不能治本echo 4194304 /proc/sys/kernel/pid_max要永久生效就写进/etc/sysctl.confkernel.pid_max 4194304但核心逻辑是PID 只是表象进程生命周期管理出了问题才是根源。日志里如果能看到同一进程反复启停优先去查业务日志和启动脚本。3.2 杀手二systemd 单元数量失控systemd 把每个服务、套接字、挂载点都当成一个单元来管理。正常情况下一个系统几十到几百个单元完全没问题。但某些情况下单元数量会像细胞分裂一样膨胀。最容易触发的是模板实例问题。服务名里带符号就是模板实例例如redis6380.service。如果监控软件或自动扩缩容脚本不断生成新的实例文件旧的又不清理systemctl list-units会变得非常臃肿。另外有种情况跟 socket 激活有关。systemd 可以为每个端口创建一个 socket 单元如果一个进程多次监听不同端口而且每次都生成新单元文件unit 数量也会涨上去。怎么确认# 查看所有已加载的 unit 总数 systemctl list-unit-files --no-pager | wc -l # 查看 active 和 dead 的对比 systemctl list-units --all --no-pager | awk {print $2} | sort | uniq -c怎么处理先定位到是哪个服务产生的大量实例然后清理对应的 unit 文件。常见位置是/etc/systemd/system/*.service和/run/systemd/system/*.service。清完后跑systemctl daemon-reload systemctl reset-failed这里有一个特别的坑systemctl reset-failed会清掉服务的失败计数但如果单元文件本身还在下次故障又会重新积累。做清理前一定要确认源头否则只是治标。3.3 杀手三文件描述符和句柄耗尽文件描述符File Descriptor是进程访问文件、网络连接、管道等资源的通行证。系统对所有进程的总 FD 数有限制单个进程也有自己的限制。典型的泄漏路径代码里打开文件、建立连接之后忘记 close日志库在轮转时没有正确关闭旧文件句柄监控工具不断连新端口但旧 socket 处于 TIME_WAIT 状态迟迟不回收。这些问题积少成多最终把 FD 配额吃光。怎么确认# 系统当前 FD 使用量实际/上限 cat /proc/sys/fs/file-nr # 输出三列已分配、未使用、最大值 # 查看某个服务的 FD 使用数 ls /proc/PID/fd | wc -l # 查看最耗 fd 的进程 for pid in $(ls /proc | grep -E ^[0-9]$); do if [[ -d /proc/$pid/fd ]]; then count$(ls /proc/$pid/fd 2/dev/null | wc -l) echo $count $pid $(cat /proc/$pid/comm 2/dev/null) fi done | sort -rn | head -20调大限额的方法临时调ulimit -n 65535 ulimit -u 65535永久调改/etc/security/limits.conf* soft nofile 65535 * hard nofile 65535 * soft nproc 65535 * hard nproc 65535但如果服务由 systemd 管理limits.conf 里的设置未必生效因为 systemd 会用自己的单元级限制覆盖它。要在/etc/systemd/system/service.service.d/override.conf里写[Service] LimitNOFILE65535 LimitNPROC65535 TasksMaxinfinity然后systemctl daemon-reload systemctl restart service关于 MAX值怎么选不要盲目调到几十万先估算业务并发量。假设每个连接需要 10 个 FD你的服务峰值并发是 1000那 20000 左右的 npfle 已经够用。调到百万级反而会让系统内存浪费因为内核要给每个进程预留表项。3.4 杀手四cgroup 的限制与系统限制打架systemd 服务启动时默认会被放入独立的 cgroup这个 cgroup 里有TasksMax参数控制该服务最多可以创建多少个任务。如果服务本身是并发模型比如 PHP-FPM、Java 线程池TasksMax 默认值 512 可能根本不够用。现象非常迷惑服务明明没超内存也没死循环但新请求就是进不来。查看日志能看到cgroup: fork rejected by pids controller之类的提示。怎么确认systemctl show service.service -p TasksMax -p TasksCurrent cat /sys/fs/cgroup/pids/system.slice/service.service/pids.max cat /sys/fs/cgroup/pids/system.slice/service.service/pids.current如果pids.current逼近pids.max基本就是撞上限了。怎么处理systemctl set-property service.service TasksMax4096或者写进 override.conf[Service] TasksMax4096同样要注意TasksMaxinfinity虽然省事但等于放弃了资源隔离的保护机制。我见过有人把所有服务都设成 infinity结果一个服务线程泄漏把整台机器的进程表撑爆连 sshd 都起不来。资源和稳定性的平衡才是目的不是一刀切。3.5 杀手五内存碎片和频繁 swap 的连锁反应磁盘、CPU、内存都还有余量但服务就是“起不来、起不稳”。这种情况常见于长期运行的机器上swap 频繁使用内存碎片化严重加上部分服务用了大量共享内存内存分配器的行为和平时完全不同。怎么确认cat /proc/meminfo | grep -E MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree # 看内存碎片化程度 cat /proc/buddyinfo处理方式如果确认是内存碎片临时手段是触发一次内存压缩echo 1 /proc/sys/vm/compact_memory长期手段是调整vm.swappiness降低 swap 使用倾向sysctl -w vm.swappiness10但说句实话这类问题最干脆的解法就是滚动重启集群节点。碎片化是运行时间累积出来的没有银弹。4. 实操过程与核心环节实现4.1 一次真实故障的完整排查记录为了把上面的思路串起来我模拟一次常见故障某业务凌晨开始周期性报警起初不多后来干脆完全无响应SSH 能连上但敲命令都卡。第一步保持冷静先看全局。uptime # 输出 load average: 3.45, 2.10, 1.08负载在涨但不算极端。free -h # 内存还剩不少没有到 swap 极限df -h df -i # 根分区 70%inode 还在安全范围基础资源看起来都活着那就往下深挖。第二步看进程数和 FD 数。ps -eLf | wc -l # 输出 120345这个数字大得离谱 ls /proc | grep -E ^[0-9]$ | wc -l # 输出 78211进程数明显异常cat /proc/sys/fs/file-nr # 输出 389120 0 1048576 # 已分配接近 40%总上限 1M还没到死亡线第三步找到杀手进程。for pid in $(ls /proc | grep -E ^[0-9]$); do if [[ -d /proc/$pid/fd ]]; then count$(ls /proc/$pid/fd 2/dev/null | wc -l) echo $count $pid $(cat /proc/$pid/comm 2/dev/null) fi done | sort -rn | head -20结果出来吓一跳某个 worker 类进程 PID 只有几百但 FD 数拥到 6000 多。显然是连接句柄泄漏。接着看它的父进程和启动信息ps -fp PID systemctl status 服务名果然该服务有几百个子进程都是因为父进程不断 fork。查看服务的日志发现每个子进程处理完任务后都没有正常退出而是挂在 TIME_WAIT 上等待回收。第四步针对性处理。先把问题进程暂停systemctl stop 服务名然后清理僵尸进程kill -HUP 1此时ps -eo stat | grep -c ^Z的数量开始下降。等 PID 回落后查看服务配置systemctl cat 服务名.service发现Restartalways存在但TimeoutStopSec没设置进程在处理完任务前不会响应 SIGTERM——这解释了为什么旧进程迟迟不退出。我给该服务加了[Service] TimeoutStopSec15 Restarton-failure并调大了句柄限制LimitNOFILE65535 TasksMax1024然后systemctl daemon-reload systemctl restart 服务名重启后观察ps -eLf | wc -l # 回落到几百 systemctl list-units --statefailed # 无异常 ss -tan | wc -l # 连接数恢复正常整个过程的核心是先确认哪个层满了再用数据定位谁在吃资源最后改配置并验证。4.2 调优参数对照参考表参数默认参考值排查时重点观测点调大后的注意隐患kernel.pid_max32768 或 4194304/proc下目录数进程数过大导致内核内存压力fs.file-max系统自动fs/file-nr第一列内存占用增加LimitNOFILE1024ls /proc/PID/fd无符号整数溢出LimitNPROC系统默认pids.current线程过载TasksMax512部分系统cgroup pids.current失去资源隔离vm.swappiness60swap使用率调低后内存压力增大调参一定要小步验证一次改一个变量观察至少十分钟。改两个以上参数同时出问题责任就很难说清楚了。5. 常见问题与排查技巧速查5.1 故障现象与原因匹配表现象可能原因第一时间查看的命令ssh 登录后敲命令卡死进程表满ps -eLf、cat /proc/loadavg服务一直 restarting启动超时或崩溃循环systemctl status、journalctl -u日志大量Too many open filesFD 耗尽cat /proc/sys/fs/file-nr、ulimit -n新连接建不上旧连接正常端口耗尽/TIME_WAIT堆积ss -tan state time-wait、netstat -scgroup 报 fork 失败TasksMax 超限systemctl show -p TasksMax磁盘显示还有空间但创建文件失败inode 耗尽df -i大量僵尸进程但 CPU 不高父进程未 waitps -eo stat,pid,ppid,comm5.2 我踩过的三个高频坑第一个坑是只调系统级 limit忽略 systemd 级覆盖。费了半天口舌教别人改limits.conf结果对方服务是 systemd 管的根本不生效。一定要先确认服务是不是 systemd unit如果是直接在 override.conf 里改LimitXXX。第二个坑是僵尸进程用 kill 解决。对普通进程 kill 能清理僵尸但对已经 defunct 的进程 kill 无效——僵尸进程已经死了只是等父进程收尸。正确做法是处理父进程。如果父进程是 PID 1少数情况下需要重启系统才能清干净。第三个坑是忽略日志中的周期性节奏。很多服务爆满不是突然的而是每到整点或某个定时任务触发时暴涨。我在日志分析时特意看时间戳的分布如果异常集中在每分钟的第 30 秒或每小时的第 0 分多半是 crontab 任务没锁或超时重叠。这个时候去优化业务调度比无限调大资源配额有针对性得多。5.3 关于排查效率的四个建议第一建基线。正常运行时就把关键指标记录下来故障时一对比就知道异常程度。第二善用 systemd 的journalctl -u service -b查看本次启动以来的日志而不是全部历史日志省去大量噪音。第三用timeout命令给可能卡死的命令加超时。排查时本身系统就很紧张如果执行的命令又卡住会雪上加霜。例如timeout 10 top -bn1 timeout 10 systemctl list-units --failed --no-pager第四排查过程中做的每一步操作都记录下来。故障复盘时能清晰还原现场而不是靠回忆。6. 收尾一个值得养成的运维习惯上面讲了一堆命令和方法但真要避免服务爆满功夫还在平时。我在实际运维中最深的体会是不能等报警响了才查。建议每周低频执行一次巡检把 systemd failed 单元数、PID 使用率、FD 使用率、僵尸进程数量这四个关键数字记下来。万一哪天某项数字异常翻一下历史记录就能快速锁定变化时间点。另外一个小技巧给关键服务的重启行为加上保护。比如用了Restartalways的服务务必同时设置StartLimitIntervalSec和StartLimitBurst否则崩溃循环会让“自动重启”变成“自动加速消耗资源”。这个坑很多人没注意等到服务反复崩溃把系统拖垮才后悔。服务爆满的原因再多归根结底是资源管理和代码质量两个层面的问题。资源管理靠基线、监控和调参代码质量靠排查日志时多问一句“为什么会这样”。排查多了之后你会发现大部分看似复杂的故障背后原因都很朴素——配置没跟上业务变化或者代码没管好生命周期。希望这篇文章能帮你少走点弯路。
阅读完成 · 觉得有帮助?
咨询建站