上一篇 下一篇 分享链接 返回 返回顶部

服务器CPU持续100%时,如何从进程溯源到内核参数逐层排查?

发布人:Minchunlin 发布时间:2026-10-04 20:20 阅读量:2

看到服务器 CPU 持续 100% 时,不要先把问题归结为“CPU 不够”或立即修改内核参数。需要先确认 100% 是单个核心被打满、整机用户态计算繁忙、内核态调用过多,还是大量线程处于 I/O 等待;随后再把高占用映射到具体进程、线程、父子关系、服务和容器,最后才根据内存、磁盘、连接队列等证据判断是否需要调整内核参数。

引言配图

一条相对稳妥的排查顺序是:先保存现场和确认负载范围,再定位进程与线程;之后分辨 CPU、内存、磁盘 I/O、连接数和进程状态之间的关联;确认根因后采取低风险修复,最后在同一组指标上复测。只有当现象明确对应到交换、写回、监听队列、连接跟踪或文件描述符等内核资源时,才进行单项参数调整。

引言配图

先固定现场,确认“100%”到底代表什么

以下命令适用于常见 Linux 服务器。top、ps、vmstat、ss通常由系统自带工具提供,mpstat、pidstat、iostat一般来自 sysstat。生产故障期间如果工具缺失,应先使用已有命令采样,不要为了安装工具而改变系统状态。

1. 记录主机、负载和 CPU 拆分

date
uptime
nproc
cat /proc/loadavg
top -b -n 1 -H -o %CPU | head -n 35

这里需要同时看三个概念:

  • nproc 是可见 CPU 逻辑核数。
  • top 中单线程接近 100%,通常表示占满一个逻辑核;在部分显示方式下,多线程进程可能出现 400%、800% 等数值。
  • load average 表示处于可运行队列或不可中断等待状态的任务数量,并不等于 CPU 使用率。CPU 使用率不高但负载很高,常见原因是磁盘 I/O、NFS 等待或大量 D 状态进程。

继续使用 mpstat 和 vmstat 观察 5 个采样周期:

mpstat -P ALL 1 5
vmstat 1 5

重点关注:

指标含义排查方向
%usr用户态代码消耗的 CPU应用计算、死循环、查询处理、压缩、垃圾回收
%sys内核态消耗的 CPU系统调用、网络栈、文件系统、上下文切换
%iowaitCPU 等待 I/O 的时间比例磁盘、存储或文件系统响应慢
%steal虚拟化环境中被宿主机拿走的 CPU 时间宿主机争用或虚拟 CPU 调度问题
%soft软中断占用网络包、块设备完成事件或软中断压力
r可运行任务数与 CPU 核数比较,判断运行队列是否持续堆积
b不可中断睡眠任务数常见于磁盘或其他内核 I/O 等待
si/so换入、换出速率判断是否发生交换抖动
waI/O 等待比例配合 iostat 判断存储是否成为瓶颈
cs上下文切换次数线程过多、锁竞争或频繁唤醒的线索

例如,4 核服务器上一个进程稳定占用约 390% CPU,同时 %usr 很高,通常是进程内部多个线程正在计算;如果整机 %iowait 很高而进程 CPU 并不高,则不应继续围绕 CPU 频率或调度参数排查。

2. 保存高占用进程和线程快照

ps -eLo pid,ppid,tid,stat,psr,pcpu,pmem,etime,comm,args \
  --sort=-pcpu | head -n 35

这个输出比只看进程汇总更重要,因为一个进程的总 CPU 可能不高,但其中一个线程已经持续占满单个核心。需要记录:

  • PID:进程编号;
  • PPID:父进程编号;
  • TID:线程编号;
  • STAT:进程或线程状态;
  • PSR:当前运行或最近运行的 CPU;
  • PCPU:CPU 占用;
  • PMEM:物理内存占用;
  • ETIME:进程已经运行的时间;
  • ARGS:启动参数和工作对象。

如果告警平台显示的是整机 CPU 100%,但 top 中只有一个核心满载,先检查 CPU 核数和监控口径;如果所有核心都接近满载,再进入进程溯源。

从进程溯源到具体线程

1. 确认进程属于哪个服务

将实际 PID 放入变量后执行以下只读命令:

pid=1234

ps -p "$pid" -o pid,ppid,stat,psr,pcpu,pmem,etime,lstart,comm,args
pstree -aps "$pid"
readlink -f "/proc/$pid/exe"
tr '\0' ' ' < "/proc/$pid/cmdline"
echo
cat "/proc/$pid/cgroup"

结果通常可以回答几个关键问题:

  • 进程是由 systemd、脚本、任务调度器还是容器启动的;
  • 当前执行文件和启动参数是否符合预期;
  • 是否存在多个同名实例;
  • 进程是否属于某个 cgroup,是否受到 CPU 或内存配额限制;
  • 父进程是否已经退出,导致服务处于异常托管状态。

如果确认它是 systemd 服务,不要直接通过 PID 强制结束,先查看服务状态:

systemctl status your-service --no-pager
systemctl show your-service \
  -p MainPID -p ControlGroup -p CPUQuotaPerSecUSec -p MemoryMax -p LimitNOFILE

your-service 只是占位名称,需要替换为真实服务名。若服务不是由 systemd 管理,systemctl 返回“找不到单元”并不代表进程异常,只说明启动方式不同。

2. 判断进程状态和线程是否持续繁忙

grep -E '^(State|Threads|VmRSS|VmSize|voluntary_ctxt_switches|nonvoluntary_ctxt_switches):' \
  "/proc/$pid/status"

pidstat -u -r -d -w -t -p "$pid" 1 10

常见状态含义如下:

  • R:正在运行或处于可运行队列,配合高 %usr 时通常是计算型繁忙;
  • S:可中断睡眠,短时间出现属于正常现象;
  • D:不可中断睡眠,多见于磁盘、块设备或文件系统等待;
  • Z:僵尸进程,只保留退出状态,通常不会直接消耗 CPU,但数量持续增加说明父进程没有正确回收子进程;
  • T:被停止或正在调试。

pidstat 每秒采样 10 次,可以避免把一次性的瞬时峰值误判为持续故障。需要观察的是:

  • 某个线程 %CPU 是否连续多个周期偏高;
  • kB_rd/s、kB_wr/s 是否与 CPU 峰值同步;
  • cswch/s 和 nvcswch/s 是否异常升高;
  • 线程是否从 R 转为 D,说明瓶颈可能转向 I/O。

若高占用的是一个明确的线程,可以进一步查看:

top -H -p "$pid"

不要因为看到单个高 CPU 线程就立即 kill -9。先记录 PID、TID、启动时间、命令行和服务归属,否则重启后现场消失,无法判断问题是否会复现。

3. 用户态 CPU 高:继续定位代码路径

当 mpstat 中 %usr 占主导,并且进程或线程长期处于 R 状态时,常见方向包括:

  • 请求量突然上升;
  • 正则、排序、压缩、加密或序列化消耗过大;
  • 查询结果集过大或应用层循环异常;
  • 垃圾回收、编译、索引构建等后台任务;
  • 某个错误分支形成忙等或死循环。

可以先将进程 CPU 变化与应用日志、请求量、任务队列和错误率对齐:

journalctl -u your-service --since "-10 min" --no-pager

如果服务日志不在 journald 中,应使用该服务实际的日志路径。日志中若出现同一请求、任务或错误反复出现,优先在应用层限流、暂停异常任务或修复代码路径,而不是先调整内核调度参数。

需要更细粒度信息时,可以使用采样工具,但应先评估生产影响:

perf top -p "$pid"

或在允许短时采样的维护窗口中:

perf record -F 99 -p "$pid" -g -- sleep 30

perf 需要相应权限,部分系统还会受到 kernel.perf_event_paranoid 限制。采样可能产生额外开销,不能在已经严重抖动的主机上无限制运行。strace 更适合确认系统调用行为,但跟踪会放大开销:

timeout 10 strace -tt -T -p "$pid" -o "/tmp/strace-$pid.log"

只建议短时间、针对单个进程使用。若看到大量 futex,可能是线程锁竞争;大量 poll、epoll_wait 通常说明线程在等待事件,不能单凭系统调用名称认定它是 CPU 根因;大量 read、write、fsync 则应转向磁盘和文件系统排查。

4. 内核态或软中断高:不要只盯着用户进程

如果 %sys 或 %soft 明显偏高,且进程 CPU 不能解释整机消耗,检查上下文切换、软中断和内核线程:

cat /proc/interrupts | head -n 25
cat /proc/softirqs
ps -eLo pid,tid,stat,psr,pcpu,comm,args --sort=-pcpu | head -n 35

如果高占用对象是 ksoftirqd 等内核线程,可能与大量连接事件、网络包处理、块设备完成事件或中断集中有关。此时需要结合连接数量、网卡或磁盘活动、系统调用频率判断,不能直接通过提高某个网络队列参数解决。

如果大量线程的 nvcswch/s 很高,可能是线程频繁被抢占;如果 cswch/s 很高,可能是线程频繁睡眠和唤醒。二者都只是线索,最终仍要结合应用线程模型和 I/O 指标。

将 CPU 告警与内存、磁盘和连接数交叉验证

1. 内存压力:区分缓存正常和真正的内存不足

free -h
vmstat 1 5
swapon --show
cat /proc/pressure/memory
ps -eo pid,ppid,stat,pmem,rss,vsz,etime,comm,args \
  --sort=-rss | head -n 25

free 中的 buff/cache 不等于“已经泄漏的内存”。Linux 会使用空闲内存作为文件缓存,判断内存压力时应重点看:

  • available 是否持续下降;
  • vmstat 中 si、so 是否持续非零;
  • /proc/pressure/memory 的 some、full 是否持续升高;
  • 进程 RSS 是否持续增长;
  • 是否出现 OOM 记录。
journalctl -k -b --no-pager | grep -Ei 'oom|out of memory|killed process'

如果同时出现高 CPU、交换读写和大量回收,CPU 可能消耗在内存回收或压缩上,而不是业务计算。此时应先处理占用异常的进程、容器内存上限或任务并发,不能把降低 vm.swappiness 当作内存扩容。

2. 磁盘 I/O:确认 CPU 是“计算”还是“等待”

iostat -xz 1 5
pidstat -d -p "$pid" 1 10
df -h
df -ih

iostat 中可重点查看:

  • %util:设备忙碌比例;
  • await:I/O 请求平均等待时间;
  • aqu-sz:设备队列长度;
  • r/s、w/s:读写请求速率;
  • 读写吞吐量是否与业务高峰同步。

单看 %util 不能完全判断设备已经饱和。不同设备的并发能力不同,应同时观察 await 和队列长度。例如 %iowait 很高、进程大量处于 D 状态、await 和 aqu-sz 同时上升,这条链路比“CPU 使用率下降了”更能证明存储是瓶颈。

常见修复方向包括:

  • 找到产生大量读写的进程或任务;
  • 降低批处理并发,错开备份、索引和日志压缩;
  • 检查是否存在文件系统空间或 inode 耗尽;
  • 对异常频繁的 fsync、小块随机写进行应用侧优化;
  • 确认存储延迟恢复后,再观察 CPU 和负载是否同步下降。

不要通过修改 vm.dirty_ratio 掩盖持续的写入过量。写回参数只能改变脏页达到阈值后的行为,不能消除业务实际产生的 I/O。

3. 连接数和监听队列:区分连接多与处理不过来

ss -s
ss -lnt
ss -tan state established | wc -l
ss -tan state syn-recv | wc -l
ss -tan state time-wait | wc -l

ss -lnt 中监听 socket 的 Recv-Q 和 Send-Q 有特殊含义:

  • Recv-Q 通常表示当前排队等待应用接受的连接数;
  • Send-Q 通常表示监听队列上限;
  • 已建立连接的 Recv-Q 较大,可能是应用读取不及时;
  • 已建立连接的 Send-Q 较大,可能是发送受阻或对端读取慢。

在确认单个端口队列持续接近上限后,再把连接映射到进程:

ss -tanp | head -n 40

该命令在连接很多时可能输出量较大,部分信息需要 root 权限。若 SYN-RECV 很多,关注监听队列和连接建立压力;若 ESTAB 很多,关注应用线程、文件描述符和请求处理能力;若 TIME-WAIT 很多,不能直接认定是故障,还要结合短连接比例、端口范围和业务模式判断。

同时检查进程和服务的文件描述符:

ls "/proc/$pid/fd" 2>/dev/null | wc -l
cat "/proc/$pid/limits" | grep -i 'open files'
systemctl show your-service -p LimitNOFILE

如果接近进程的 Max open files,调高 net.core.somaxconn并不能解决文件描述符耗尽。需要先确认服务的 LimitNOFILE、应用连接池和关闭连接逻辑,修改 systemd 限制时还要重启服务才能让新限制生效。

进程状态与指标组合的判断方法

可以用下面的组合快速缩小范围:

现象组合更可能的方向下一步
%usr 高、线程处于 R、磁盘指标正常应用计算或忙等按线程采样,关联请求和任务日志
%sys 高、上下文切换高系统调用、锁竞争或频繁唤醒查看 pidstat -w,必要时短时使用 perf 或 strace
%iowait 高、D 状态多、磁盘队列升高存储或文件系统等待使用 iostat、pidstat -d 定位读写来源
%steal 高、业务进程并不高虚拟 CPU 被宿主机争用记录时间段和实例负载,避免修改应用参数掩盖问题
内存 PSI 高、si/so 持续变化内存不足或交换抖动找 RSS 增长进程,检查容器和服务内存限制
SYN-RECV 多、监听 Recv-Q 接近上限建连压力或监听队列不足同时检查应用 backlog、somaxconn和连接来源
ESTAB 多、应用线程繁忙、文件描述符接近上限连接处理能力或句柄不足调查连接池、请求耗时、LimitNOFILE
Z 状态持续增加父进程回收子进程异常追踪父进程和任务执行逻辑,不要反复清理僵尸本身

这张表只能用于建立排查方向,不能替代连续采样。例如 TIME-WAIT 数量高本身不等于内核参数错误,load average 高也不等于 CPU 一定不足。

只有证据明确时,才进入内核参数层

1. 先读取当前值并保存基线

不同发行版和内核版本支持的参数可能不同,先查询实际存在的项目:

sysctl vm.swappiness \
       vm.dirty_background_ratio \
       vm.dirty_ratio \
       vm.dirty_background_bytes \
       vm.dirty_bytes \
       net.core.somaxconn \
       net.ipv4.tcp_max_syn_backlog \
       fs.file-max 2>/dev/null

cat /proc/sys/net/ipv4/ip_local_port_range

保存基线时至少记录时间、内核版本和相关监控数据:

uname -a
date
sysctl -a 2>/dev/null | grep -E \
'^(vm\.swappiness|vm\.dirty_|net\.core\.somaxconn|net\.ipv4\.tcp_max_syn_backlog|fs\.file-max)' \
> /var/tmp/sysctl-baseline-$(date +%F-%H%M%S).txt

基线文件应保留在受控目录中,并与变更单或故障记录关联。只读查询不会改变参数,但 sysctl -a 在少数系统上可能受到权限或安全策略限制。

2. 交换相关参数:只针对已确认的交换压力

如果 vmstat 的 si/so 持续变化、内存 PSI 升高,才有理由评估 vm.swappiness。它影响内核回收匿名页和文件缓存的倾向,不是“CPU 100%开关”。

例如当前值为 60,经过内存使用模式评估后,临时测试一个较低值:

sysctl -w vm.swappiness=10
sysctl vm.swappiness

这里的 10 只是示例,不代表所有业务都应使用该值。数据库、缓存服务、容器和混合型工作负载的最佳取值可能不同。测试后如果交换没有改善、可用内存变少或文件缓存命中率下降,应立即恢复变更前的值:

sysctl -w vm.swappiness=60

实际回滚值必须使用现场保存的旧值,不能照抄示例。更重要的是,若主机已经频繁 OOM,仅调低 swappiness 可能让匿名内存更快挤压文件缓存,甚至提前触发 OOM。

3. 写回参数:先证实脏页和写回是瓶颈

vm.dirty_background_ratio、vm.dirty_ratio以及对应的 *_bytes控制脏页达到一定规模后的写回行为。比例参数和字节参数存在互斥关系,不能在不了解现有配置的情况下同时设置。

如果 iostat显示持续写入、队列堆积,且内存中脏页增长明显,可以在维护窗口中测试单项变化。不要把降低或提高阈值直接当成修复方案,因为它可能造成:

  • 更早开始写回,增加后台 I/O;
  • 延迟一段时间后集中写回,形成突发延迟;
  • 在大内存服务器上产生远大于预期的脏页规模。

这类参数应优先通过业务写入节奏、批处理并发和日志策略解决。只有在确认写回节奏本身导致延迟尖峰时,才根据内核版本和存储能力调整,并在 await、队列长度、I/O 吞吐和业务延迟上同时验证。

4. 监听队列参数:必须与应用 backlog 一起判断

下面两个参数经常被混用:

  • net.core.somaxconn:已建立连接等待应用 accept 的监听队列上限;
  • net.ipv4.tcp_max_syn_backlog:尚未完成握手的连接请求队列上限。

如果监听队列持续接近上限,可以临时测试更高值:

old_somaxconn=$(sysctl -n net.core.somaxconn)
old_syn_backlog=$(sysctl -n net.ipv4.tcp_max_syn_backlog)

sysctl -w net.core.somaxconn=4096
sysctl -w net.ipv4.tcp_max_syn_backlog=4096

sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog

这两个参数只是在内核层提供上限,应用实际调用 listen() 时设置的 backlog 仍可能更小。若应用没有及时 accept,单纯增大队列只能延迟拒绝,不能提升请求处理能力;队列过大还可能增加内存占用和排队延迟。

回滚时使用变更前保存的值:

sysctl -w net.core.somaxconn="$old_somaxconn"
sysctl -w net.ipv4.tcp_max_syn_backlog="$old_syn_backlog"

如果 shell 会话已经结束,应从基线文件或变更记录中取回原值。不要因为看到 TIME-WAIT 较多就直接调大这两个参数,它们并不专门解决已关闭连接堆积。

5. 文件描述符和连接跟踪:先确认是否真的耗尽

全局文件句柄和进程文件句柄是两个层次。可先查看:

sysctl fs.file-max
cat /proc/sys/fs/file-nr
cat /proc/sys/fs/nr_open

如果使用连接跟踪功能,还要确认相关参数是否存在:

sysctl net.netfilter.nf_conntrack_max 2>/dev/null
cat /proc/sys/net/netfilter/nf_conntrack_count 2>/dev/null

只有当前者接近上限、后者持续逼近最大值时,才有必要评估对应容量。盲目提高上限会消耗更多内核内存,不能代替清理异常连接、修复连接泄漏或降低无效并发。

修复动作要按风险分级

确认根因后,优先选择可逆、影响范围小的动作:

  1. 暂停异常批处理、重试风暴或非必要后台任务。
  2. 对已经确认的异常请求源或任务队列实施应用层限流。
  3. 通过服务自身的优雅重载或重启释放异常线程、连接和缓存。
  4. 在维护窗口中调整一个内核参数,并保留旧值。
  5. 只有在明确需要时,才将临时参数写入持久化配置。

重启服务会影响连接和请求,必须确认服务拓扑、健康检查和回滚方式。相比直接发送 SIGKILL,优先使用服务管理器提供的优雅操作;如果必须处理单个失控进程,先发送 TERM并观察退出结果,KILL只作为经过授权的最后手段。不要杀掉名称相似但归属不明的系统进程。

持久化内核参数时,应先备份现有配置,避免覆盖其他运维项。例如:

sudo cp -a /etc/sysctl.d/99-highload-tuning.conf \
  /var/backups/99-highload-tuning.conf.$(date +%F-%H%M%S) 2>/dev/null || true

随后只写入已经验证过的单项配置,不要一次加入多个未经验证的参数。持久化配置修改后,应在变更窗口中重新加载并检查输出:

sudo sysctl --system
sysctl vm.swappiness net.core.somaxconn

sysctl --system可能读取多个配置目录中的文件,执行前要确认不会同时加载其他未审核变更。回滚时恢复备份文件,再重新加载;如果该文件是本次新建的,删除或移出配置目录前同样要保留备份,并确认没有其他配置文件重复设置相同参数。

修复后的验证不能只看 CPU 曲线

修复完成后,使用与故障期间相同的指标复测至少一个业务高峰周期,或连续观察 5~15 分钟。建议同时记录:

mpstat -P ALL 1 5
vmstat 1 5
iostat -xz 1 5
ss -s
ps -eLo pid,ppid,tid,stat,psr,pcpu,pmem,etime,comm,args \
  --sort=-pcpu | head -n 25

验证应覆盖四个层面:

  • CPU:用户态、内核态、I/O 等待、软中断和 steal 是否回到稳定范围;
  • 进程:原高占用 PID 或线程是否消失,是否出现新的进程替代它;
  • 资源:内存 PSI、交换、磁盘 await、I/O 队列、连接队列和文件描述符是否改善;
  • 业务:请求延迟、错误率、超时、任务积压和服务可用性是否恢复。

如果 CPU 从 100%降到 40%,但磁盘延迟、连接排队或业务响应时间仍然很高,不能算排查完成;如果重启后暂时恢复,也要继续观察进程 CPU、RSS、线程数和连接数是否再次逐步增长。能够复现的故障,应保留修复前后的采样结果和参数差异,避免下一次告警时再次从“CPU 100%”这一表象开始猜测。

目录结构
全文