尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Linux进程管理与系统监控:从load average飙升到故障定位的完整实战

发布时间:2026/9/29 22:33:26

资讯中心
01
ARTICLE

Linux进程管理与系统监控:从load average飙升到故障定位的完整实战

Linux进程管理与系统监控:从load average飙升到故障定位的完整实战
凌晨一点半监控告警把手机震醒一台Linux服务器的load average冲到28业务流量却没有任何上涨。登录之后我也没有急着重启而是沿着进程查看、系统监控、定时任务、日志系统这条链路一层层往下走大约十分钟后定位到问题——某个清理脚本条件写错批量拉起一堆D状态进程全堵在磁盘IO上。搞Linux的人早晚都会遇到这种机器看起来要死了的时刻而Linux进程管理与系统监控要解决的就是在这种时刻你仍然知道下一步该看什么。这篇文章想把这套东西完整串一遍进程查看与信号、后台任务与会话、系统性能监控、定时任务、日志系统。这几块在教科书里往往分开讲实际排障时却是同一套循环——发现异常、缩小范围、定位根因、修复验证。适合刚入门运维和开发的读者也适合那些命令会敲、原理模糊的人对照补缺。我会尽量把每个操作背后的为什么也讲清楚毕竟只记住命令换一个场景就抓瞎了。1. 先建立进程模型的直觉从任务管理器思维切换过来1.1 程序、进程、父进程与PID我见过太多从Windows切过来的同事第一条命令就是ps看完输出之后茫然地问我这跟任务管理器长得也不像啊。其实不是命令长得不像而是思维模型不一样。Windows的任务管理器是给你看有哪些进程在跑而Linux的进程模型是一个活着的树每个进程都有唯一的PID都能找到它的父进程PPID所有进程最终都能追溯到PID为1的systemd早期是init。ps -ef --forest能把这种父子关系直接画成树形结构看起来一目了然。程序与进程的区别我常用一个生活类比程序是菜谱进程是灶台上正在做的那盘菜。菜谱放在那儿不占灶台只有开火下锅才占一口锅。同理可执行文件只是一堆字节被加载运行之后才成为进程才会占用内存、CPU、文件描述符等资源。Linux的进程创建也跟Windows很不一样。Linux通过fork()复制当前进程再用exec()把新的程序映像加载进来所以父子进程天然存在血缘关系。这种设计让信号传递、作业控制、进程回收都形成了一套自洽的机制。理解这一点后后面看进程状态、杀进程、搞后台任务都会顺畅很多。1.2 进程状态机R、S、D、T、Z分别意味着什么ps aux输出的STAT列是很多人忽略又很关键的信息我简单列个对应关系状态名称含义Rrunning/runnable正在CPU上跑或者排在运行队列里随时可跑Ssleeping可中断睡眠通常是在等待某个事件可被信号唤醒Duninterruptible sleep不可中断睡眠一般是在等磁盘/网络IO返回信号都叫不醒Tstopped被暂停比如CtrlZZzombie僵尸进程子进程已退出但父进程还没回收它的退出状态Iidle内核线程的空闲态属于正常状态D状态是需要重点警惕的。教科书里说它是不可中断睡眠实际表现就是kill -9都杀不掉因为进程此时在内核态等着IO完成根本不处理信号。生产环境里D状态进程大量堆积往往伴随load average飙升背后通常是磁盘故障、NFS挂死、断连的存储设备这也是我开头那个告警场景的真正元凶。1.3 僵尸进程与孤儿进程最容易被误解的两个概念网上最流行的说法是僵尸进程用kill -9杀不掉这没错但很多人不知道它为什么会存在。僵尸进程的本质是子进程已经退出它的内核栈等资源基本已经释放只保留一个进程表项用来给父进程提供退出码。父进程只要调用wait()就能把这个表项回收此前的间隔里进程就显示为Z。真正的问题是父进程自己不回收也不退出。这种情况下你kill掉僵尸进程没意义它已经死了你需要处理的是它的父进程——要么让父进程正常退出由systemd接管后回收要么修复父进程里的资源管理逻辑。顺带一提孤儿进程是父进程先死、子进程被PID 1收养它跟僵尸是两码事别混在一起。2. 看进程就这三板斧ps、top、htop2.1 ps一张命令行的进程快照ps是最基础的进程查看命令但参数组合多得让人记不住。我日常主要用三套ps -ef ps aux ps -eo pid,ppid,user,stat,etime,%cpu,%mem,cmd --sort-%cpu | head -20ps -ef和ps aux输出内容差不多区别主要在语法来源前者是System V风格后者是BSD风格aux多带%CPU、%MEM、RSS这些资源字段。你要想在脚本里做自动化用ps -eo自定义字段最稳字段名固定不会因为系统版本差异出现列错位。按进程名查我习惯用pgreppgrep -a nginx pstree -ap | grep nginxpgrep -a会同时显示PID和完整命令行pstree则把进程树画出来排查谁是谁的父进程时非常直观。2.2 top实时监控与load average的正确读法top默认按CPU占用排序进入交互界面后按P按CPU排、按M按内存排、按1展开每个核心、按c切换完整命令行。新手容易忽略第一行的load average分别是1分钟、5分钟、15分钟的平均负载。这里的负载不是CPU使用率而是处在运行队列和不可中断睡眠状态的进程平均数。判断负载是否异常要先看机器核数nproc。一个8核机器load average长期在16以上基本说明每个核平均排了近两个任务但如果只是瞬时冲到28又回落可能是定时任务或发布脚本造成的毛刺。顶部的%us、%sy、%wa、%st几个字段也很有讲究%wa高说明CPU在等IO%st高说明云服务器正在被宿主机抢占资源steal这俩都不是你直接在进程列表里能看出来的。2.3 htop、pstree、pgrep交互式定位进程的加分项htop是top的增强版支持鼠标操作、F5树状视图、F6排序、F9直接发信号。虽然很多服务器默认不装但排查复杂问题时空降装上一次效率提升明显。我实际用htop最频繁的场景是CPU跑满时切换成树状视图看哪个进程孵化了哪批子进程再按F9给对应进程发SIGTERM而不是SIGKILL。pstree -ap和pgrep -a适合在纯命令行环境里快速定位。定位到可疑PID后用top -p PID持续观察单个进程或者用pidstat 1 -p PID看它在每个采样周期里的CPU和内存变化比反复敲ps刷新更有价值。3. 信号与进程控制kill不是只有-93.1 信号的本质内核给进程发消息kill这个名字有误导性它不是杀死进程的唯一手段本质是给目标进程发一个信号。信号可以理解成内核或别的进程敲门通知你有事发生了。进程可以选择默认处理、忽略、或者调用自己注册的处理函数来响应。用kill -l可以列出系统支持的所有信号不同架构下数字可能不同所以脚本里建议写信号名而不是数字比如kill -TERM而不是kill -15。信号机制还解释了几个日常现象为什么SSH掉线后后台任务常常跟着死掉——因为终端关闭会向会话里的前台进程组发SIGHUP为什么CtrlC能中断命令——内核向前台进程组发送SIGINT为什么CtrlZ能暂停——发送SIGTSTP。进程组和会话的概念听着抽象但理解了它信号的行为就变得非常可预测。3.2 高频信号对照表与kill家族我用得最多的信号就这几个信号编号触发场景默认行为SIGHUP1终端挂断、SSH断开终止进程守护进程常用来重载配置SIGINT2CtrlC终止进程SIGQUIT3Ctrl\终止并产生core dumpSIGKILL9kill -9强制终止不可捕获、不可忽略SIGTERM15kill默认发送终止进程可被捕获做清理SIGCONT18恢复暂停进程继续执行SIGSTOP19kill -STOP暂停进程不可捕获、不可忽略SIGTSTP20CtrlZ暂停进程可被捕获对应的命令族有kill、killall、pkill三个。kill按PID发信号killall按进程名匹配pkill支持按命令行模式匹配。pkill -f run.sh看起来很省事但-f会匹配完整命令行容易误杀同名脚本的不同实例生产环境里用之前一定要先pgrep -af确认目标。3.3 为什么优先SIGTERM而不是SIGKILL我的原则是SIGKILL是最后手段除非进程明显失控或者服务已无响应否则一律先发SIGTERM。SIGTERM是可被进程捕获的信号一个写得规范的程序收到它后会停止接收新请求、处理完手头任务、刷新缓冲区、删除PID文件、释放数据库连接然后再退出。这种优雅停机对数据库、支付网关这类有状态服务尤其重要。我处理过最典型的反面案例有人图省事用kill -9杀掉一个在跑批量导入的Java进程结果留下半截事务和一堆临时表第二天业务方找上门来才发现问题。Kafka、MySQL这类服务其实都有自己的优雅停机脚本本质就是先发SIGTERM等它自己收拾残局。换个角度说如果你自己写服务也应该注册好信号处理逻辑让SIGTERM体面地死。另外kill -0 PID可以检查进程是否存在但实际不发送任何有效信号非常适合写监控脚本时探测进程存活状态。3.4 调整优先级nice与renice进程控制不只有信号还有调度优先级。Linux用nice值表示优先级范围是-20到19默认是0数值越低优先级越高。一个nice值为19的进程只有在CPU空闲时才会被调度非常适合备份、日志压缩这类别打扰线上业务的任务。nice -n 19 ./backup.sh renice -n 10 -p 1234普通用户只能把nice值调大降低优先级调成负数需要root权限。开机时用nice运行中用renice配合top里的NI列可以直观看到变化。我曾经在一个高负载服务器上把凌晨的批量任务统一nice成19再配合后面的定时任务配置线上服务明显少了很多抖动。4. 后台任务与长驻进程、nohup、tmux与systemd的边界4.1 作业控制、jobs、fg、bg与CtrlZ终端里直接跑长命令会占住当前会话最简单的后台化就是在命令末尾加sleep 300 jobs -l fg %1 bg %1jobs能看到当前shell的后台作业列表fg %1把作业调到前台CtrlZ把当前前台作业暂停bg %1让暂停的作业在后台继续跑。这套机制对临时手工操作够用但仅限于当前shell活着的时候。shell一旦退出依赖这个终端的后台作业就会收到SIGHUP多半活不成。所以我在真实服务器上很少把当作把任务丢在那里不管的方式它更适合会话内的临时切换真正要长期跑的任务需要后面几招。4.2 nohup与setsid摆脱终端依赖的关键nohup的作用是忽略SIGHUP让进程在终端关闭后继续存活。经典写法是nohup ./run.sh run.log 21 很多人只记住了格式没搞懂两个细节。第一21必须放在重定向后面表示把标准错误也送到同一个文件顺序反了会出错第二进程要真正脱离终端还应该把标准输入也重定向否则某些程序会因为读不到输入而异常。更稳妥的写法是nohup ./run.sh /dev/null run.log 21 setsid更进一步它会为进程创建一个全新的会话让它彻底脱离当前终端的进程组setsid ./run.sh /dev/null run.log 21 这个命令在写启动脚本时特别实用。不过说句实在话nohup加是最常见的但也是最粗糙的——没有自动重启、没有崩溃恢复、没有日志统一管理适合临时跑一把不适合生产环境提供服务。4.3 tmux比nohup更可控的多会话方案如果你经常需要在远程机器上开多个窗口跑任务tmux才是更现代化的选择。它的核心思路是会话session与终端解耦你关掉SSH会话里的程序照常运行下次连上来还能重新接回去。tmux new -s work Ctrlb d # detach tmux ls tmux attach -t work常用的快捷键有Ctrlb c创建新窗口Ctrlb n/p切换窗口Ctrlb %左右分屏Ctrlb 上下分屏Ctrlb [进入复制模式Ctrlb z放大当前窗格。nohup只能让单个命令不因挂断而死tmux则能让你随时回到现场看到之前的输出、继续操作交互式程序排查故障时这个能力极其关键。我在生产服务器上跑top或strace这类需要持续观察的命令时基本都会先开一个tmux会话这样就算网络抖动导致SSH断了观察进程本身也不会断。4.4 生产环境的长驻进程应该交给systemd最后说结论任何要长期对外提供服务的进程都不应该用、nohup甚至tmux来托底而应该写成systemd服务。一个典型单元文件长这样[Unit] DescriptionMy App Service [Service] Userweb ExecStart/opt/myapp/bin/server --config /etc/myapp.conf Restarton-failure RestartSec3 EnvironmentAPP_ENVproduction [Install] WantedBymulti-user.target启用方式systemctl daemon-reload systemctl enable --now myapp systemctl status myapp journalctl -u myapp -fsystemd带来的收益是其他后台化方式给不了的开机自动启动、崩溃自动拉起、重启间隔可调、日志统一走journald、可以用systemctl查看状态。Restartalways和Restarton-failure的区别也要注意——前者不管什么原因退出都会拉起后者只在非正常退出时拉起这会影响你对服务为什么反复重启的判断。5. 系统性能监控实战CPU、内存、磁盘IO、网络的排查闭环5.1 告警后先看全局uptime、free、df、vmstat机器出问题时我最忌讳一上来就盯着具体进程瞎猜。正确的姿势是从全局指标开始缩小范围顺序是负载、内存、磁盘、整体IOuptime nproc free -h df -hT vmstat 1 5uptime看负载曲线vmstat 1 5每秒采样5次重点看这几列r表示运行队列里的进程数b表示不可中断睡眠的进程数si/so表示内存换入换出wa表示CPU等待IO的时间占比。如果r长期大于核数说明CPU不够或者有进程在空转如果b很高、wa很高问题大概率出在磁盘如果si/so持续有值说明物理内存已经紧张系统在拿磁盘当内存用。5.2 CPU与内存从top到mpstat、pidstat全局看完后再往局部钻。CPU维度top已经能看整体但要看清是哪个核心在忙是用户态还是内核态忙就需要mpstat -P ALL 1 pidstat 1 -u pidstat 1 -rmpstat -P ALL 1把每个CPU核心的使用率单列出来能发现是不是某个核心被某个线程打满。pidstat -u按进程输出CPU占用pidstat -r输出内存占用比反复刷新top更适合持续观察和留档。内存维度free -h里的buff/cache经常让新手误判——这部分是内核给文件做的缓存内存紧张时会自动回收不能直接等同于可用内存为0。真正要警惕的是持续换页也就是vmstat里si/so一直不为0。再深一层如果怀疑OOM Killer杀了进程直接查内核日志journalctl -k | grep -i oom5.3 磁盘IO与网络iostat、iotop、ss、sar磁盘和网络是最容易被负载高这个表象掩盖的深层原因。磁盘先看iostat -x 1重点看%util和await。%util表示设备忙的时间占比传统机械盘到接近100%基本就是瓶颈但现在的SSD并发能力强%util高不一定代表性能见顶还要结合await平均IO请求等待时长判断。如果await跟着一起高说明请求真的在排队。用iotop -oP能看到谁在真正读写磁盘——那是一条命令直接锁定凶手的体验。网络层面我通常按这三步来ss -tlnp ss -s sar -n DEV 1 3ss -tlnp看监听端口对应的进程ss -s看连接总数和状态分布sar -n DEV看网卡吞吐量。交互式实时看各连接的流量用iftop -n排查连接数打满、端口冲突、网卡软中断高这些命令基本够用。5.4 一个完整的排查案例load高但CPU却不高我开篇提到的深夜告警完整链路是这样的uptime显示load 28但top里%idle还有六成说明CPU没被打满那负载哪来的再看vmstatb列持续十几wa很高基本锁定IO问题。用iostat -x 1确认磁盘%util已经跑满再用ps -eo pid,stat,wchan:30,cmd | awk $2 ~ /^D/ {print}找出所有D状态进程发现它们全是同一个清理脚本的子进程。问题根因是脚本里find的条件没加路径限制把某个目录下的文件全匹配上并发开启大量删除任务直接干穿了磁盘IO。修复方式也不是kill -9——这些进程当时卡在内核IO里根本杀不动我是先停掉脚本入口等IO队列消化再把find命令加上路径范围最后用pidstat -d 1确认每个进程的IO等待时间恢复正常。这个案例说明负载、CPU、IO、进程状态从来不是孤立指标排障就是先全局后局部、先现象后根因的循环。6. 定时任务cron、at与systemd timer的取舍6.1 cron配置与常见崩溃点定时任务最常用的是cron配置入口是crontab -e语法是五个时间字段加一条命令# 分 时 日 月 周 命令 0 2 * * * /opt/scripts/backup.sh */10 * * * * /opt/scripts/health.sh /var/log/health.log 21系统级任务还可以放在/etc/crontab或/etc/cron.d/区别是系统级配置多了一个用户字段。例如/etc/cron.d/mytask里要写0 3 * * * root /opt/scripts/cleanup.shcron最容易踩的坑有三个。第一PATH极简脚本里用了相对路径或python3这种命令很可能找不到脚本内一律用绝对路径。第二cron任务不会加载用户登录环境很多人发现脚本手动跑正常、定时跑就报错基本都是环境变量问题解决办法是在脚本开头source /etc/profile或用#!/bin/bash -l。第三任务没有标准输出重定向时cron会把输出通过邮件发给root没人看不说日积月累还会攒一堆积压邮件。另外如果某个任务执行时间可能超过周期要注意加锁防重入。flock是个轻量方案*/5 * * * * flock -n /tmp/xxx.lock /opt/scripts/xxx.sh避免上一个还没跑完下一个又启动把资源打爆。6.2 at一次性任务cron适合周期任务一次性延时任务用at更顺手echo /opt/scripts/upgrade.sh | at 23:00 atq atrm job-idat依赖atd服务适合两小时后自动执行某个变更这种场景。实际运维中发版后的延迟重启、低峰期临时清理我都用at因为它不会像cron那样留下周期性执行的隐患。6.3 systemd timer更现代的定时任务方案如果系统跑的是现代版本systemdsystemd timer在很多场景比cron更可靠。它的核心是一个.timer单元加一个同名.service单元timer负责触发service负责干活# /etc/systemd/system/cleanup.timer [Unit] DescriptionRun cleanup daily [Timer] OnCalendardaily Persistenttrue [Install] WantedBytimers.target# /etc/systemd/system/cleanup.service [Unit] DescriptionCleanup task [Service] Typeoneshot ExecStart/opt/scripts/cleanup.shsystemctl enable --now cleanup.timer systemctl list-timers --allPersistenttrue是cron没有的好东西如果系统在预定时间点处于关机状态开机后timer会补跑错过的任务。OnCalendar写法也灵活OnCalendar*-*-* 04:00:00表示每天凌晨4点OnBootSec15min表示开机15分钟后执行。再加上journalctl -u cleanup能直接看执行日志比cron翻邮件强太多。6.4 定时任务坑PATH、日志与并发定时任务排障时最值得养成的习惯是先看时间轴。journald里查cron自己的日志很方便journalctl -u cron --since 1 hour ago journalctl -u cleanup -fcron的日志通常也在/var/log/cron但如果任务本身输出全被吞了定位非常痛苦。我的经验是所有定时任务脚本自己负责写日志文件名带上日期启动时记录started结束前记录finished必要时记录返回码。看似繁琐但下次排障时你会感谢自己。7. 日志系统journalctl、rsyslog与logrotate的协作7.1 日志都在哪/var/log的那些文件.日志是系统监控里最容易被忽略的数据源。先建立一个地图RedHat系一般有/var/log/messages系统整体日志、/var/log/secure认证与安全日志、/var/log/dmesg内核日志Debian系常用/var/log/syslog和/var/log/auth.log。应用日志千奇百怪但多数服务都在/var/log/下有自己的子目录。还有一个大块头是/var/log/journal/journald自己持久化日志的地方。很多磁盘莫名其妙满了的问题最后都能在这里发现几GB甚至几十GB的日志文件。所以查看日志磁盘占用是基本功du -sh /var/log/* journalctl --disk-usage7.2 journalctl检索日志的核心命令journald把系统和服务的日志统一收拢journalctl是它的查询接口。我反复用的几个组合journalctl -b # 本次启动以来的日志 journalctl -b -p err # 本次启动以来的错误级别日志 journalctl -u nginx --since 1 hour ago journalctl -f # 实时跟踪 journalctl -k # 内核日志 journalctl -o json-pretty # 结构化输出-p err能过滤掉INFO和WARNING级别的噪音排障时先看到error往往最快-u 服务名直接定位某个unit的日志配合-f实时观察重启过程相当于给systemd服务装了个实时日志终端。journald自己也会占磁盘/etc/systemd/journald.conf里的SystemMaxUse可以限制上限改完重启journald生效紧急情况下用journalctl --vacuum-size200M立刻瘦身。7.3 rsyslog与logrotate日志的收集与轮转传统Linux日志的骨干是rsyslog它按facility和priority把日志路由到不同文件。配置文件在/etc/rsyslog.conf和/etc/rsyslog.d/核心语法是来源.级别 目标*.info;mail.none;authpriv.none /var/log/messages authpriv.* /var/log/secure理解了facility来源模块和priority级别你就能自己写规则把应用日志单独归档。不过日志也不只是写下来还要防它无限膨胀这个交给logrotate。全局配置在/etc/logrotate.conf各应用配置放在/etc/logrotate.d/。举个nginx的例子/var/log/nginx/*.log { daily rotate 14 compress delaycompress missingok notifempty sharedscripts postrotate /usr/sbin/nginx -s reopen endscript }含义是每天轮转一次保留14份轮转后压缩压缩延后一天保证最近一份可读日志文件为空不轮转轮转后执行nginx -s reopen让Nginx重新打开日志文件。调试配置不能直接拿线上赌先用试运行logrotate -d /etc/logrotate.d/nginx logrotate -f /etc/logrotate.d/nginx-d是debug模式只打印执行过程不实际动作确认无误后-f强制轮转一次验证效果。另外有些程序不响应日志重开信号logrotate可以用copytruncate先复制当前日志再清空原文件代价是可能丢极少量日志但换来了兼容性。顺便说一句凡是用systemd托管的应用如果直接写stdout/stderr日志会进journald不一定会写文件。我见过有人配了logrotate却发现文件根本不动就是因为日志去了journal而不是文件。先确认日志流向再决定用journalctl还是logrotate。7.4 用日志反推故障的小案例有一次业务反馈每天凌晨3点页面变慢我第一反应是查定时任务把/etc/cron.d/和各个用户的crontab -l都翻了一遍。凌晨3点确实有个日志压缩任务但看起来人畜无害。真正定位是靠journalctl把那个时间段所有error级别日志拉出来发现logrotate在凌晨3点10分左右执行完毕随后Nginx开始大量返回502。原因其实是logrotate轮转Nginx日志后/var/log/nginx/error.log重新创建文件权限没保持worker进程无权限写入新日志文件导致日志句柄异常。问题本身不算复杂但如果不是把日志、定时任务、性能监控三条线交叉验证单看哪一边都很难闭环。最后说几句自己的体会这套进程管理与系统监控的功夫我是一步步练出来的最有价值的不是记住了多少命令而是建立了一种先看现象、再分层次、后定位根因的肌肉记忆。遇到机器卡死先分清是CPU、内存、磁盘还是网络问题遇到进程杀不掉先想它是D状态还是Z状态再决定要不要动父进程遇到服务半夜自己挂掉先翻journal再查定时任务而不是盲目加Restartalways掩盖问题。如果你刚开始学我给一个很土但很有效的建议把每一条命令都放到自己的服务器上跑一遍故意制造点异常——比如用yes打满CPU、用dd产生IO压力、写个脚本留下僵尸进程然后挨个用top、ps、vmstat、iostat、journalctl去观察。亲手看一遍D状态和新手期看十篇文章效果完全不同等真上了生产你会感谢这些当年故意折腾出来的事故。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。