尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux系统卡顿排查实战:从进程定位到安全终止的完整指南

Linux系统卡顿排查实战:从进程定位到安全终止的完整指南 我先把话说在前头Linux 服务器卡、开发机卡、或者你台式机上风扇狂转多数情况下不是系统莫名其妙抽风而是某个或某几个进程把 CPU 或内存吃干了。我这几年前后端、运维都摸过碰到这种问题从来不会第一时间重启机器先花几分钟定位是哪个进程干的把它揪出来处理掉这才是正路。这篇文章就完整走一遍这个流程从查看到杀死把常用的命令、原理、还有我踩过的坑都放进来几分钟就能跟着实操。1. 系统卡顿的第一现场先判断问题出在 CPU 还是内存先说一个最常见也最简单的开场。不管你是远程连服务器还是自己电脑卡了第一步永远是打开终端敲一个命令top。这个命令几乎每个 Linux 发行版都带不需要额外安装交互界面按 q 退出。它上半部分显示系统整体状态负载、进程数、CPU 占用率、内存使用下半部分实时刷新运行中的进程列表默认按 CPU 占用排序。很多人都用过 top但不少人只盯着那一堆跳动的数字发呆不知道看什么。我的习惯是分三步先看顶部的load average三个数值。它是一分钟、五分钟、十五分钟的系统平均负载可以理解为“有多少进程在等待被 CPU 执行”。这里要特别提醒负载高不等于 CPU 忙它统计的是正在运行和不可中断睡眠状态的进程总数。如果你看到 load average 数值很高但 CPU 百分比却不怎么高那多半是 IO 瓶颈磁盘读写卡住了一堆进程这时候盲目去杀高 CPU 进程是没用的。看%Cpu(s)那一行。us是用户态占用sy是内核态占用wa是等待 IO。如果us跑到百分之八九十那就是纯算力问题如果wa很高同样是 IO 问题如果sy高可能是系统调用太频繁比如网络发包、频繁读写小文件。按 P 键让进程列表按 CPU 使用率排序按 M 键按内存排序这是 top 交互模式里最实用的两个快捷键。判断 CPU 问题还是内存问题直接决定了你后续的操作方向。CPU 飙高进程响应不过来内存不够系统会频繁用 swap 交换表现为卡顿但你去看 CPU 又不高。所以先定位是哪个维度出问题再往下走。1.1 用 top 快速锁定嫌疑进程top 打开后进程列表每一行有 PID、USER、PR、NI、VIRT、RES、SHR、S、%CPU、%MEM、TIME、COMMAND 这些列。我一般只关心这几列PID进程号后面杀进程要用。%CPUCPU 占用百分比注意这个值是相对单核的如果你机器有 4 个核一个进程最高可以显示接近 400%。看到超过 100% 的数字不用慌它只是占满了多个核。%MEM物理内存占用百分比。RES常驻物理内存大小单位默认 KB按大写 E 可以切换显示单位。TIME进程累计消耗的 CPU 时间这个很关键。有的进程 %CPU 看着高但 TIME 很短说明它是刚启动在跑初始化有的 %CPU 只有 50%TIME 却好几个小时说明它一直持续在消耗 CPU更要警惕。COMMAND进程启动的命令行能直接看出是什么程序。在 top 界面里还能按1展开查看每个 CPU 核心的使用情况。这个我强烈建议做一下尤其是多核机器。之前我遇到过一台 16 核的编译服务器某个进程只把线程绑到了第 0 号核上导致那个核被打满其他 15 个核闲着但整体 CPU 使用率看起来只有 6%很多人就被骗过去了。展开看每核负载能直接发现这种问题。1.2 ps 命令手动排序把进程列表“拍下来”top 是交互式的适合实时观察但如果你想把进程列表保存下来、或者放到脚本里处理用 ps 加排序参数更合适。最常用的组合是ps aux --sort-%cpu | head -n 15这个命令把当前所有进程按 CPU 占用从高到低排取前 15 行。USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND这些列都会显示出来。同理按内存排序ps aux --sort-%mem | head -n 15把--sort参数直接接负号字段名就是个降序。后面我写巡检脚本也经常用这招。另外ps -eo可以自定义输出列比如我只想看 PID、CPU、内存、命令行可以写成ps -eo pid,ppid,%cpu,%mem,comm --sort-%cpu | head -n 20这种方式的优势是固定输出格式方便你复制 PID 去排查也方便二次管道处理。2. 按 CPU 维度排查找出一直在“烧”的进程定位到 CPU 占用最高的几个进程之后不要急着 kill先判断它到底是不是真正的元凶。我见过太多人看到某个进程 CPU 高就直接杀掉结果把数据库或者 Web 服务给杀了业务直接挂掉。按 CPU 维度排查核心是分清楚“持续消耗”和“瞬时冲高”。2.1 CPU 占用多少才算异常怎么判断很多新手会问CPU 占用 30% 算不算高这个问题其实没有固定答案得结合机器核数和业务类型。单核机器一个进程占 30% 已经很离谱了32 核机器上一个进程占 30% 可能只是因为它在做压测。我更建议看两个指标第一是累计 CPU 时间 TIME。同样一个进程%CPU 是每秒采样TIME 是从进程启动到现在累计消耗的 CPU 时间。如果 TIME 持续快速增长说明这个进程一直在干活。比如你用 top 连续看三次每次间隔十秒发现一个进程的 TIME 从 00:12:30 涨到 00:14:50那基本可以确定它每秒吃掉 12% 左右的单个核心持续在计算。第二是进程的启动时间和运行时长。配 ps 的 etime 参数就能看到进程已经运行了多久ps -eo pid,comm,etime,time,%cpu --sort-%cpu | head -n 15如果这个进程刚启动几分钟CPU 占用又高可能是初始化任务吃资源等一会儿可能就降下来了。如果已经跑了几天、甚至几个月CPU 还一直高那就是典型的资源泄漏或者死循环。2.2 从 CPU 高占用到问题根因的三种思路确定进程是持续消耗 CPU 之后我一般会按下面三条路径往下查思路一看进程的工作目录和启动命令。很多进程的 COMMAND 列只显示了一个名字比如java、python根本看不出它在干什么。这时候去/proc/PID/cwd看工作目录去/proc/PID/cmdline看完整启动参数ls -l /proc/1234/cwd cat /proc/1234/cmdline | tr \0 在运维排查时这两个文件能救大命。尤其是服务器上莫名出现的高 CPU 进程通过启动参数和工作目录基本能推断出是哪个应用、哪个项目的。思路二用 pidstat 观察进程态。pidstat 是 sysstat 包里的命令它比 top 更适合做连续采样pidstat -p 1234 1 10这个命令每秒钟采样一次进程 1234 的状态连续采 10 次。输出里能看到进程是在用户态消耗%usr还是内核态消耗%system。如果 %system 很高说明进程在做系统调用这时候可以去排查是不是 IO 读写频繁如果 %usr 高那就是纯计算逻辑的锅。思路三看进程打开了哪些文件。高 CPU 往往伴随着频繁的文件操作用 lsof 能列出进程打开的所有文件lsof -p 1234 | head -n 30看到大量日志文件、临时文件再结合它的 TIME 增长趋势基本就能判断是一个写日志的死循环。我遇到过最经典的一个案例某个服务把 debug 日志开关打开后忘了关每秒写几百条日志CPU 被磁盘 IO 和日志格式化吃掉了大半。找到打开的文件之后问题原因一目了然。3. 按内存维度排查别被“看似很高”的数值误导内存排查比 CPU 更繁琐因为内存相关的指标实在太多很多人一看总内存 16G用掉 14G 就慌了觉得是不是有进程泄漏。但实际上 Linux 对内存的使用机制非常激进它会尽量把空闲内存拿去做缓存用来加速文件读写。3.1 内存占用的三个关键指标VSZ、RSS、共享内存看 ps 输出的时候你会看到 VSZ 和 RSS 两列。VSZ 是虚拟内存大小指进程可以访问的虚拟地址空间大小别太当真因为它包含了没实际分配的内存RSS 是常驻物理内存这才是进程真正占用的物理内存。但 RSS 本身也有坑它会重复计算共享库。比如多个进程都用了 libc同一个物理内存页会被映射到多个进程的地址空间但 RSS 里每个进程都算了一份。所以把一堆进程的 RSS 加起来很可能会超过总物理内存但系统实际并不会崩溃因为有很多是共享的重复计数。另一个容易忽略的是共享内存SHR。多进程架构的程序尤其是 Java、浏览器这类共享内存在总占用里占的比重相当大。判断一个进程内存占用是否异常时光看 RSS 不够还要看它单独占用的部分有多少。3.2 用 free 和 ps 交叉验证内存瓶颈判断系统内存是否吃紧第一个命令应该是free -hfree -h输出里最关键的不是 used而是 available。available 是估算的“在不触发 swap 的情况下还能启动多少新进程”的可用内存。即便 used 看着很高只要 available 还够系统就不会卡。一旦 available 越来越小swap 占用越来越高才是真的内存危机。确认系统内存吃紧之后再用 ps 查具体是哪个进程占用最多ps aux --sort-%mem | head -n 15拿到进程 PID 后我推荐看一个更详细的文件cat /proc/PID/status | grep -E VmPeak|VmSize|VmRSS|VmSwap这里面 VmSwap 最重要它表示进程被换到交换分区的内存大小。如果这个数字持续增长说明该进程的内存压力已经大到系统不得不把它的内存往 swap 里搬。一个内存泄漏的程序VMPeak历史峰值会不断变大而 VmRSS 有时候看起来还行因为你只看了当前值没看峰值。内存问题的处理通常没有 CPU 那么爽快。CPU 高直接杀进程通常能立竿见影内存高杀了进程不一定能立刻释放全部内存因为内核缓存还在需要等系统自己回收。如果你杀完进程free -h看到的 used 依然很高别急那大部分是 buff/cache系统会在内存紧张时自动清理。4. 看清进程底细再动手防止误杀的三个关键动作很多初学者习惯性看到 CPU 或内存高的进程就直接 kill -9这个做法风险非常大。我之前在实践中吃过一次亏把一台 IP 为 10.0.10.15 的服务器上占用 CPU 最高的进程当病毒杀了结果那是别人刚启动的数据迁移脚本一个小时的活白干了。所以动手前必须做足功课。4.1 通过 /proc 文件系统深挖进程信息Linux 的 /proc 目录是一个虚拟文件系统进程运行期间的很多细节都能在这里找到。对 PID 为 1234 的进程我通常看这几个路径/proc/1234/cmdline完整启动命令包含所有参数/proc/1234/cwd工作目录软链接/proc/1234/exe可执行文件的真实路径/proc/1234/environ进程的环境变量/proc/1234/status进程状态、内存、父进程 PID 等信息/proc/1234/task/线程目录数一下子目录数量就知道开了多少线程一个进程异常高 CPU经常是开了大量线程死循环。查线程数用这个命令更直接ls /proc/1234/task | wc -l我曾经排查过一个 Kafka 客户端程序线程数高达三千多CPU 自然爆表。正常情况下一个业务进程线程数在几十到一两百是合理的上千线程的进程不是配置错了就是存在线程泄漏。4.2 查父子关系避免误杀关键服务每个进程都有父进程用pstree或者命令行参数-ef可以查到进程树ps -ef --forest | grep 1234看到父进程是谁、子进程有哪些能帮你判断这个进程是什么角色。比如它是 systemd 直接拉起的系统服务杀掉了之后 systemd 可能马上又把它拉起来它是某个主进程的子进程杀掉反而可能触发主进程的异常退出。在服务器上我还会留意进程对应的服务端口ss -tlnp | grep PID或者直接用 lsof 查端口lsof -i:8080通过端口能判断它是不是对外提供服务影响面有多大。如果服务刚好是对外提供 HTTP 接口的你把它杀了线上直接 502。5. 终止进程的完整姿势从温和到强制按需选择确认了进程身份、确定了它确实异常之后进入正式处理环节。杀进程不是只有 kill -9 一个选择从温和到强制有好几档选错档位可能导致数据丢失。5.1 信号机制kill 命令背后的原理kill 命令本质上是向进程发送一个信号不叫杀掉。Linux 信号有很多种常用的是下面这几个信号编号默认行为使用场景SIGHUP1终止进程让进程重新加载配置很多守护进程约定SIGINT2终止进程相当于 CtrlC前台程序可捕获SIGTERM15终止进程默认的 kill让进程优雅退出SIGKILL9强制终止不可被捕获或忽略最后手段SIGSTOP19暂停进程不终止只是挂起我的处理顺序一般是kill PIDSIGTERM→ 等几秒 → 如果进程没退再kill -9 PID。SIGTERM 为什么是首选因为它给了进程自己做善后处理的机会释放资源、刷写缓冲区、关闭文件描述符、通知子进程退出。很多应用会注册信号处理函数收到 SIGTERM 后走优雅退出流程。直接 SIGKILL 相当于电脑直接拔电源数据可能没来得及落盘。5.2 kill 命令的常见参数与组合用法先讲两个后台任务的场景。如果你在前台跑了一个程序想中断它直接按 CtrlC 发送的是 SIGINT。如果你在一个 SSH 会话里起了后台进程这个进程在会话断开后可能会变成孤儿用 kill 不一定能传递到完整的进程组。这时候用kill -- -PID进程组或者pkill -P PPID按父进程取子弹进程会更彻底。我之前排查过一些启动脚本父进程是一个 shellshell 退出后子进程变成孤儿继续跑用 kill 杀父进程根本没用得用pkill -P把子进程全干掉。多个进程批量处理的场景用 pkill 或者 pgrep 更高效# 杀掉所有包含 miner 关键字的进程危险慎用 pkill -f miner # 先查出来看看再动手 pgrep -f minerpkill -f是按完整命令行匹配的很容易误杀用之前务必先 pgrep 看清楚。5.3 处理杀不掉的进程D 状态与不可中断睡眠在 Linux 中有些进程你 kill -9 都杀不掉看 top 的时候状态列显示 D。D 状态叫不可中断睡眠一般是进程正在做内核态 IO 操作比如等待磁盘写回、等待 NFS 网络存储响应。这个状态下的进程无法响应任何信号包括 SIGKILL。遇到 D 状态进程最有效的办法是检查底层 IO 是否卡死dmesg | tail -n 20 cat /proc/PID/stack如果是 NFS 挂载的目录变得不可访问那进程就会陷入持续的 D 状态。这种问题靠 kill 解决不了得恢复底层的存储服务。我碰上过一台机器 NFS 服务端宕机客户端一堆进程状态 D重启客户端机器才恢复。所以下次看到 kill -9 没反应别只怀疑命令没执行先看进程状态是不是 D。6. 结尾几个经常被忽略的细节与我的习惯做法看到这里排查流程基本完整了。最后分享几个我长期实践下来觉得很有用的经验和细节算是对上面内容的补充也方便你在实际环境里少踩点坑。第一个细节是不要只杀进程不查它的启动来源。我之前在客户服务器上处理过一次不明进程杀掉之后过几分钟又出现一个新的 PID继续占 CPU。顺着/proc/PID/exe找到执行文件再通过systemctl status才发现是某个服务脚本被配置成了开机自启光杀进程不关服务它就会无限重生。正确的做法是先systemctl disable或移出开机自启再杀进程。第二个细节是尽量用完整的绝对路径配合通配符做批量操作。比如要杀 /opt/app/bin 下的所有 worker 进程写成pkill -f /opt/app/bin/worker比pkill -f worker安全得多能减少误杀同名进程的概率。我自己写巡检脚本时也一直坚持这个习惯。第三个细节是养成先输出快照再动手的习惯。杀进程前先把当前进程信息保存下来ps -eo pid,ppid,%cpu,%mem,etime,cmd --sort-%cpu | head -n 30 /tmp/before_kill_$(date %F).txt万一判断错了这个文件能帮你还原当时的现场也方便事后复盘到底是什么进程在消耗资源。第四个细节如果一个进程持续出现、每次换一个新 PID很可能有守护进程在帮你拉起它。这时要查父进程是谁而不是跟新 PID 死磕。用一个 shell 循环连续观察几次就能发现规律for i in 1 2 3; do pgrep -f suspicious_process; sleep 2; done如果每次输出的 PID 都不一样就说明存在自动重启机制这时候杀进程只是治标找出守护源头才是治本。最后再提供一个我经常用的一行命令适合快速判断当前机器最该处理的进程ps -eo pid,ppid,%cpu,%mem,comm --sort-%cpu | head -n 10结合free -h和uptime一起看半分钟内基本能确定要不要动手、拿谁动手。想再快一点的可以把 top 改成语带批处理模式top -bn1 | head -n 20这条命令不用进入交互界面直接输出一次快照适合写脚本轮询。这些命令和思路组合起来不管你是维护服务器还是折腾自己的 Linux 电脑排查高 CPU 高内存进程都是够用的。关键在于先观察、再判断、最后动手杀之前一定要搞清楚进程的身份和影响范围。希望这篇文章能帮你少走一些弯路至少下次碰到系统卡顿时不用第一反应就是重启。
返回列表