概述

完成软件包管理之后,Linux 系统管理进入运行时层面的核心主题:进程管理。软件包把二进制、配置文件、库文件和 systemd 单元安装到系统中,但真正消耗 CPU、内存、文件句柄、端口、磁盘 I/O 和网络连接的是进程。一个服务启动失败、CPU 长时间打满、内存持续增长、端口被占用、僵尸进程堆积、批处理脚本误杀业务进程,最终都要回到进程视角定位。

本文是 Linux 系统管理系列第 6 篇,承接上一篇《软件包管理》。上一篇解释软件如何被安装、升级、审计和回滚;本文进一步解释软件运行后如何成为进程,进程如何被内核调度、如何被 systemd 管理、如何用信号终止、如何观察资源消耗,以及在生产环境中如何建立一套可复用的进程排障路径。

为什么进程管理容易被误解

进程管理看起来只是 pstopkill 几个命令,但生产事故往往发生在命令背后的边界没有想清楚:

  1. 进程不是服务的全部:一个 systemd service 可能包含主进程、子进程、工作进程、临时 helper 和同一个 cgroup 下的多个线程。只看一个 PID 可能漏掉真正消耗资源的对象。
  2. 杀进程不是排障方法kill -9 可以快速清理现象,也可能跳过清理逻辑、留下锁文件、破坏事务、触发 supervisor 反复拉起。
  3. CPU 高和系统慢不是同一件事:CPU user、system、iowait、steal、load average、run queue 分别代表不同问题,不能只凭一个百分比判断。
  4. 内存“用满”未必异常:Linux 会积极使用 page cache。判断内存压力要看 available、swap、OOM、RSS、cgroup 限制和业务延迟,而不是只看 used。
  5. 进程状态有上下文DRSTZ 不是简单的好坏标签。僵尸进程需要处理父进程,D 状态进程常常指向 I/O 或内核等待。
  6. 服务管理优先于裸 PID 管理:由 systemd 管理的服务,应优先使用 systemctljournalctlsystemd-cgls 和 unit 配置排查,而不是绕过服务管理器直接操作 PID。

学习目标

通过本文的学习,您将能够:

  1. 理解进程、线程、PID、PPID、进程组、会话、cgroup 和 systemd unit 的关系。
  2. 使用 pstophtoppgreppidofpstreelsofssjournalctl 等命令定位进程问题。
  3. 正确理解进程状态、信号、退出码、僵尸进程、孤儿进程和服务重启行为。
  4. 处理 CPU 飙高、内存增长、端口占用、文件句柄泄漏、进程无法退出、服务反复重启等常见问题。
  5. 为生产服务建立安全的启动、停止、重载、资源限制和排障流程。

先建立运行时心智模型

运行时模型

Linux 中的进程是一个正在运行的程序实例。它拥有自己的地址空间、打开的文件、环境变量、当前工作目录、用户和组身份、资源限制、信号处理方式以及调度状态。线程可以理解为同一进程内共享部分资源的执行流;在 Linux 内核里,线程和进程都属于 task,只是在资源共享方式上不同。

这张图说明了进程管理的基本顺序:先确认进程从哪里来,再观察它在运行时占用了什么资源,最后判断应该通过信号、服务管理、资源限制、配置修复还是代码修复来处理。不要直接从“机器慢”跳到“杀掉 CPU 最高的 PID”。

PID、PPID 与进程树

每个进程都有一个 PID。父进程创建子进程后,子进程会记录父进程的 PID,也就是 PPID。进程树能帮助我们回答几个关键问题:

  1. 这个进程是由 systemd、shell、cron、应用 master 进程,还是某个临时脚本拉起的?
  2. 如果杀掉子进程,父进程是否会重新拉起它?
  3. 如果父进程异常退出,子进程会被谁接管?
  4. 僵尸进程属于哪个父进程,父进程为什么没有回收它?

常用命令:

bash
ps -ef
ps -eo pid,ppid,user,stat,etime,cmd --sort=ppid,pid
pstree -aps <PID>
pgrep -a nginx
pidof sshd

pstree -aps 对理解多进程服务特别有用。例如 Nginx、PostgreSQL、Redis Sentinel、Celery worker、Java 应用启动脚本,通常都会形成一棵进程树。只看单个 PID 很容易误判。

进程、服务和 cgroup 的关系

现代 Linux 服务器大多由 systemd 作为 init 系统。systemd 不只是启动脚本替代品,它会把服务进程放入对应 cgroup,记录 unit 状态,处理停止超时,按规则发送信号,并能为服务设置资源限制。也就是说,生产服务的“进程边界”通常不是裸 PID,而是某个 systemd unit 的 cgroup。

排查 systemd 服务时,建议先使用:

bash
systemctl status nginx.service
systemctl show nginx.service -p MainPID -p ControlPID -p SubState -p Result
systemd-cgls /system.slice/nginx.service
journalctl -u nginx.service -n 100 --no-pager

这些命令能把进程树、服务状态、最近日志和 systemd 对服务的判断合在一起。直接 kill <PID> 可能绕过 unit 生命周期,导致 systemd 认为服务异常退出并自动重启。


进程状态:先判断它在等什么

进程状态与负载判断

状态判断

psSTAT 字段和 top 的状态列是进程排障的入口。Linux 手册中 /proc/<pid>/stat 会记录进程状态,常见状态如下:

状态 含义 常见场景 排查方向
R Running 或 runnable,正在运行或等待 CPU CPU 密集、run queue 堆积 看 CPU、调度、线程数、负载
S Interruptible sleep,可中断睡眠 等待网络、定时器、锁、输入 多数正常空闲进程都是 S
D Uninterruptible sleep,不可中断睡眠 等待磁盘、NFS、块设备、内核 I/O 看 I/O、挂载、存储、内核日志
T Stopped,被信号暂停或调试 SIGSTOP、调试器、作业控制 看父进程、终端、调试工具
Z Zombie,已退出但未被父进程回收 父进程没有 wait 子进程 处理父进程,不是 kill 僵尸本身
I Idle kernel thread 内核线程 通常不是业务问题

查看状态:

bash
ps -eo pid,ppid,user,stat,pri,ni,pcpu,pmem,etime,wchan:24,cmd --sort=-pcpu | head -30
cat /proc/<PID>/status
cat /proc/<PID>/stat

wchan 字段可以显示进程当前在内核中等待的大致位置。它不是万能答案,但对 D 状态排查很有帮助。例如大量进程卡在 NFS、磁盘 I/O 或文件系统调用附近时,应把注意力转向存储、挂载和内核日志,而不是继续发送信号。

进程状态流转

这张图强调一点:僵尸进程已经退出,不再执行代码,也不再消耗 CPU;它保留的是退出状态和少量进程表信息。真正的问题通常是父进程没有回收子进程,或者子进程退出速度远快于父进程回收速度。

load average 与进程状态

uptimetop 显示的 load average 不是 CPU 使用率,而是一定时间窗口内处于可运行状态和不可中断睡眠状态的任务平均数量。单核机器 load 4 很高,32 核机器 load 4 可能很轻;如果大量任务处于 D 状态,load 也会升高,但 CPU 使用率可能并不高。

基础观察命令:

bash
uptime
nproc
top
vmstat 1 5
mpstat -P ALL 1 5 2>/dev/null || true

判断时可以按下面顺序看:

  1. load 是否长期高于 CPU 核心数。
  2. vmstatr 是否持续高,表示可运行队列拥堵。
  3. vmstatb 是否高,表示不可中断睡眠任务较多。
  4. CPU 是 user 高、system 高、iowait 高,还是 steal 高。
  5. 是单个进程高,还是大量进程共同推高。

常用观察命令:不要只会 top

进程观察命令路径

观察路径

top 适合交互观察,但生产排障需要能把结果记录下来,便于复盘和交接。以下命令覆盖进程列表、资源占用、文件句柄、端口、环境和服务日志。

ps:一次性快照

bash
# CPU 排序
ps -eo pid,ppid,user,stat,pcpu,pmem,etime,cmd --sort=-pcpu | head -20

# 内存 RSS 排序
ps -eo pid,ppid,user,stat,rss,vsz,pmem,etime,cmd --sort=-rss | head -20

# 按服务用户查看
ps -u nginx -o pid,ppid,stat,pcpu,pmem,etime,cmd

# 查看线程
ps -L -p <PID> -o pid,tid,psr,stat,pcpu,comm

RSS 是常驻内存,VSZ 是虚拟地址空间。很多语言运行时会预留较大虚拟地址空间,不能只凭 VSZ 判断内存泄漏。更可靠的方式是观察 RSS 趋势、cgroup 内存、应用内部指标和 OOM 记录。

top、htop 与 pidstat

bash
top -H -p <PID>
pidstat -p <PID> 1 5 2>/dev/null || true
pidstat -r -p <PID> 1 5 2>/dev/null || true
pidstat -d -p <PID> 1 5 2>/dev/null || true

top -H 可以展开线程,对 Java、Go、数据库、多线程代理很有用。pidstat 来自 sysstat 包,适合记录一段时间内的 CPU、内存、I/O 趋势;如果没有安装,不要在事故中临时破坏环境,可以先用现有命令收集证据,事后再补齐基线工具。

lsof 与 ss:文件和端口视角

进程问题经常表现为“端口被占用”“文件无法删除”“日志空间不释放”“打开文件过多”。这些都需要从文件描述符和 socket 视角看。

bash
# 查看进程打开的文件
sudo lsof -p <PID> | head -50

# 查看删除但仍被进程占用的文件
sudo lsof +L1

# 查看监听端口
sudo ss -lntup

# 查看某端口对应进程
sudo ss -lntup 'sport = :8080'

# 查看进程 fd 数量
ls /proc/<PID>/fd | wc -l
cat /proc/<PID>/limits

如果 df 显示空间满,但 du 找不到大文件,优先检查 lsof +L1。这通常是日志文件被删除后仍被进程持有。正确处理方式是让服务重新打开日志文件,或者重启相关服务,而不是继续删除更多文件。

/proc:进程事实来源

/proc 是进程管理的事实入口。很多工具最终都读取 /proc/<PID> 下的数据。

路径 用途
/proc/<PID>/cmdline 启动命令,参数以空字符分隔
/proc/<PID>/environ 环境变量,排查配置注入时有用
/proc/<PID>/cwd 当前工作目录
/proc/<PID>/exe 可执行文件路径
/proc/<PID>/fd/ 打开的文件描述符
/proc/<PID>/limits 资源限制
/proc/<PID>/status 状态、内存、线程、能力等摘要
/proc/<PID>/task/ 线程列表

示例:

bash
tr '\0' ' ' < /proc/<PID>/cmdline; echo
sudo tr '\0' '\n' < /proc/<PID>/environ | sort
readlink -f /proc/<PID>/exe
readlink -f /proc/<PID>/cwd
cat /proc/<PID>/limits

环境变量可能包含密钥,不要随意贴到工单、聊天群或知识库中。排障记录需要脱敏。


信号与停止流程:优雅退出优先

安全停止与 systemd 边界

停止边界

Linux 通过信号通知进程发生了某类事件。kill 这个命令名容易误导,它并不一定是“杀死”,本质是向进程发送信号。默认发送 SIGTERM,表示请求进程终止;SIGKILL 才是不可捕获、不可忽略的强制终止。

常见信号:

信号 常用编号 作用 生产建议
SIGHUP 1 传统上用于重新加载配置或终端断开 是否支持取决于程序
SIGINT 2 类似 Ctrl+C 适合前台进程
SIGTERM 15 请求进程终止 优先使用
SIGKILL 9 内核强制终止,进程无法清理 作为最后手段
SIGSTOP 19/17/23 暂停进程,不能捕获 临时止血需谨慎
SIGCONT 18/19/25 继续暂停的进程 与 SIGSTOP 配合
SIGUSR1/2 10/12 用户自定义 需看应用文档

不同架构上部分编号可能不同,脚本里建议使用信号名而不是数字:

bash
kill -TERM <PID>
kill -KILL <PID>
pkill -TERM -f 'worker.py'

安全停止决策树

这张图的核心是不要一开始就 kill -9。强制终止会跳过应用清理流程,可能导致临时文件、锁、事务、连接池和下游状态异常。除非已经确认服务无响应且影响大于风险,否则应先收集现场。

systemd 如何停止服务

systemd 停止服务时,会按 unit 配置执行停止流程。常见逻辑包括运行 ExecStop=、发送 KillSignal=、等待 TimeoutStopSec=,必要时向剩余进程发送最终信号。实际行为受 KillMode=SendSIGKILL=FinalKillSignal= 等配置影响。

查看相关配置:

bash
systemctl cat <unit>
systemctl show <unit> -p KillMode -p KillSignal -p FinalKillSignal -p SendSIGKILL -p TimeoutStopUSec
systemctl status <unit>

生产建议:

  1. 对 systemd 服务优先使用 systemctl stoprestartreload
  2. 如果服务支持平滑重载,应在 unit 中定义正确的 ExecReload=
  3. 对退出较慢的数据库、队列、批处理 worker,不要盲目缩短 TimeoutStopSec=
  4. 遇到服务停止卡住,先看日志、进程状态、打开文件和下游依赖,再决定是否强制终止。

CPU 异常:区分计算、内核、I/O 和调度

CPU 与内存异常排查路径

资源排查

CPU 高是最常见的告警之一,但“CPU 高”不是根因。需要先判断 CPU 时间花在 user、system、iowait、steal 还是软中断上,再定位到具体进程、线程或内核路径。

快速定位高 CPU 进程

bash
uptime
nproc
ps -eo pid,ppid,user,stat,pcpu,pmem,etime,cmd --sort=-pcpu | head -20
top -c

如果是多线程进程,例如 Java、Go、数据库、代理程序,需要展开线程:

bash
top -H -p <PID>
ps -L -p <PID> -o pid,tid,psr,stat,pcpu,pmem,comm --sort=-pcpu | head

拿到高 CPU 线程 ID 后,下一步取决于语言和工具链。Java 可以结合 jstackjcmd;Go 可以结合 pprof;Python 可以结合 py-spy 或应用日志;数据库要用自己的慢查询、等待事件和性能视图。系统管理员不一定要现场完成代码级诊断,但必须把进程、线程、时间窗口、资源指标和业务现象记录清楚。

load 高但 CPU 不高

如果 load 高、CPU 不高,常见原因是 D 状态任务或 I/O 等待。检查:

bash
vmstat 1 5
ps -eo pid,ppid,stat,wchan:24,cmd | awk '$3 ~ /D/ {print}'
dmesg -T | tail -100
iostat -xz 1 5 2>/dev/null || true

大量 D 状态进程可能与以下问题有关:

  1. 本地磁盘故障或延迟严重。
  2. NFS、CIFS 等网络文件系统不可达。
  3. 存储阵列、云盘、虚拟化底层抖动。
  4. 文件系统或块设备驱动异常。
  5. 应用大量同步写入,触发 I/O 队列堆积。

D 状态进程通常无法被普通信号立即终止,因为它们正在等待不可中断的内核路径返回。此时反复 kill -9 没有意义,应优先恢复底层 I/O 或隔离故障挂载。


内存异常:看趋势,不看单点

Linux 会把空闲内存用于缓存,所以 free 中的 used 偏高不一定是问题。真正要关注的是 available 是否持续下降、swap 是否频繁进出、OOM 是否发生、某个进程 RSS 是否持续增长、cgroup 是否达到限制。

基础命令:

bash
free -h
vmstat 1 5
ps -eo pid,ppid,user,stat,rss,vsz,pmem,etime,cmd --sort=-rss | head -20
cat /proc/meminfo | head -30
journalctl -k --since "2 hours ago" | grep -Ei 'out of memory|oom|killed process' || true

RSS、VSZ、cache 与 swap

指标 含义 判断方式
RSS 进程实际驻留物理内存 更接近进程当前占用,但仍包含共享页影响
VSZ 进程虚拟地址空间 可能很大,不等于真实内存占用
available 内核估算可用于新进程的内存 比 free 更有参考价值
buff/cache 缓冲和页缓存 可回收,不应简单视为浪费
swap in/out 交换活动 持续活跃通常代表内存压力

进程内存增长要看趋势。一次 ps 只能说明当前状态,不能证明泄漏。建议每隔一段时间记录:

bash
while true; do
  date
  ps -p <PID> -o pid,ppid,rss,vsz,pmem,etime,cmd
  sleep 30
done

生产环境更推荐由监控系统采集进程 RSS、cgroup memory、OOM 事件、GC 指标、请求量和延迟。只有把业务压力和内存趋势放在一起,才能区分正常扩容缓存、流量增长、配置过大、内存泄漏和异常循环。

cgroup 内存限制

systemd 可以为服务设置内存限制。容器、Kubernetes、systemd slice 也都会使用 cgroup。进程被 OOM kill 时,不一定是整机内存不足,也可能是当前 cgroup 达到限制。

bash
systemctl show <unit> -p MemoryCurrent -p MemoryMax -p MemoryHigh
systemd-cgtop
cat /proc/<PID>/cgroup

如果是容器内进程,还要同时看容器限制和宿主机压力。不要只在容器里执行 free -h 就下结论,因为不同运行时和 cgroup 版本下展示方式可能不同。

OOM 排查流程

OOM 排查要保留内核日志中的关键信息:被杀进程、PID、内存占用、oom_score、cgroup、触发时间。不要只记录“服务挂了”。


僵尸、孤儿和反复重启

僵尸进程怎么处理

僵尸进程显示为 Z<defunct>。它已经退出,但父进程还没有通过 wait 系列调用读取退出状态。单个短暂僵尸通常无害;大量僵尸持续堆积才需要处理。

查看僵尸:

bash
ps -eo pid,ppid,stat,etime,cmd | awk '$3 ~ /Z/ {print}'
pstree -aps <ZOMBIE_PID>

处理思路:

  1. 找到僵尸进程的 PPID。
  2. 判断父进程是什么服务或脚本。
  3. 查看父进程是否卡死、是否还能回收子进程。
  4. 如果父进程由 systemd 管理,优先重启对应服务。
  5. 如果父进程本身异常,需要保留日志和现场,交由应用修复子进程回收逻辑。

僵尸进程本身不能通过 kill -9 <zombie_pid> 清理,因为它已经不再执行。通常要让父进程回收,或者终止父进程后由 PID 1 接管并回收。

孤儿进程和会话残留

父进程退出后仍在运行的子进程会变成孤儿进程,由 PID 1 或子 reaper 接管。孤儿进程不一定异常,后台任务、daemon 化程序、容器进程都有可能出现。但如果脚本退出后留下大量业务 worker、临时 rsync、压缩进程或数据库导出进程,就要判断是否存在清理逻辑缺失。

排查:

bash
ps -eo pid,ppid,sid,pgid,user,stat,etime,cmd --sort=ppid,pid | head -100
loginctl list-sessions 2>/dev/null || true
systemd-cgls

对用户会话残留进程,要谨慎处理。某些系统启用了用户 lingering,允许用户服务在退出登录后继续运行;某些批处理任务则是意外残留。关键是确认来源和期望生命周期。

服务反复重启

systemd 服务反复重启时,不要只看到进程在不断变换 PID 就认为“系统自己好了”。反复重启通常意味着启动后立即退出、健康检查失败、依赖不可用、配置错误、权限不足或资源限制触发。

bash
systemctl status <unit>
journalctl -u <unit> --since "30 minutes ago" --no-pager
systemctl show <unit> -p NRestarts -p Restart -p RestartUSec -p Result -p ExecMainStatus

重点关注:

  1. ExecMainStatusResult
  2. 是否触发 StartLimitBurst
  3. 是否因为端口占用启动失败。
  4. 是否因为配置文件语法错误退出。
  5. 是否被 OOM 或 cgroup 限制终止。
  6. 是否缺少权限、目录、环境变量或依赖服务。

端口占用、文件句柄和资源限制

进程管理不仅是 CPU 和内存,还包括端口、文件、进程数和 ulimit。

端口占用

bash
sudo ss -lntup
sudo ss -lntup 'sport = :80'
sudo lsof -iTCP:80 -sTCP:LISTEN

发现端口被占用后,不要立即杀进程。先确认:

  1. 这个进程是否属于同一个服务的旧实例。
  2. 是否有 systemd socket activation。
  3. 是否是容器、反向代理、sidecar 或调试进程占用。
  4. 是否有 IPv4/IPv6 双栈监听差异。
  5. 是否存在配置里端口写错导致冲突。

文件句柄过多

报错 Too many open files 时,需要同时看进程限制和系统限制:

bash
cat /proc/<PID>/limits | grep -i 'open files'
ls /proc/<PID>/fd | wc -l
sudo lsof -p <PID> | awk '{print $5}' | sort | uniq -c | sort -n | tail
sysctl fs.file-nr

如果 fd 数持续增长,常见原因包括连接泄漏、日志文件未关闭、文件轮转不当、客户端长连接积压、代码异常路径没有关闭句柄。临时提高限制可以止血,但根因通常在应用连接管理或运行参数。

systemd 服务可通过 drop-in 设置限制:

bash
sudo systemctl edit <unit>

示例:

ini
[Service]
LimitNOFILE=65535
TasksMax=4096

修改后:

bash
sudo systemctl daemon-reload
sudo systemctl restart <unit>
systemctl show <unit> -p LimitNOFILE -p TasksMax

资源限制与服务边界

systemd 的资源限制可以帮助防止单个服务拖垮整机。常见配置包括:

配置 作用 使用建议
CPUQuota= 限制 CPU 配额 对后台任务、批处理有用,核心服务慎用
MemoryMax= 限制最大内存 可防止失控,但要结合峰值和缓存
TasksMax= 限制进程/线程数量 防 fork bomb 或线程爆炸
LimitNOFILE= 文件描述符上限 网络服务常需要调整
IOWeight= / IODeviceWeight= I/O 权重 多服务共享磁盘时可考虑

资源限制不是越小越安全。限制过紧会导致服务在高峰期异常退出,限制过松又失去保护意义。应基于历史监控、压测、业务峰值和故障演练设置。


生产排障 runbook

生产进程排障闭环

排障闭环

当收到“服务慢”“CPU 高”“进程异常”“端口占用”这类告警时,可以按固定路径收集证据。重点是先保留现场,再变更。

一次通用排查流程

建议的现场采集命令

bash
mkdir -p ~/incident-process-$(date +%F-%H%M%S)
cd ~/incident-process-$(date +%F-%H%M%S)

date > date.txt
hostnamectl > hostnamectl.txt 2>&1
uptime > uptime.txt
free -h > free.txt
vmstat 1 5 > vmstat.txt
df -hT > df.txt
ps -eo pid,ppid,user,stat,pcpu,pmem,rss,vsz,etime,wchan:24,cmd --sort=-pcpu > ps-cpu.txt
ps -eo pid,ppid,user,stat,pcpu,pmem,rss,vsz,etime,wchan:24,cmd --sort=-rss > ps-mem.txt
ss -lntup > ss-listen.txt 2>&1
journalctl -p warning --since "2 hours ago" --no-pager > journal-warning.txt 2>&1
journalctl -k --since "2 hours ago" --no-pager > kernel.txt 2>&1

如果已经知道目标服务:

bash
systemctl status <unit> > unit-status.txt 2>&1
systemctl show <unit> > unit-show.txt 2>&1
journalctl -u <unit> --since "2 hours ago" --no-pager > unit-journal.txt 2>&1
systemd-cgls > cgls.txt 2>&1
systemd-cgtop -b -n 3 > cgtop.txt 2>&1

这些文件可以用于交接和复盘。注意脱敏环境变量、命令参数中的密码、日志中的 token 和业务数据。

常见问题对照表

现象 首查命令 常见根因 处理方向
CPU 高 top -Hps --sort=-pcpu 代码循环、查询暴涨、压缩加密、线程池异常 限流、采样、定位线程、应用修复
load 高 CPU 不高 vmstatps stat D I/O 等待、NFS 卡死、存储延迟 恢复底层 I/O、隔离挂载
内存持续增长 ps --sort=-rssjournalctl -k 泄漏、缓存无限制、流量增长、cgroup 太小 趋势分析、重启止血、修配置或代码
端口占用 ss -lntuplsof -i 旧实例、配置冲突、socket activation 查来源,避免误杀
僵尸进程多 ps stat Zpstree 父进程未 wait 处理父进程或应用逻辑
文件句柄耗尽 lsof -p/proc/PID/limits 连接泄漏、日志轮转、限制太低 修泄漏,合理提高限制
服务反复重启 systemctl statusjournalctl -u 配置错误、依赖缺失、OOM、端口冲突 修 unit、配置、资源和依赖
删除文件空间不释放 lsof +L1 进程仍持有已删除文件 让服务重新打开文件或重启

日常治理:把进程管理变成习惯

进程问题不能只靠事故时排查。日常应建立基础治理。

服务启动方式标准化

生产服务应尽量由 systemd、容器平台或明确的进程管理器托管。不要长期使用 nohup command &、手工 screen、临时 shell 后台任务承载核心服务。临时方式会带来几个问题:

  1. 启动命令难审计。
  2. 日志位置不统一。
  3. 崩溃后不会按策略重启。
  4. 退出登录、会话、环境变量变化可能影响运行。
  5. 资源限制和权限边界不清晰。

如果必须临时启动,至少记录启动人、启动命令、目录、环境、日志和停止方式,并尽快转成正式 unit。

指标和告警基线

建议纳入监控的进程指标包括:

  1. 服务是否存活,systemd unit 是否 active。
  2. 进程数、线程数、重启次数。
  3. CPU 使用率和负载。
  4. RSS、cgroup memory、OOM 事件。
  5. 打开文件数、连接数、监听端口。
  6. 关键错误日志数量。
  7. 请求量、队列长度、延迟等业务指标。

只监控进程存活是不够的。进程活着但不处理请求、线程池满、连接池耗尽、磁盘写不动,都会造成业务不可用。

变更前后验证

重启服务、调整资源限制、修改 unit、升级软件包后,应验证:

bash
systemctl is-active <unit>
systemctl status <unit> --no-pager
journalctl -u <unit> -n 50 --no-pager
ss -lntup | grep '<port>'
ps -eo pid,ppid,user,stat,pcpu,pmem,etime,cmd | grep '<process-name>' | grep -v grep

如果服务有健康检查接口,还应执行:

bash
curl -fsS http://127.0.0.1:<port>/health

验证命令要结合具体服务修改,不能把示例端口和路径原样复制到所有系统。


实战案例:一次业务 worker 失控的排查

下面用一个典型场景把前面的命令串起来。假设某台应用服务器在下午业务高峰收到告警:负载从 2 升到 35,接口延迟明显上升,监控显示 CPU 使用率接近 100%,但业务并没有完全中断。该主机运行一个 Web 服务和一组后台 worker,均由 systemd 管理。

第一步:确认影响和资源类型

管理员先确认主机没有磁盘满、内存 OOM 或网络中断:

bash
uptime
free -h
vmstat 1 5
df -hT
systemctl --failed

观察结果显示 load 很高,vmstatr 队列持续高于 CPU 核心数,wa 不高,free 中 available 仍充足,没有明显 swap 活动。这说明问题更像 CPU 计算或线程调度拥堵,而不是磁盘 I/O 或内存耗尽。此时不需要立刻重启整台机器,应该继续定位哪个进程、哪个线程、哪个服务造成压力。

第二步:定位进程和线程

bash
ps -eo pid,ppid,user,stat,pcpu,pmem,etime,cmd --sort=-pcpu | head -20
systemctl status app-worker.service --no-pager
top -H -p <worker-main-pid>

结果显示 app-worker.service 下多个线程 CPU 长时间接近 100%,Web 主进程本身 CPU 不高。systemd-cgls /system.slice/app-worker.service 显示这些高 CPU 线程都属于同一个 worker unit。这个发现非常关键:如果管理员只凭端口或进程名重启 Web 服务,不会解决后台 worker 的问题,还会扩大影响。

第三步:结合日志判断业务原因

bash
journalctl -u app-worker.service --since "30 minutes ago" --no-pager
systemctl show app-worker.service -p NRestarts -p MainPID -p MemoryCurrent -p TasksCurrent

日志中出现大量同一类任务重试,且每次失败后立即重新入队。结合业务监控可见,某个外部接口从 14:10 开始返回异常,worker 没有退避策略,导致任务反复执行、CPU 被重试逻辑占满。此时根因并不是 Linux 调度异常,而是应用层重试风暴。

第四步:止血要控制边界

如果直接 kill -9 worker,systemd 可能按 Restart=always 立即拉起,任务队列继续重试,CPU 很快再次打满。更稳妥的止血方式通常有几类:

  1. 在业务入口或队列侧暂停相关任务类型。
  2. 临时降低 worker 并发,避免继续压垮主机。
  3. 如果服务支持优雅停止,使用 systemctl stop app-worker.service,并确认不会丢任务。
  4. 对已经失控但无法优雅退出的进程,先采集线程、日志、队列状态,再执行强制终止。

可以先查看 unit 是否配置了重启策略:

bash
systemctl show app-worker.service -p Restart -p RestartUSec -p KillMode -p TimeoutStopUSec

如果需要临时禁止自动重启,必须记录原因,并在事故后恢复配置。不要在事故中随手改 unit 文件而不留痕。

第五步:恢复后验证

止血后要验证两个层面。系统层面看负载、CPU、进程数、日志;业务层面看队列积压、任务成功率、接口延迟、错误率。

bash
uptime
ps -eo pid,ppid,user,stat,pcpu,pmem,etime,cmd --sort=-pcpu | head -20
journalctl -u app-worker.service -n 100 --no-pager
systemctl status app-worker.service --no-pager

如果重启 worker 后 CPU 恢复正常,但队列积压仍在增长,说明只是暂时释放主机压力,业务处理能力仍不足。后续需要修复重试策略、增加熔断、限制单类任务并发、补充外部依赖告警,并把“外部接口异常导致本地 worker 风暴”写入故障知识库。

这类案例的复盘重点

进程管理复盘不要只写“重启后恢复”。建议至少回答:

  1. 哪个 unit、哪个进程、哪个线程或哪类任务造成资源异常。
  2. 为什么监控先看到主机 CPU,而不是更早看到任务重试或外部接口异常。
  3. systemd 的重启策略、并发配置、资源限制是否扩大了影响。
  4. 当时有没有保留足够现场,例如日志、线程、队列、cgroup 指标。
  5. 下次是否可以通过限流、退避、熔断、隔离队列或资源限制自动止血。

这个案例说明,Linux 进程管理的价值不是替代应用排障,而是把运行时事实整理清楚,帮助团队从“机器很慢”快速收敛到“某个服务的某类任务出现重试风暴”。


进程治理清单

为了让日常操作可复用,可以把进程治理拆成基线、巡检、变更、事故和复盘五类清单。

基线清单

  1. 核心服务必须由 systemd、容器平台或明确的 supervisor 管理。
  2. 每个服务应有负责人、启动方式、停止方式、日志位置、健康检查和回滚说明。
  3. 服务用户应使用最小权限,不使用 root 运行普通业务进程。
  4. 长期服务应配置合理的 Restart=TimeoutStopSec=LimitNOFILE=TasksMax=
  5. 关键服务要明确是否支持 reload,不能把 restart 当作所有配置变更的默认动作。
  6. 需要高并发连接的服务要提前验证文件描述符限制、端口范围、连接跟踪和内核参数。
  7. 批处理、备份、压缩、索引、报表任务应错峰运行,并限制并发和资源。

巡检清单

巡检不是每天人工看一遍 top,而是确认系统是否存在长期积累的异常信号:

bash
systemctl --failed
ps -eo stat | sort | uniq -c
ps -eo pid,ppid,stat,etime,cmd | awk '$3 ~ /Z/ {print}'
ss -s
sudo lsof +L1 2>/dev/null | head

巡检关注点包括:是否有失败 unit、僵尸进程是否持续存在、删除文件是否仍被占用、监听端口是否符合基线、服务重启次数是否异常、CPU 和内存是否出现新趋势。对生产环境,应尽量把这些巡检转成监控规则或自动化报告,而不是依赖管理员记忆。

变更清单

涉及进程的变更包括服务重启、升级软件包、修改 unit、调整并发、更新资源限制、切换端口、迁移运行用户等。变更前应确认:

  1. 哪些 PID 或 unit 会受影响。
  2. 是否有活跃请求、长事务、批处理任务或队列消费。
  3. 是否需要摘流、暂停任务、通知业务或安排维护窗口。
  4. 是否可以 reload,还是必须 restart。
  5. 回滚方式是恢复配置、降级包、切回旧实例,还是重启旧服务。
  6. 验证点是端口、健康检查、日志、业务接口还是队列消费。

变更后不要只看命令返回 0。要确认服务状态、日志、端口、进程、资源使用和业务指标都符合预期。

事故清单

事故中最容易犯的错误是边查边改,最后无法复盘。建议先保存最小现场:

  1. 告警时间、主机、服务、影响范围。
  2. uptimefreevmstatdfpsss 快照。
  3. 目标 unit 的 systemctl statussystemctl showjournalctl
  4. 如果是 CPU 问题,记录高 CPU 线程和采样时间。
  5. 如果是内存问题,记录 RSS、OOM 日志、cgroup 限制。
  6. 如果是端口或 fd 问题,记录 sslsoflimits
  7. 每一次止血动作的执行人、命令、时间和结果。

这些信息不要求完美,但要足以让后续人员知道“当时到底发生了什么”。没有现场的事故,很容易在复盘时变成猜测。

复盘清单

复盘不应停留在“某进程异常,已重启”。更有价值的复盘应形成可执行改进:

复盘问题 可能产出
为什么这个进程会失控? 配置修复、代码修复、容量调整
为什么监控没有更早发现? 新增业务指标、队列指标、重启次数告警
为什么止血需要人工判断? 自动限流、熔断、降级、runbook
为什么影响扩散到整机? cgroup 限制、服务隔离、任务拆分
为什么恢复验证不充分? 健康检查、合成探测、验收脚本

进程治理最终要服务于稳定性:减少未知进程、减少手工启动、减少无边界资源消耗、减少粗暴终止,把运行时行为纳入可观察、可控制、可复盘的体系。


常见反模式

1. 看到高 CPU 就 kill -9

高 CPU 可能是正常批处理、流量峰值、压缩任务、索引构建、备份、GC、慢查询或死循环。直接 kill -9 会丢失现场,也可能导致服务被 supervisor 重新拉起后继续高 CPU。正确做法是先定位 PID、线程、命令、服务、日志和业务影响,再决定限流、摘流、重启或升级处理。

2. 用 grep 查进程再批量杀

bash
ps -ef | grep app | awk '{print $2}' | xargs kill -9

这类命令风险很高,可能匹配到 grep 自身、无关进程、同名脚本、其他用户任务。更稳妥的方式是使用 pgrep -a 先确认,再用更精确的用户、完整命令、父进程或 systemd unit 边界操作。

3. 忽略父进程和服务管理器

杀掉子进程后父进程可能马上重建;杀掉主进程后 systemd 可能重启整个服务;直接杀 systemd 管理的进程可能让 unit 状态变成 failed。处理前应确认生命周期归属。

4. 把内存 used 当成内存泄漏

Linux 使用缓存是正常行为。内存泄漏需要结合 RSS 趋势、available、swap、OOM、请求量、GC、cgroup 和应用指标判断。只看一次 free -h 得不出结论。

5. 长期依赖手工后台进程

nohuptmuxscreen 适合临时任务,不适合长期服务。长期服务要有日志、重启策略、权限、资源限制、健康检查和交接文档。


总结

进程管理是 Linux 系统管理从“安装配置”走向“运行治理”的关键一环。软件包安装完成后,真正决定系统稳定性的,是进程如何启动、如何消耗资源、如何退出、如何被服务管理器监督,以及异常时能否快速保留现场并做出正确处置。

关键要点回顾:

  1. 进程排障要从来源、进程树、服务 unit、cgroup 和资源消耗一起看。
  2. pstoppstreepgreplsofssjournalctlsystemctl 是日常进程管理的基本工具。
  3. 进程状态能帮助判断它在运行、睡眠、等待 I/O、暂停还是已经变成僵尸。
  4. 信号要按优雅退出优先,SIGKILL 是最后手段,不是默认操作。
  5. CPU 高、load 高、内存增长、端口占用、文件句柄耗尽分别对应不同排查路径。
  6. systemd 管理的服务应通过 unit 边界处理,避免绕过服务管理器直接操作 PID。
  7. 事故中先采集现场,再止血;止血后要形成可复盘的修复项。
  8. 日常治理应包括服务托管标准、资源限制、监控指标、启动停止流程和验证清单。

在团队实践中,进程管理还应留下可追溯记录。每一次异常终止、临时扩容、资源限制调整、守护进程重载和启动脚本变更,都应关联工单、负责人、观察窗口和回退条件。这样下一次看到相同 PID、端口或队列积压时,值班人员可以先复用已有证据,而不是重新从 top 和日志里猜测原因。对于高频问题,还应沉淀为巡检规则、告警阈值或自动化脚本,让排障经验进入日常治理,而不是只留在个人记忆里。

下一步学习: 掌握进程管理之后,建议继续学习《系统服务管理》。进程是运行实例,服务则是生产环境中可管理、可重启、可依赖、可审计的进程集合。下一篇会围绕 systemd unit、服务依赖、启动顺序、重启策略、日志、timer 和故障恢复展开。


参考资料



本文最后更新于 2026-05-29