问题背景
2025 年 6 月,某电商平台在年中促销活动期间,线上 Web 服务器(配置 24 核 CPU / 32GB 内存)于 14:30 左右触发 CPU 负载告警。监控数据显示 load average 从正常值 5~8 飙升至 42+,同时业务监控显示接口 P99 响应时间从 200ms 升高到 3800ms,用户侧开始出现超时和 502 错误。
本文以这次故障的完整排查过程为线索,展示服务器负载过高的系统性排查方法。文章以第一人称叙事展开,每步都给出实际命令输出和决策思路。
一、接到告警:第一反应
收到告警后,登录服务器执行第一个命令:
$ uptime 1445 up 15 days, 6:12, 3 users, load average: 42.35, 28.17, 15.42
load average 的三个值分别是 1 分钟 / 5 分钟 / 15 分钟的平均负载。42.35 对于 24 核的服务器意味着平均每个 CPU 核上有 1.76 个任务在竞争——CPU 已经饱和。
但负载高不等于 CPU 使用率高,需要进一步区分瓶颈类型。
二、全局扫描:定性瓶颈类型
2.1 top —— 看 CPU 时间分布
$ top top - 1450 up 15 days, 6:12, 3 users, load average: 42.35, 28.17, 15.42 Tasks: 325 total, 4 running, 321 sleeping, 0 stopped, 0 zombie %Cpu(s): 8.5 us, 5.2 sy, 0.0 ni, 32.1 id, 53.8 wa, 0.0 hi, 0.4 si, 0.0 st MiB Mem : 31967.6 total, 4284.5 free, 14234.2 used, 13448.9 buff/cache MiB Swap: 4096.0 total, 945.2 free, 3150.8 used. 13233.4 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 5678 www 20 0 0.356t 0.042t 32768 S 2.2 0.6 12:34.56 java 2345 root 20 0 175240 5300 4212 S 1.8 0.1 2:15.30 nginx
最关键的发现是 **wa(I/O 等待)= 53.8%**,说明大量 CPU 时间在等待 I/O 完成。结合 id(空闲)= 32.1%,说明 CPU 并没有满载(us + sy = 13.7%),但系统负载高的原因是 I/O 阻塞导致进程排队。
这是典型的"负载高因为 I/O 瓶颈"场景——CPU 有空闲时间,但进程在等 I/O 完成,所以 load average 中的"等待进程"数量很高。
2.2 vmstat —— 确认阻塞情况
$ vmstat 1 5 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 3 38 204800 42845 ...
b列(不可中断睡眠进程数)为 38,说明有 38 个进程阻塞在 I/O 上。r列(运行队列)为 3,说明 CPU 调度并没有明显积压。
判断:系统瓶颈在 I/O 子系统,不是 CPU。
2.3 free —— 检查内存和 swap
$ free -h -w
total used available buffers cache
Mem: 31G 14G 13G 2.2G 11G
Swap: 4.0G 3.0G 1.0G
available 内存 13G,不是内存瓶颈。但 swap 使用了 3GB,说明存在一定程度的内存压力,进程被换出到磁盘,可能加重 I/O 负担。
2.4 第一阶段小结
全局扫描结论:
瓶颈类型:I/O 瓶颈(wa=53.8%,b=38)
资源情况:CPU 有余量(id=32.1%),内存充足,swap 有少量使用
方向:下一步定位哪个进程在产生大量 I/O
三、定位 I/O 来源
3.1 iostat —— 看磁盘到底有多忙
$ iostat -xdm 1 3 Linux 5.15.0-91-generic ... 06/15/2025 _x86_64_ (24 CPU) Device r/s w/s rkB/s wkB/s r_await w_await aqu-sz %util vda 120.5 4500.3 3840.0 360000.0 2.1 112.3 248.5 98.7
关键指标解读:
w/s = 4500:每秒 4500 次写请求,非常高
w_await = 112.3ms:每次写请求平均等待 112ms,远高于 SSD 的正常范围(<2ms)
aqu-sz = 248.5:平均队列长度 248,说明 IO 请求大量积压
**%util = 98.7%**:磁盘接近饱和
这是一块通用型 SSD 云盘(极速型 SSD 的 IOPS 上限一般在 2 万左右,但 4500 w/s 不至于打到上限)。112ms 的写延迟说明单次写入遇到了抖动——可能是磁盘层的资源争抢或日志落盘的大量小 IO。
# 查看磁盘队列深度(内核侧) $ cat /sys/block/vda/queue/nr_requests 256 # 查看调度器 $ cat /sys/block/vda/queue/scheduler [mq-deadline] none
3.2 iotop —— 找到 I/O 最多的进程
$ iotop -o Total DISK READ: 3.84 M/s | Total DISK WRITE: 360.00 M/s TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 3456 be/4 root 0.00 B/s 320.00 M/s 0.00 % 95.20 % java 5678 be/4 www 3.84 M/s 0.00 B/s 0.00 % 2.10 % nginx
Java 进程以 320MB/s 的速度在写入磁盘,几乎占了全部 IO。IO> 列显示该进程 95.2% 的时间在等待 I/O。
3.3 pidstat -d —— 按进程确认
$ pidstat -d 1 3 Linux ... 06/15/2025 _x86_64_ (24 CPU) 1422 PID kB_rd/s kB_wr/s kB_ccwr/s Command 1423 3456 0.00 320000.00 0.00 java
3.4 确认文件级写入
用lsof看 Java 进程打开了哪些文件在写:
$ ls -la /proc/3456/fd/ | grep -E'REG.*W'| sort -k7 -rn | head -5 lrwx------ 1 root root 64 Jun 15 14:33 23 -> /var/log/app/app.log lrwx------ 1 root root 64 Jun 15 14:33 24 -> /var/log/app/app.log.1 lrwx------ 1 root root 64 Jun 15 14:33 25 -> /var/log/app/error.log
关键发现:进程打开了app.log.1(轮转后的日志文件)—— 这意味着日志轮转后旧文件被重命名,但 Java 进程仍在往旧文件中写入。
# 检查这些 fd 是否已被删除 $ ls -la /proc/3456/fd/23 ... /var/log/app/app.log (deleted) $ ls -la /proc/3456/fd/24 ... /var/log/app/app.log.1
确认第 23 号 fd 对应的文件已被删除(logrotatecreate方式导致),而第 24 号 fd 对应的app.log.1文件仍在写入。
四、根因确认
4.1 排查发现
完整排查后,根因链路如下:
应用日志配置为 DEBUG 级别:促销活动期间开发团队在线开启了 debug 日志,日志写入量从平时的 20MB/min 暴增到 20GB/min。
logrotate 使用 create 而非 copytruncate:默认的 logrotate 配置是create,轮转时执行mv app.log app.log.1+create new app.log。Java 进程持有的旧文件句柄仍然指向app.log.1(即被重命名后的文件),所以日志继续向app.log.1写入。
写放大效应:日志写入量激增 + 双文件写入(app.log和app.log.1)→ 磁盘写 IO 打满 → 所有需要磁盘 I/O 的进程排队(包括数据库持久化、session 持久化)→ load average 飙升 → 业务响应变慢。
# 确认日志文件大小
$ ls -lh /var/log/app/
-rw-r--r-- 1 root root 5.2G Jun 15 14:34 app.log
-rw-r--r-- 1 root root 4.8G Jun 15 14:34 app.log.1
# 确认 logrotate 配置
$ cat /etc/logrotate.d/app
/var/log/app/*.log{
daily
rotate 7
create # <- 问题在这里,应该用 copytruncate
compress
delaycompress
missingok
notifempty
}
4.2 立即恢复操作
第一步:让 Java 进程释放旧文件句柄,释放 deleted 文件的磁盘空间
# 查找 Java 进程 PID $ ps aux | grep java # 发送 USR1 信号让 log4j2 重新加载配置(大多数日志框架支持) $kill-USR1 # 验证空间是否释放 $ df -h /
如果kill -USR1无效,可以用lsof确认哪个 fd 是 deleted 文件,然后清空:
# 找到 deleted 文件的 fd 编号 $ sudo lsof -p | grep'(deleted)' $ : > /proc//fd/
第二步:关闭 DEBUG 日志
修改 log4j2.xml 或 logback.xml,将日志级别恢复为 WARN 或 INFO,然后重新加载配置。
第三步:确认 IO 恢复正常
$ iostat -xdm 1 3 $ uptime
15 分钟后负载恢复到正常水平。
4.3 根本解决方案
修改 logrotate 配置,使用copytruncate:
/var/log/app/*.log{
daily
rotate 30
copytruncate
compress
delaycompress
missingok
notifempty
}
配置异步日志写入(log4j2 AsyncAppender / logback AsyncAppender),避免日志 I/O 阻塞业务线程。
将 /var/log 独立分区,避免日志写满根分区。
日志级别变更流程化:生产环境日志级别变更需审批,变更完成后自动恢复。
五、附加生产故障案例
上述案例是 I/O 阻塞型负载过高的典型场景。以下是另外三种在线上环境中反复出现的负载过高案例,供对比参考。
案例 A:数据库双 1 配置拖死磁盘
场景:某 MySQL 实例在业务高峰时 load 飙高到 60+,wa 持续在 70~80%。
$ iostat -xdm 1 sda w/s=12000 w_await=92ms %util=99.5%
根因:innodb_flush_log_at_trx_commit=1(每次事务提交都刷盘)+sync_binlog=1(每次提交同步 binlog),在高并发事务下,磁盘 fsync 成为瓶颈。
-- 临时降低持久化级别(风险:丢失 1 秒内的事务) SET GLOBAL innodb_flush_log_at_trx_commit=2; SET GLOBAL sync_binlog=1000;
长期方案:
升级更高 IOPS 的云盘(如 ESSD PL3)
开启组提交(group commit),MySQL 5.7+ 默认支持
考虑 semi-sync 复制代替强同步
案例 B:TIME_WAIT 堆积导致连接失败
场景:高并发短连接服务,load 上升到 9.2(16 核),业务开始报 Connection refused。
$ ss -s Total: 65200 (kernel 65321) TCP: 62134 (estab 12, closed 62000, orphaned 8, synrecv 0, timewait 62000) $ netstat -s | grep"listen" 18432timesthe listen queue of a socket overflowed
根因:短连接场景下 TIME_WAIT 堆积到 62000,占满了系统连接表,新的连接无法建立。
# 立即缓解 $ sysctl -w net.ipv4.tcp_tw_reuse=1 $ sysctl -w net.ipv4.tcp_fin_timeout=15
长期方案:客户端改用长连接池或连接复用。
案例 C:apt-check 后台进程 IO 打满(小内存服务器)
场景:低配云服务器(2 核 4GB)运行中突然 load 飙高到 15+,wa > 90%。
$ iotop -o TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 1234 be/4 root 84.00 M/s 0.00 B/s 0.00 % 98.5 % apt-check
根因:Ubuntu 的unattended-upgrades自动更新检查服务触发,导致大量磁盘读操作。小内存服务器上磁盘 I/O 更容易成为瓶颈。
# 临时停止 $ systemctl stop apt-daily.timer # 永久关闭(如果不需要自动更新) $ systemctldisableapt-daily.timer $ systemctldisableapt-daily-upgrade.timer
六、Netflix 60 秒法则在实战中的应用
本次排查过程中使用的正是 Netflix 性能工程团队总结的"60 秒法则"——登录服务器后的前 60 秒内执行一组标准命令,快速建立整体认知。以下是针对负载过高场景的命令序列:
# 第 1~5 秒 uptime # load average dmesg -T | tail -10 # kernel errors # 第 6~15 秒 vmstat 1 3 # procs/memory/io/system/cpu mpstat -P ALL 1 3 # per-CPU breakdown # 第 16~25 秒 pidstat -u 1 3 # per-process CPU pidstat -d 1 3 # per-process IO # 第 26~35 秒 iostat -xzm 1 3 # disk IOPS & latency free -h -w # memory pressure # 第 36~45 秒 sar -n DEV 1 3 # network throughput sar -n TCP,ETCP 1 3 # TCP retransmits, listen drops # 第 46~60 秒 top -bn1 # snapshot of top processes ss -s # connection summary
这套命令序列能在 1 分钟内完成系统的全面体检,定位出瓶颈是 CPU、内存、IO 还是网络。
七、生产环境排查注意事项
不要在负载高时盲目重启——重启清除现场数据,根因可能永远丢失。除非系统完全无响应,否则优先排查而非重启。
先采集现场数据再操作:在执行 kill / rm / restart 之前,先把关键指标保存下来:
$ mkdir -p /tmp/debug_$(date +%s) $ uptime > /tmp/debug_*/uptime $ top -bn1 > /tmp/debug_*/top $ vmstat 1 5 > /tmp/debug_*/vmstat $ iostat -xdm 1 5 > /tmp/debug_*/iostat $ ss -s > /tmp/debug_*/ss
判断依据要有数据支撑:不要说"我觉得是 IO 问题",而是"wa=53.8%、b=38、w_await=112ms,所以 IO 是瓶颈"。
灰度执行操作:如果有多个节点,先在一个节点上验证恢复方案,确认有效再推全量。
回滚方案要提前准备:修改配置前备份(cp /etc/logrotate.d/app /etc/logrotate.d/app.$(date +%F)),确保改错了能还原。
变更记录要完整:谁在什么时间执行了什么操作,影响范围是什么,要记录到故障复盘报告中。
八、总结
服务器负载过高的排查本质是找到瓶颈资源的过程。负载高 ≠ CPU 高,负载高可能来自:
I/O 瓶颈(最常见):wa 高、b 列高、iostat 显示磁盘饱和
CPU 瓶颈:us 高、r 列高、各核负载均衡
内存瓶颈:available 低、swap 活跃
网络瓶颈:重传率高、listen drops
排查流程可以固化为一个标准 SOP:
告警触发 → uptime 确认 → top 看 CPU 分布 → vmstat 看调度和阻塞 → iostat/iotop 定位 IO → pidstat 确认进程 → lsof/straace 深挖根因 → 制定恢复方案 → 灰度执行 → 验证效果 → 根因整改
本次案例的核心教训是:日志轮转配置不当 + 日志级别失控是最容易被忽视的生产隐患。建议将 lsof 检查 deleted 文件作为新服务器上线检查清单中的一项,同时规范日志级别变更流程。
-
负载
+关注
关注
2文章
677浏览量
36729 -
cpu
+关注
关注
68文章
11343浏览量
226043 -
服务器
+关注
关注
14文章
10386浏览量
91785
原文标题:一次线上服务器负载过高的完整排查过程
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
linux服务器和windows服务器
参数模块和属性约简的应用服务器优化方法
基于Java移动代理的Web服务器负载监控系统
Web服务器的网络负载均衡
Linux服务器常见的网络故障排查方法
服务器入侵现象、排查和处理步骤
服务器负载过高的系统性排查方法
评论