Linux 系统化排障与性能监控
1. Linux 性能观测工具链
本节从整机快速概览入手,先发现资源异常,再逐步定位进程、线程、系统调用和业务代码。文中的 PID、网卡名、设备名和服务地址都是实验环境示例,使用时应替换为目标环境实际值。
1.1 工具链与排障方法
Linux Performance Observability Tools 用于监控和分析 Linux 系统性能,帮助识别瓶颈并验证优化效果。

参考资料:Linux Performance。
系统状态查看和管理工具包括:
pstree, ps, pidof, pgrep, top, htop, glances, pmap, vmstat, dstat,
kill, pkill, jobs, bg, fg, nohup
其中 jobs/bg/fg 用于 Shell 作业管理,kill/pkill 用于发送信号。strace 可以捕获程序执行过程中的系统调用,例如:
# 将 1234 替换为目标 Nginx worker 的实际 PID,观察后 Ctrl+C 结束
strace -f -tt -T -p 1234 -o /tmp/nginx-strace.log
实验环境准备
课件配套 scripts/ 目录提供若依安装和 Actuator 改造脚本:
# 安装 RuoYi-Vue 环境
/bin/bash onekey_install_ruoyi_ubuntu24.sh
# 开放 Actuator 功能
/bin/bash update_actuator_ruoyi_ubuntu24.sh
这些命令依赖课件配套脚本,不能只复制命令直接复现;无配套文件时可使用第 13 节的独立实验。
USE:检查系统资源
USE(Utilization、Saturation、Errors)由 Brendan Gregg 提出,面向 CPU、内存、磁盘、网卡等资源。逐类资源检查以下三个维度,可以减少遗漏:
| 维度 | 含义 | 观测方向 |
|---|---|---|
| Utilization,利用率 | 资源被使用或繁忙的程度 | CPU 忙碌比例、设备忙碌时间等 |
| Saturation,饱和度 | 资源不足造成的排队程度 | 运行队列、I/O 队列、内存回收与交换 |
| Errors,错误 | 资源发生的异常事件 | I/O error、网卡错误、丢包等 |
典型排查线索:
- CPU 利用率持续高,同时运行队列
r持续超过可用 CPU 数,提示 CPU 饱和。 - 磁盘
await、队列长度明显升高,并伴随设备繁忙,提示 I/O 压力;不能对所有 SSD 仅凭%util=100%判断已到性能上限。 vmstat的so持续不为 0,提示内存正在换出,需要结合可用内存和回收压力分析。dmesg中的 I/O 错误、网卡报错等用于检查资源错误。
RED:检查业务请求
RED(Rate、Errors、Duration)面向 Web 服务、微服务和 API,关注用户体验,与 USE 互补。
| 维度 | 含义 | 异常线索 |
|---|---|---|
| Rate | 每秒请求数、RPS | 请求速率突增,压力变大 |
| Errors | 请求失败数或失败率 | 5xx、业务异常、依赖服务故障;4xx 需按业务语义分类 |
| Duration | 请求耗时分布 | p95/p99 显著升高,用户感知变慢 |
延迟应观察分位数及分布,不能只看平均值。RED 属于应用层指标,需要 Nginx 日志、应用埋点或监控系统提供,Linux 资源命令无法直接给出完整的业务 RED 指标。
故障排查顺序
确认影响范围与故障时间
→ 对照发布、配置、流量和依赖变更
→ 采集业务指标、系统指标与日志
→ 区分 CPU、内存、I/O、网络、资源限制和应用等待
→ 用进程、线程、调用栈或请求链路验证假设
→ 执行恢复操作
→ 在相同负载下复测
先明确哪些请求失败、哪些实例受影响、从何时开始、是否与某次变更重合。单实例异常优先比较健康实例与异常实例;所有实例同时异常时,检查共同依赖、入口流量和共享基础设施。
| 现象 | 第一轮检查 | 下一步 |
|---|---|---|
| 整机 CPU 高 | vmstat 1、mpstat -P ALL 1 | pidstat -u -t 定位线程,再看调用栈 |
| load 高但 CPU 不高 | vmstat 的 b、D 状态、PSI | 磁盘、NFS、内核栈及设备日志 |
| 内存占用增长或进程消失 | MemAvailable、RSS/PSS、内核日志 | 区分工作集增长、内核 OOM、cgroup OOM 与应用 OOM |
| 磁盘写入失败 | df -hT、df -i、findmnt | 容量、inode、配额、只读挂载、设备错误 |
| 服务无法启动 | systemctl status、服务日志 | 退出码、配置、权限、端口、资源限制 |
| 接口慢但系统资源不忙 | 请求耗时、线程栈、连接池 | 锁、下游调用、排队和超时 |
| 宿主机正常但容器慢 | cgroup CPU/内存限制、压力指标 | CPU throttling、内存回收、容器 OOM |
| 只有域名访问异常 | DNS、TCP、TLS、HTTP 分层检查 | 对比代理入口与源站,保留 Host 与 SNI |
第一轮采样
以下命令依次执行,完整采样需要数十秒。sysstat 提供 mpstat、iostat、pidstat 和 sar;先确认工具可用。
date -Is
uptime
nproc
free -h
vmstat -w 1 5
mpstat -P ALL 1 5
iostat -xz -y 1 5
pidstat -u -r -d 1 5
df -hT
df -i
ss -s
journalctl -k --since '-15 min' --no-pager -n 100
nproc 不一定反映容器的 CPU 时间配额,容器内还需检查 cgroup。命令结果应与故障时段对齐,异常已消失时改查历史记录。
重启前尽量保存进程与线程栈、相关日志、连接状态和资源限制。恢复操作一次只改变一个关键条件,记录操作及结果;若操作没有改善业务指标,应重新验证假设。紧急恢复与根因定位可以分开进行,但不能把“重启后恢复”直接当作根因。
1.2 vmstat
vmstat 用于快速观察整机运行队列、内存、交换、块 I/O、上下文切换和 CPU 状态,适合系统负载分析、故障排查和调优前后的对比。
格式和选项
vmstat [options] [delay [count]]
-a, --active 分开显示活动与非活动内存
-s, --stats 显示事件统计
-d, --disk 显示磁盘设备统计
-D, --disk-sum 显示磁盘汇总统计
-p, --partition <dev> 显示指定分区统计
-S, --unit <char> 指定内存单位:k、K、m、M
-w, --wide 宽格式输出
-t, --timestamp 显示时间
-n 表头只显示一次
输出解读
[root@ubuntu24-13 ~]# vmstat
procs ----------memory---------- ---swap-- -----io---- -system-- --------cpu--------
r b swpd free buff cache si so bi bo in cs us sy id wa st gu
1 0 0 2835168 27676 663568 0 0 10576 3563 1062 7 6 9 85 1 0 0
第一份报告中 CPU、I/O 等速率通常是开机以来的平均值,进程和内存字段是当前状态。指定间隔后,后续报告反映该采样间隔;不要把第一行当作最近一秒的数据。
| 分组 | 字段 | 含义与排查方向 |
|---|---|---|
| procs | r | 运行中及等待运行的任务数;持续超过可用 CPU 数时关注 CPU 排队 |
| procs | b | 不可中断睡眠任务数,常见于等待 I/O;需结合进程栈确认具体原因 |
| memory | swpd | 已使用的交换空间 |
| memory | free | 空闲物理内存;内存压力还需结合 MemAvailable、回收和交换判断 |
| memory | buff | 块设备相关缓冲 |
| memory | cache | 文件页缓存等可缓存内存 |
| swap | si | 从交换区读入内存的速率,swap-in |
| swap | so | 从内存写出到交换区的速率,swap-out |
| io | bi | 从块设备读入的速率 |
| io | bo | 向块设备写出的速率 |
| system | in | 每秒中断次数,包括时钟中断 |
| system | cs | 每秒上下文切换次数,过高时结合线程数、锁和业务基线分析 |
| cpu | us | 用户态 CPU 时间比例 |
| cpu | sy | 内核态 CPU 时间比例 |
| cpu | id | CPU 空闲比例 |
| cpu | wa | 统计为 I/O 等待的 CPU 时间比例;不能直接等同于磁盘利用率 |
| cpu | st | 虚拟机被宿主机占用而无法得到的 CPU 时间比例 |
| cpu | gu | 运行客户虚拟机使用的 CPU 时间比例,是否显示取决于版本 |
默认内存通常以 KiB 显示;-S M 使用 MiB,-S m 使用十进制 MB。现代 Linux procps 的 bi/bo 按 KiB/s 报告,不应按固定的 512 字节扇区数换算;-S 不改变 bi/bo 的单位。具体以本机 man vmstat 为准。
示例中 r=1、b=0,空闲内存约 2.8 GiB,无 swap 换入换出;CPU 空闲 85%,I/O 等待约 1%,没有明显 CPU 或交换压力。bi/bo 表明存在磁盘读写,但第一行不能用来判断此刻的瞬时吞吐。
基本实践
# 查看一份报告
vmstat
# 每秒采样
vmstat 1
# 每秒输出一次,共 10 次
vmstat 1 10
# 以内存 MiB 为单位显示
vmstat -S M
原实验不同采样时刻的输出:
[root@ubuntu24-13 ~]# vmstat -S M
procs -----------memory---------- ---swap-- -----io---- -system-- --------cpu--------
r b swpd free buff cache si so bi bo in cs us sy id wa st gu
1 0 0 851 108 1771 0 0 750 3699 778 8 5 3 92 0 0 0
[root@ubuntu24-13 ~]# vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- --------cpu--------
r b swpd free buff cache si so bi bo in cs us sy id wa st gu
1 0 12 874040 111308 1813820 0 0 748 3749 777 8 5 3 92 0 0 0
[root@ubuntu24-13 ~]# echo 874040 / 1024 | bc
853
两次采样的内存值可以变化,不能要求换算结果完全相同。
I/O 的输入、输出方向
这里的 i/o 以内存为参照:
si:交换磁盘 → 内存 so:内存 → 交换磁盘
bi:普通块设备 → 内存 bo:内存 → 普通块设备
实验一:读取块设备并丢弃数据
终端 A 按秒观察:
vmstat -n 1
终端 B 查看分区并读取:
[root@ubuntu24-13 ~]# df -h | grep '^/dev'
/dev/sda2 196G 10G 176G 6% /
[root@ubuntu24-13 ~]# dd if=/dev/sda2 of=/dev/null
/dev/null 会丢弃写入的数据,此实验通过用户缓冲区读取块设备,不是把整个分区内容保存为内存文件。终端 A 的采样如下:
r b swpd free buff cache si so bi bo in cs us sy id wa st gu
# 压测前
0 0 12 716040 116000 1924448 0 0 0 12 269 522 0 0 100 0 0 0
...
# dd 开始,bi 上升
1 0 12 603148 226884 1924744 0 0 110948 12 1282 936 0 6 93 1 0 0
0 1 12 257732 824856 1699484 0 0 671352 0 9039 7103 1 41 45 13 0 0
0 1 268 184252 1159184 1440964 0 8 656408 8 6666 8343 1 39 47 13 0 0
1 0 268 173424 1457956 1142692 0 0 655740 8 6648 7848 1 40 47 12 0 0
1 0 268 243740 1605904 938820 0 0 712664 0 6952 7401 1 41 47 12 0 0
1 0 268 275764 1725164 783052 0 0 764996 0 7363 6492 1 41 47 11 0 0
1 0 268 310744 1834848 642740 0 0 1114876 16 10161 764 1 53 46 1 0 0
...
观察到:
bi从 0 上升,最高为 1114876,块设备读取量显著增加。free从 716040 降至最低 173424,buff从 116000 增至 1834848,cache减少。块设备读取引起缓冲占用增长,内核也会回收其他缓存。sy约为 40%~53%,大量 CPU 时间消耗在内核 I/O 路径;id从 100% 降至约 45%~47%。- 多个样本
wa达到 11%~13%,存在 I/O 等待,但是否达到设备瓶颈还应看设备延迟和队列。
r/b 在 1 和 0 之间变化,可用以下过程理解:
获得 CPU 发起 I/O(可运行)
→ 等待 I/O 完成(可能进入不可中断睡眠)
→ I/O 完成被唤醒
→ 继续发起下一批 I/O
r/b 是采样时刻的系统状态,采样恰好落在不同阶段会得到不同数值,且也可能包含其他任务。
实验二:生成数据并写入磁盘文件
终端 A:
vmstat -n 1
终端 B:
dd if=/dev/zero of=/tmp/1.txt bs=1K count=10240000
/dev/zero 生成零字节,写入约 10 GiB 文件;/tmp 的实际挂载类型决定数据是否落到磁盘。原实验采样:
r b swpd free buff cache si so bi bo in cs us sy id wa st gu
# 压测前
1 0 268 186208 962380 1611624 0 0 89828 2819 1340 5 2 6 92 0 0 0
0 0 268 186208 962380 1611624 0 0 0 0 291 541 0 1 100 0 0 0
# dd 开始写入,bo 上升
2 0 268 356220 962440 1416204 0 0 0 548864 1555 831 4 37 60 0 0 0
1 0 268 122684 607752 2037992 0 0 0 1318916 3184 1172 6 74 20 0 0 0
2 0 268 144928 170088 2453156 0 0 0 1351680 3641 1196 6 81 13 0 0 0
3 0 268 165388 35300 2556208 0 0 0 1302528 3473 1162 6 79 15 0 0 0
1 0 268 163612 35340 2555256 0 0 4 1372160 3340 1217 7 79 14 0 0 0
3 0 268 169560 35156 2542240 0 0 4 1212956 3393 1139 6 75 19 0 0 0
3 0 268 161440 35192 2575212 0 0 0 1289068 3341 1154 7 80 14 0 0 0
1 0 268 126064 35220 2583576 0 0 4 1233844 3614 1232 7 81 12 0 0 0
# dd 结束后,后台仍有脏页回写
0 0 268 161804 35240 2531576 0 0 0 610304 1664 823 3 30 67 0 0 0
# 回写完成
0 0 268 161804 35240 2531576 0 0 0 0 270 524 0 1 99 0 0 0
bo 最高达到 1372160;sy 达到 37%~81%,CPU 在数据复制、文件系统和回写路径上消耗较多。默认缓冲写先把数据复制到 Page Cache,由后台回写线程逐步写入设备,因此进程退出后仍可能有磁盘写入。
本次采样中 b=0、wa=0,说明未在这些采样点观察到明显阻塞;这不代表缓冲写永不阻塞。脏页过多、内存回收、文件系统操作等都可能阻塞写入进程。现代内核使用 writeback 工作线程,不宜统一称为旧版 pdflush。
故障线索与案例
| 现象 | 后续检查 |
|---|---|
r 持续超过 CPU 数且 CPU 繁忙 | 定位高 CPU 进程和线程 |
si/so 持续活跃 | 检查可用内存、工作集、回收及交换压力 |
wa 持续高 | 检查磁盘/NFS 等 I/O 延迟、队列和阻塞任务 |
cs 高于业务基线 | 检查线程数、锁竞争、频繁唤醒 |
st 持续高 | 检查宿主机 CPU 争用和虚拟机资源配额 |
多服务服务器响应变慢,课件给出的示例:
# vmstat -S m
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
3 1 100 512 200 1500 0 0 5 2 100 200 30 20 45 5 0
假设机器有 2 个 CPU,r=3 提示可能排队;wa=5% 表明有一定 I/O 等待。swpd=100 MB 仅代表有交换空间在用,当前 si/so=0,不能由此直接认定正在内存不足。应继续采集多个间隔,结合进程资源、可用内存和请求延迟确认。
处理方向:检查不必要的内存占用、业务计算逻辑和高 CPU 进程;可调整非关键任务优先级、优化缓存策略。只有确认容量不足后再增加内存或升级 CPU,不宜为了降低 buff/cache 数值而直接清缓存。
top 与 vmstat 配合
两者的资源统计都来自内核接口,可以相互印证,但覆盖范围不同:vmstat 偏向整机聚合,top 同时提供整机概览和进程/线程明细。
- 用
vmstat 2看 CPU、交换、I/O 和队列是否持续异常。CPUid<10%可作为关注线索;低free或bi/bo>1000都不是通用故障阈值,需要结合缓存、硬件能力和历史基线判断。 - 在
top中按P按 CPU 排序,按M按内存排序,定位相关进程。 - 对磁盘问题进一步使用
pidstat -d、iotop或其他进程 I/O 统计;top的字段和 I/O 能力依版本而异,不保证按f就能获得实际磁盘吞吐。
1.3 iostat
iostat(I/O Statistics)属于 sysstat 工具集,用来观察块设备 I/O 和 CPU 统计。
iostat [options] [interval [count]]
-c 只显示 CPU 统计
-d 只显示设备统计
-k 以 kB 单位显示吞吐
-m 以 MB 单位显示吞吐
-h, --human 便于阅读的单位
-t 包含时间戳
-x 显示扩展设备指标
-y 有间隔时跳过开机以来的第一份报告
基本输出
[root@ubuntu24-13 ~]# iostat
Linux 6.8.0-31-generic (ubuntu24-13) 2026年09月08日 _x86_64_ (2 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
1.46 0.09 4.05 0.13 0.00 94.26
Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_wrtn kB_dscd
loop0 0.00 0.00 0.00 0.00 10 0 0
sda 490.39 60039.51 4808.76 0.00 210379633 16850000 0
不带间隔时显示开机以来的统计;带间隔时第一份报告通常仍是开机平均,后续才是间隔数据,可用 -y 跳过第一份。
CPU 字段与前文类似,%nice 是调整过 nice 值的用户态任务时间。设备字段:
| 字段 | 含义 |
|---|---|
tps | 每秒传输请求数,反映合并后的设备 I/O 请求速率 |
kB_read/s、kB_wrtn/s | 每秒读取、写入的数据量 |
kB_dscd/s | 每秒 discard/TRIM 请求对应的数据量,不是普通写入数据丢失 |
kB_read、kB_wrtn、kB_dscd | 统计期内累计读取、写入、discard 的数据量 |
命令实践
iostat # 开机累计统计
iostat 2 # 后续每 2 秒采样
iostat 2 8 # 共 8 份报告
iostat -d 1 # 只看设备
iostat -m 1 # MB 单位
iostat -x 1 # 扩展指标
iostat -x -y 1 # 跳过开机平均报告
扩展指标包括:
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz
d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util
| 字段 | 含义 |
|---|---|
r/s、w/s | 每秒读、写请求数 |
rkB/s、wkB/s | 每秒读、写数据量 |
rrqm/s、wrqm/s | 每秒合并的读、写请求数 |
%rrqm、%wrqm | 读、写请求合并比例 |
r_await、w_await | 读、写请求平均完成耗时,包含排队和设备处理,单位 ms |
rareq-sz、wareq-sz | 平均读、写请求大小,单位 kB |
d/s、dkB/s、d_await | discard 请求速率、数据量和平均耗时 |
f/s、f_await | flush 请求速率及平均耗时 |
aqu-sz | 平均 I/O 队列长度,包括在途请求 |
%util | 统计期内设备有 I/O 请求在处理的时间比例 |
MySQL I/O 瓶颈案例
数据库业务变慢,SQL 层初查后继续检查磁盘:
# iostat -x 1
avg-cpu: %user %nice %system %iowait %steal %idle
8.20 0.10 6.50 35.60 0.00 49.60
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz aqu-sz %util
sda 1230.00 49200.00 2.00 0.16 62.00 40.00 320.00 20480.00 10.00 3.03 85.00 64.00 12.50 98.20
loop0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
这些数值是课件的演示数据:%iowait=35.60%、读等待 62 ms、写等待 85 ms、平均队列 12.50、繁忙时间 98.20%,共同提示 I/O 延迟和排队值得重点排查。读 1230 IOPS、写 320 IOPS 是否高,要看设备能力;仅从请求大小不能证明一定是随机 I/O。原示例各列也不宜作为严格自洽的容量计算依据。
一般排查线索包括:持续较高的 await、较长队列、机械盘繁忙时间接近 100%,以及大量小请求。课件以 await>50 ms 举例,实际阈值取决于设备和业务延迟目标;SSD/NVMe 支持并行请求,不能只看 %util。
处理方向:
- 优化慢 SQL 和索引,提高数据库缓存命中率,减少不必要的日志写入。
- 评估刷盘批量和频率;修改数据库持久化参数前明确数据可靠性要求。
- 确认容量瓶颈后升级 SSD、拆分数据盘或分散业务 I/O。
1.4 mpstat
mpstat(Multi-Processor Statistics)属于 sysstat,用于观察全部 CPU 或单个逻辑 CPU,尤其适合检查单核繁忙、多核负载不均衡和中断开销。
mpstat [options] [interval [count]]
-P ALL 查看所有逻辑 CPU
-P 0 只查看编号为 0 的 CPU
-A 输出全部支持的统计
输出解读
[root@ubuntu24-13 ~]# mpstat
Linux 6.8.0-31-generic (ubuntu24-13) 2026年09月08日 _x86_64_ (2 CPU)
10时56分35秒 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
10时56分35秒 all 1.03 0.07 2.82 0.09 0.00 0.07 0.00 0.00 0.00 95.93
不带间隔显示开机以来统计;使用 mpstat -P ALL 1 查看逐秒采样。
CPU=all表示全部逻辑 CPU 的汇总,0/1是 CPU 编号。%irq是硬中断处理时间,%soft是软中断处理时间。%guest/%gnice是运行客户虚拟机的时间。- 其余 CPU 字段与前文相同。
硬中断由网卡、磁盘等设备通知 CPU 处理事件。软中断是内核延后处理部分工作的机制,例如网络收发;它可以在中断返回路径或内核线程中执行。普通软中断执行期间仍可被硬中断打断,具体行为也与内核配置有关。
命令实践
mpstat -P ALL # 全部 CPU 的累计统计
mpstat -P ALL 1 # 每秒采样
mpstat -P ALL 2 5 # 每 2 秒采样,共 5 次
mpstat -P 0 1 # 只观察 CPU0
单核瓶颈案例
业务卡顿,但整机 CPU 看起来并未用满:
# mpstat -P ALL 1
09:30:00 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
09:30:00 all 45.00 0.00 5.00 0.20 0.00 0.50 0.00 0.00 0.00 49.30
09:30:00 0 92.00 0.00 7.00 0.30 0.00 0.60 0.00 0.00 0.00 0.10
09:30:00 1 2.00 0.00 3.00 0.10 0.00 0.40 0.00 0.00 0.00 94.50
CPU0 空闲只有 0.1%,CPU1 空闲 94.5%,整机空闲仍约 49.3%。这提示单核热点或负载分配不均衡,可能来自单个繁忙线程、CPU 亲和性限制、中断分配等,不能仅凭这份输出断定程序是单线程。
处理方向:检查线程级 CPU 和调用栈;业务允许时并行化;检查 taskset 亲和性和中断分配。绑定 CPU 只改变调度范围,不能让单线程自动并行。确定无法并行且单核算力不足时,再评估更高单核性能。
常见线索还包括:%soft 高时检查网络小包和软中断分布;%irq 高时检查硬件中断;全部 CPU 空闲长期很低时检查整机计算压力;%steal 高时检查宿主机争用。
1.5 sar
sar(System Activity Reporter)属于 sysstat,支持采集、保存和回放 CPU、内存、磁盘、网络、队列、中断等历史数据,是事后排查工具。历史记录必须事先启用采集,工具无法补回未记录的数据。
sar [options] [interval [count]]
-u CPU 统计
-r 物理内存统计
-S 交换空间使用统计
-W 交换活动速率
-b 块 I/O 汇总
-d 分设备 I/O
-n DEV 网卡流量
-q 队列和负载
-f file 读取历史二进制数据文件
-o file 保存采样到二进制文件
历史输出
[root@ubuntu24-13 ~]# sar
Linux 6.8.0-31-generic (ubuntu24-13) 2026年09月08日 _x86_64_ (2 CPU)
09时33分23秒 LINUX RESTART (2 CPU)
09时40分14秒 CPU %user %nice %system %iowait %steal %idle
09时50分13秒 all 5.91 0.49 3.70 0.15 0.00 89.75
10时00分07秒 all 1.85 0.00 0.57 0.01 0.00 97.58
10时10分00秒 all 0.29 0.00 17.10 0.51 0.00 82.10
10时20分16秒 all 0.11 0.00 1.43 0.00 0.00 98.46
10时30分18秒 all 0.02 0.00 0.16 0.00 0.00 99.82
10时40分06秒 all 0.02 0.00 0.17 0.00 0.00 99.82
10时50分18秒 all 0.01 0.00 0.16 0.00 0.00 99.83
11时00分16秒 all 0.01 0.00 0.15 0.00 0.00 99.84
平均时间: all 1.02 0.06 2.91 0.09 0.00 95.93
不带间隔通常读取当天历史文件,默认输出 CPU 统计;LINUX RESTART 表示记录到了系统重启。配置及采集方式可能是 cron,也可能是 systemd 定时器,以发行版实际配置为准。
实践
sar -u 1 # 实时 CPU
sar -r 2 6 # 每 2 秒采样物理内存,共 6 次
sar -S 2 6 # 交换空间使用
sar -W 1 # 换入换出速率
sar -b 1 # 块 I/O 汇总
sar -n DEV 1 # 网卡流量
sar -q 1 # 运行队列与负载
sar -f /var/log/sysstat/sa08
sa08 是本月 8 日的 sysstat 二进制历史文件,不能当普通文本直接阅读;实际目录、文件命名和保留策略取决于配置。
凌晨卡顿回溯案例
# sar -f /var/log/sysstat/sa08
01:20:00 CPU %user %nice %system %iowait %steal %idle
01:20:00 all 78.00 0.00 18.00 3.50 0.00 0.50
01:20 的 CPU 空闲只有 0.5%,说明该统计时段 CPU 非常繁忙。若与业务卡顿时间吻合,这是重要线索;还需查当时进程、任务和请求日志确认根因,不能由整机 CPU 一项直接证明具体业务故障原因。
优先检查凌晨定时任务和脚本,优化计算逻辑,拆分任务或错峰执行。其他历史线索包括交换持续增长、I/O 高且等待升高、网卡吞吐接近链路能力、运行队列持续过长。
1.6 dstat
dstat 将 CPU、内存、磁盘、网络、交换和系统事件放在同一屏幕显示,适合多资源联动排查。需要安装对应软件包,部分系统由兼容实现提供。
apt install dstat
dstat [options] [delay [count]]
-c CPU
-d 磁盘
-n 网络
-m 内存
--tcp TCP 连接统计
--disk-util 磁盘利用率
-o file 保存为 CSV 文件
默认示例:
[root@ubuntu24-13 ~]# dstat
You did not select any stats, using -cdngy by default.
----total-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read writ| recv send| in out | int csw
0 0 100 0 0| 0 92k| 60B 278B| 0 0 | 329 596
-cdngy 等价于 -c -d -n -g -y。
| 分组 | 字段 |
|---|---|
| total-usage | usr/sys/idl/wai/stl:用户态、内核态、空闲、I/O 等待、steal |
| dsk/total | read/writ:每秒磁盘读取、写入数据量 |
| net/total | recv/send:每秒网络接收、发送量 |
| paging | in/out:交换换入、换出速率 |
| system | int/csw:中断、上下文切换速率 |
实践
dstat # 默认监控,Ctrl+C 退出
dstat 1 # 每秒输出
dstat 1 10 # 共 10 次
dstat -c -d -n 2 # CPU、磁盘、网络,2 秒刷新
dstat -c -m --tcp 1 # CPU、内存、TCP
dstat --disk-util 1 # 磁盘利用率
dstat -o dstat_out.csv 1 20 # 保存 CSV
多资源压力案例
# dstat 1
----total-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read writ| recv send| in out | int csw
11 7 38 44 0| 130M 66M| 12M 8M| 0 126k |1240 24600
CPU 用户态和内核态合计 18%,而等待比例 44%;磁盘吞吐较大,并出现 swap 换出。该组合提示内存压力可能放大了 I/O 负载,应再用 vmstat、iostat 和进程统计验证。24600 次/秒的上下文切换是否异常,要结合 CPU 数和业务基线。
处理方向:减少不必要的线程和随机 I/O,按内存容量合理配置业务缓存;确认工作集不足时增加物理内存;确认设备能力不足时升级 SSD。已有内存压力时不能盲目调大缓存。
1.7 perf
perf 基于 Linux perf_events 子系统,支持 CPU 周期、缓存、上下文切换、缺页、tracepoint 等硬件和软件事件。它在发现系统异常后用于进一步定位函数、调用栈和代码热点。软件包一般为 linux-tools 或 linux-perf,与内核、权限和符号信息有关。
子命令
| 子命令 | 用途 |
|---|---|
perf top | 实时查看热点符号 |
perf record | 采样并保存到 perf.data |
perf report | 分析 perf.data |
perf annotate | 汇编/源码注解,源码行定位需要对应调试信息 |
perf stat | 统计事件计数器;不是读取 perf.data 的命令 |
perf script | 将采样记录输出为文本 |
perf list | 列出可用事件 |
perf mem | 内存访问分析 |
perf probe | 定义动态探测点 |
perf c2c | 缓存共享和伪共享分析 |
perf iostat | 部分平台/版本提供的 I/O 性能统计 |
perf diff | 对比采样文件 |
perf archive | 收集离线分析所需的符号文件 |
实时输出
[root@ubuntu24-13 ~]# perf top
Samples: 7K of event 'cpu-clock:ppp', 4000 Hz, Event count (approx.): 1868250000
lost: 0/0 drop: 0/0
Overhead Shared Object Symbol
95.09% [kernel] [k] pv_native_safe_halt
0.64% [kernel] [k] kallsyms_expand_symbol.constprop.0
0.52% perf [.] io__get_char
0.36% perf [.] kallsyms__parse
Samples是样本数,示例采样事件是cpu-clock;实际默认事件可能是cycles或回退到软件事件,应以输出为准。4000 Hz是目标采样频率,实际样本量受事件、运行时间和系统限制影响。Event count为估算事件计数,lost/drop用于观察采样丢失。Overhead是样本或事件权重占比,不一定等于该函数占整机 CPU 的绝对比例。Shared Object是所属程序、库或内核;[k]表示内核符号,[.]表示用户态符号。
这里 pv_native_safe_halt 是空闲相关路径,不能据其样本占比高就认定业务计算热点。Ctrl+C 退出。
若依进程实践
[root@ubuntu24-13 ~]# ps aux | grep java
root 12776 0.3 10.7 3917024 426340 ? Ssl 09:58 0:25 /usr/lib/jvm/java-17-openjdk-amd64/bin/java -jar ruoyi-admin.jar
# 全局热点
perf top
# 目标进程热点
perf top -p 12776
# 采样 10 秒并收集调用栈
perf record -g -p 12776 -- sleep 10
原实验输出:
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.070 MB perf.data (9 samples) ]
这表示只采到 9 个样本。默认 CPU 事件主要在目标运行时采样,目标长期休眠/阻塞时可能样本极少;它不会自动完整记录全部阻塞时间。
perf report
perf script
perf list
perf list | grep CPU_core
# 对目标进程统计 10 秒
perf stat -e task-clock,context-switches -p 12776 -- sleep 10
Performance counter stats for process id '12776':
10.34 msec task-clock
680 context-switches # 65.790 K/sec
10.002154586 seconds time elapsed
约 10 秒墙钟时间内目标累积运行约 10.34 毫秒,发生 680 次上下文切换。输出的 K/sec 以任务运行时间归一化,不能误解为每秒墙钟时间发生 6.579 万次切换。不指定时长的附着统计通常持续到中断或目标退出。
热点函数案例
# perf top -p 2891
Samples: 28K of event 'cycles', Event count (approx.): 3267891234
Overhead Shared Object Symbol
68.70% mybusiness [.] calc_big_loop
7.20% libc-2.39.so [.] memcpy
2.10% [kernel] [k] copy_user_generic
calc_big_loop 占样本 68.70%,应优先检查其循环和算法,再用调用栈确认上层调用来源:
perf record -g -p 2891 -- sleep 10
perf report
perf annotate
处理方向包括减少重复计算、优化算法、合理缓存及可行的并行化。高占比只说明热点,不自动证明死循环。Java JIT 方法解析还需适当的符号/栈支持。
若 perf top -p PID 无样本,先看目标是否在运行、事件是否可用及权限是否允许;上下文切换多时检查线程竞争,分支预测失败率高时分析相关代码路径。
2. Linux 进程追踪排障工具
以具体进程为立足点,检查系统调用、线程资源、父子关系和打开的文件。
2.1 strace
strace 通常通过 ptrace 跟踪系统调用、参数、返回值和信号,可用于启动失败、文件找不到、权限错误、网络连接失败和慢系统调用分析。频繁系统调用场景下开销可能很大,应限制目标和采集时间。
strace [选项] 命令
strace [选项] -p PID
-p PID 附着目标进程
-f 跟踪派生进程/线程
-e trace=... 限定系统调用或调用类别
-c 汇总调用次数、错误和耗时
-o file 保存跟踪文本
-tt 微秒精度时间戳
-T 单次调用耗时
输出示例
[root@ubuntu24-13 ~]# strace ls
execve("/usr/bin/ls", ["ls"], 0x7fff85a71c60 /* 25 vars */) = 0
brk(NULL) = 0x644787b27000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x705de34f5000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (没有那个文件或目录)
...
exit_group(0) = ?
+++ exited with 0 +++
格式为 系统调用(参数) = 返回值。-1 后跟 errno 通常表示失败,但单次失败可能是正常探测,例如程序寻找可选文件。非负返回值的含义由具体系统调用决定,并非都表示文件描述符。
| 系统调用 | 观测内容 |
|---|---|
open/openat | 文件路径、打开模式、权限和不存在错误 |
access | 文件可访问性检查;检查后再使用存在 TOCTOU 风险 |
read/write | 文件或套接字读写;返回字节数,EAGAIN 常表示暂时不能完成非阻塞操作 |
connect | 网络连接,ECONNREFUSED 表示连接被拒绝 |
mmap | 文件或匿名内存映射 |
brk | 调整数据段/堆边界;部分分配也使用 mmap |
fstat | 已打开文件的元数据 |
close | 关闭文件描述符 |
nanosleep | 休眠,检查是否存在大量等待 |
exit_group | 结束整个线程组 |
实践
# 跟踪一次命令
strace ls /tmp
# 附着若依,跟踪线程及后续派生任务
strace -f -p 12776
# 只看打开文件
strace -f -e trace=openat,open -p 12776
# 按路径操作的文件系统调用类别
strace -f -e trace=%file -p 12776
# 网络系统调用类别
strace -f -e trace=%network -p 12776
# 汇总统计,观察 10~20 秒后 Ctrl+C 结束跟踪
strace -f -c -p 12776
# 保存日志
strace -f -o ruoyi_strace.log -p 12776
# 时间戳与每次调用耗时
strace -f -tt -T -p 12776
%file 主要按文件名操作分类,不等价于全部文件描述符操作,不能保证包含所有 read/write/close。附着后若没有匹配事件,界面暂时不输出是正常的,可通过访问页面、上传文件等触发目标操作。
2.2 pidstat
pidstat 属于 sysstat,按进程/线程统计 CPU、I/O、内存、缺页和上下文切换。它通过周期采样读取内核统计,通常比 strace 的逐调用跟踪开销小,适合持续观察业务进程。
pidstat [选项] [间隔秒数 [采样次数]]
-p PID 指定进程
-t 同时显示线程
-u CPU 统计
-d 磁盘 I/O 统计
-w 上下文切换统计
-r 内存及缺页统计
保存日志使用 Shell 重定向;部分版本 -o JSON 用于格式选择,不能把 -o file 当作通用输出文件选项。
[root@ubuntu24-13 ~]# pidstat -p 12776
Linux 6.8.0-31-generic (ubuntu24-13) 2026年09月08日 _x86_64_ (2 CPU)
13时09分51秒 UID PID %usr %system %guest %wait %CPU CPU Command
13时09分51秒 0 12776 0.26 0.07 0.00 0.00 0.33 1 java
%CPU 为进程 CPU 使用率;CPU 表示观测到的 CPU 编号;%wait 是等待获得 CPU 的时间比例。未指定间隔的报告不能视为最新一秒实时采样。
# 每 2 秒采样,共 5 次
pidstat -u -p 12776 2 5
# 进程磁盘 I/O
pidstat -d -p 12776 2
# 上下文切换
pidstat -w -p 12776 2
# 线程 CPU
pidstat -t -u -p 12776 2
# 同时输出 CPU、I/O、线程并保存日志
pidstat -t -u -d -p 12776 2 > ruoyi_pidstat.log
# 整机活动进程 CPU 统计(不保证按 CPU 降序)
pidstat 2
2.3 pstree
pstree 以树形展示进程父子和线程关系,适合查看进程派生、服务启动来源及僵尸进程的父进程。Ubuntu 可安装 psmisc。
pstree [选项] [PID | USER]
-p 显示 PID/TID,同时取消相同子树压缩
-t 显示完整线程名
-u 标记与父进程不同的 UID
-A ASCII 线条
-T 隐藏线程
[root@ubuntu24-13 ~]# pstree
systemd─┬─ModemManager───3*[{ModemManager}]
├─NetworkManager───3*[{NetworkManager}]
├─VGAuthService
...
xxx(12776) 是进程及 PID;{xxx}(12777) 中花括号表示线程,数字为 TID。JVM 的业务、GC 等线程可以在其中观察。
pstree -tp 12776 # 目标进程及线程,显示名字与 PID/TID
pstree -tpu 12776 # 同时标记 UID 变化
pstree -A # 整机 ASCII 树
pstree -u root # root 用户进程树
pstree -tp 12776 > ruoyi_pstree.log
2.4 lsof
lsof(List Open Files)查看进程打开的普通文件、目录、套接字、管道和设备。可用于端口占用、文件描述符泄漏、已删除文件仍占磁盘空间等排查。
文件描述符 fd 是进程持有资源的整数编号;耗尽时可能出现 Too many open files。
ulimit -Sn # 当前 Shell 的软限制
ulimit -Hn # 当前 Shell 的硬限制
ulimit -n # 默认查看软限制
cat /proc/sys/fs/file-max # 全局文件句柄上限
cat /proc/12776/limits # 目标进程实际限制
Shell 的 ulimit 不一定等于 systemd 管理的服务限制,应检查目标进程或服务的 LimitNOFILE。
lsof [选项]
-p PID 指定进程
-i 网络套接字
-i :8080 指定端口
+D 目录 递归查看目录下打开的文件
-c java 按命令名前缀过滤
-a 多个选择条件按 AND 组合
-nP 不解析主机名和端口服务名
输出解读
[root@ubuntu24-13 ~]# lsof -p 12776
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 12776 root cwd DIR 8,2 4096 9961475 /data/ruoyi/server
java 12776 root rtd DIR 8,2 4096 2 /
COMMAND/PID/USER:程序名、进程号和用户。FD:cwd为当前目录,rtd为根目录;1w表示 fd 1 以写方式打开,42u表示 fd 42 可读写,其是否 socket 还需看TYPE。TYPE:REG普通文件、DIR目录、IPv4/IPv6网络套接字等。NAME:路径或网络端点,*:8080 (LISTEN)表示监听 8080。
实践
lsof -p 12776 # 进程打开的全部文件
lsof -a -p 12776 -i -nP # 该进程的网络套接字
lsof -i :8080 -nP # 8080 端口相关套接字
lsof -iTCP:8080 -sTCP:LISTEN -nP # 只看 TCP 监听者
lsof -Pti :8080 # 仅输出相关 PID
lsof -a -p 12776 +D /var/log # 该进程在目标目录下打开的文件
lsof -c java # java 命令名匹配
lsof -p 12776 > ruoyi_lsof.log # 保存文本日志
lsof +L1 # 检查链接数小于 1 的已删除打开文件
# 按数字 fd 统计目标进程的描述符数,排除 cwd、rtd、mem 等记录
lsof -p 12776 -F f | grep -Ec '^f[0-9]+$'
lsof -p PID | wc -l 只能粗略统计记录行,还包含表头、内存映射等,不是准确的 fd 数。lsof 的不同选择条件默认常按 OR 组合,因此“指定进程且指定网络/目录”要加 -a。-o 是显示文件偏移量,保存日志应使用 >。
3. 网络性能观测工具集
本节观察真实网络活动,从网卡流量、连接状态、报文和弱网模拟几个方向定位异常。
3.1 iftop
iftop 按通信端点展示网卡实时流量。它是网卡级工具,不能直接按 PID 过滤;需要结合端口和 lsof/ss 判断连接属于哪个业务。
apt install iftop
iftop [选项]
-i ens33 指定网卡
-P 显示端口
-n 不反向解析主机名
-N 不把端口转换为服务名
-B 速率以字节显示;默认按 bit 显示
-f 表达式 抓包过滤条件
交互界面中:P 暂停/继续,T 切换累计传输量显示,j/k 滚动,q 退出。
输出解读
[root@ubuntu24-13 ~]# iftop
12.5Kb 25.0Kb 37.5Kb 50.0Kb 62.5Kb
└─────────┴───────────┴───────────┴───────────┴───────────
源端点 目标端点 2s流量 10s流量 40s流量
bogon => bogon 1.38Kb 1.73Kb 1.73Kb
<= 160b 213b 213b
bogon => bogon 0b 269b 269b
<= 0b 345b 345b
────────────────────────────────────────────────────────
TX: cum: 1.49KB peak: 2.91Kb rates: 1.38Kb 1.99Kb 1.99Kb
RX: 419B 1.32Kb 160b 559b 559b
TOTAL: 1.90KB 4.23Kb 1.53Kb 2.54Kb 2.54Kb
箭头表示流量方向;三列速率通常是过去 2、10、40 秒的平均速率。底部 TX/RX/TOTAL 是发送、接收和合计,cum/peak/rates 分别是累计量、峰值和窗口平均速率。
实验网卡信息:
interface: ens33
IP address is: 10.0.0.13
MAC address is: 00:0c:29:40:b4:6f
实践
# 显示 IP 和数字端口
iftop -i ens33 -nNP
# 使用字节速率
iftop -i ens33 -nNPB
# 只观察 8080 流量;没有流量时内容为空
iftop -i ens33 -nNP -f 'port 8080'
# 确认连接所属进程
lsof -a -p 12776 -i -nP
3.2 ss
ss(Socket Statistics)用于查看套接字状态,是 netstat 的常见替代工具,适合检查监听端口、TCP 状态、队列和内部信息。
ss [选项] [过滤表达式]
-t TCP
-u UDP
-l 监听套接字
-a 包括监听与非监听状态
-n 数字地址和端口
-p 进程信息;查看其他用户进程可能需要权限
-i TCP 内部信息
输出
[root@ubuntu24-13 ~]# ss
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
u_str ESTAB 0 0 * 14130 * 14131
u_str ESTAB 0 0 /run/systemd/journal/stdout 11225 * 11039
State:如 LISTEN、ESTAB、TIME-WAIT、CLOSE-WAIT。Local/Peer Address:Port:本地和对端地址。- 对已建立 TCP 连接,
Recv-Q主要表示应用尚未读取的数据字节,Send-Q主要表示尚未确认的数据字节。瞬时非零并不等于故障,持续增长才值得关注。 - 对监听 TCP 套接字,队列字段通常表示当前等待 accept 的连接数和 backlog 上限,不能按字节解读。
常见排查方向:
- 大量 TIME-WAIT 通常与主动关闭和短连接有关,先确认连接复用、端口/连接资源压力,不能一看到数量大就调整回收参数。
- 大量 CLOSE-WAIT 表示对端已关闭而本地尚未关闭,持续积累时检查应用资源释放。
- 已建立连接队列持续积压,结合应用读取速度、对端处理、丢包重传和带宽分析。
实践
ss -tlnp # TCP 监听端口
ss -tanp '( sport = :8080 or dport = :8080 )'
# 只统计本机业务端口上已建立的连接;-H 去掉表头
ss -Htan state established '( sport = :8080 )' | wc -l
# 各状态数量
ss -Htan '( sport = :8080 )' | awk '{print $1}' | sort | uniq -c
# TCP 内部信息
ss -tani
# MySQL 相关连接
ss -tanp '( sport = :3306 or dport = :3306 )'
使用 ss 自身过滤表达式可以减少 grep 8080 匹配其他地址或端口带来的误差。
3.3 tcpdump
tcpdump 抓取原始网络报文,用于检查交互顺序、握手、连接失败、重传和明文协议内容,也可以保存为 pcap 交给 Wireshark 分析。抓包通常需要 root 或相应 capability。
tcpdump [选项] [过滤表达式]
-i ens33 指定网卡
-n 不解析主机名
-nn 主机和端口都数字显示
-w ruoyi.pcap 保存原始抓包文件
-r ruoyi.pcap 读取抓包文件
-c 100 捕获 100 个报文后结束
-A ASCII 输出内容
port 8080 8080 端口报文
port 3306 MySQL 端口报文
host 192.168.1.20 与目标 IP 相关报文
tcp TCP 协议
输出解读
[root@ubuntu24-13 ~]# tcpdump -nn
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), snapshot length 262144 bytes
14:17:47.267325 IP 10.0.0.13.22 > 10.0.0.1.53848: Flags [P.], seq 16108345:16108393, ack 2510831917, win 997, length 48
14:17:47.310800 IP 10.0.0.1.53848 > 10.0.0.13.22: Flags [.], ack 48, win 4105, length 0
时间戳后是源 IP/端口到目标 IP/端口;seq/ack/win/length 分别是序列号、确认号、通告窗口和本次 TCP 载荷长度。相对序列号显示取决于是否已见到连接前面的报文。
| 标志 | 含义 |
|---|---|
[S] | SYN,建立连接 |
[F] | FIN,发送方不再发送数据 |
[R] | RST,复位连接 |
[P] | PSH,推送提示;不是“此包一定包含完整应用消息” |
[.] | ACK |
[P.] | PSH 和 ACK 同时置位 |
TCP 本身没有应用消息边界,抓到报文也不一定能还原完整请求;TLS 加密内容不能直接按明文读取。tcpdump 不直接按 PID 过滤,需要通过网卡、IP、端口等限定。
实践
# 浏览器 Ctrl+F5 刷新页面,观察 HTTP 端口
tcpdump -i ens33 -nn port 80
# 若依连接远端 MySQL;本机回环连接需选择对应接口
tcpdump -i ens33 -nn port 3306
# 保存 200 个包,随后用 Wireshark 打开
tcpdump -i ens33 -nn port 80 -w ruoyi.pcap -c 200
# 回放
tcpdump -nn -r ruoyi.pcap
# 查看明文 HTTP 报文
tcpdump -i ens33 -nnA port 80
3.4 tc
tc(Traffic Control)是 iproute2 的用户态命令,配置 Linux 内核流量控制。可借助 netem 模拟延迟、抖动、丢包、损坏,或借助 tbf 限制带宽,用于测试业务超时、重试和大文件传输表现。
tc qdisc [add|change|replace|del|show] dev 网卡 规则
dev ens33 目标网卡
delay 200ms 延迟
loss 5% 随机丢包
corrupt 2% 随机损坏
rate 1mbit 速率
根 qdisc 通常控制出口方向;入口整形常借助重定向到 IFB 实现。规则作用于网卡流量,不会自动限定为若依进程或 8080 端口。操作同一 SSH 网卡也会让远程终端变慢。
固定延迟实验
tc qdisc add dev ens33 root netem delay 1000ms
tc qdisc show dev ens33
浏览器响应和 SSH 交互会明显变慢。测试结束清理:
tc qdisc del dev ens33 root
其他场景
以下是独立实验,不能在已有 root qdisc 时连续 add;切换场景前先处理原规则。
# 200 ms 延迟,抖动参数 50 ms
tc qdisc add dev ens33 root netem delay 200ms 50ms
# 5% 随机丢包
tc qdisc add dev ens33 root netem loss 5%
# 延迟与丢包
tc qdisc add dev ens33 root netem delay 150ms loss 3%
# 出口限制为 2 Mbit/s
tc qdisc add dev ens33 root tbf rate 2mbit burst 10k latency 70ms
# 查看和删除本次 root qdisc
tc qdisc show dev ens33
tc qdisc del dev ens33 root
固定延迟场景验证超时处理;丢包场景验证重试和熔断;限速场景检查导出和下载。删除 root qdisc 不是恢复任意原有自定义规则的备份操作,有原配置时应先记录并按需恢复。
4. 网络性能压测工具集
压测工具主动产生测试数据,不是被动采集业务流量。测试结果可以辅助缩小问题范围,但某种报文大小、并发度和方向测试正常,不代表所有真实业务网络路径都正常。
4.1 iperf3
iperf3 采用客户端/服务端模型,测试两台机器间 TCP/UDP 吞吐,并提供 TCP 重传、UDP 抖动与丢包等统计。它测网络测试流,不执行数据库查询或业务 API;不能直接提供真实业务请求延迟。
iperf3 -s [选项]
iperf3 -c 服务端IP [选项]
-s 服务端
-c IP 客户端
-p 5201 端口,默认 5201
-t 30 持续秒数,默认通常为 10 秒
-P 4 4 条并行流
-u UDP 数据流
-b 100M 目标发送速率;UDP 默认通常为 1 Mbit/s,TCP 默认不限速
-R 反向:服务端发送,客户端接收
-b 也可以用于 TCP 限速,具体实现及每流语义以版本帮助为准。
安装与服务端
多节点安装:
apt install iperf3 -y
课件实验选择了软件包提供的服务模式:
[root@ubuntu24-13 ~]# systemctl is-active iperf3.service
active
[root@ubuntu24-13 ~]# netstat -tnulp | grep iperf3
tcp6 0 0 :::5201 :::* LISTEN 22852/iperf3
如改为前台启动,应避免和已有服务抢占同一端口:
[root@ubuntu24-13 ~]# systemctl stop iperf3.service
[root@ubuntu24-13 ~]# iperf3 -s
-----------------------------------------------------------
Server listening on 5201 (test #1)
-----------------------------------------------------------
TCP 带宽实验
[root@ubuntu24-16 ~]# iperf3 -c 10.0.0.13 -t 20
Connecting to host 10.0.0.13, port 5201
[ 5] local 10.0.0.16 port 35192 connected to 10.0.0.13 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 675 MBytes 5.66 Gbits/sec 90 1.69 MBytes
...
[ 5] 19.00-20.00 sec 816 MBytes 6.84 Gbits/sec 10 1.67 MBytes
- - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-20.00 sec 15.4 GBytes 6.62 Gbits/sec 296 sender
[ 5] 0.00-20.00 sec 15.4 GBytes 6.62 Gbits/sec receiver
iperf Done.
| 字段 | 含义 |
|---|---|
ID | 测试流的内部标识,不是“第几条流”的固定编号 |
Interval | 统计时间区间 |
Transfer | 区间内传输的数据量 |
Bitrate | 区间平均速率 |
Retr | 发送端观察到的 TCP 重传数 |
Cwnd | TCP 拥塞窗口 |
sender/receiver | 发送端和接收端统计 |
20 秒传输约 15.4 GiB,平均约 6.62 Gbit/s,共 296 次重传。TCP 重传保证可靠交付的同时会影响延迟或吞吐;吞吐正常不表示完全无丢包。虚拟机间结果也不一定代表物理网卡线速。
多流时关注每条流和 [SUM] 汇总,同一条流会按多个时间段重复输出,不能通过简单数方括号行数来统计并发数;客户端和服务端的内部 ID 也不要求相同。
实践
# 4 条并发流
iperf3 -c 10.0.0.13 -t 20 -P 4
# 反向测试
iperf3 -c 10.0.0.13 -R -t 20
# 自定义端口:对应两台机器分别执行
iperf3 -s -p 5202
iperf3 -c 192.168.1.20 -p 5202
# 以目标速率 100 Mbit/s 做 UDP 测试
iperf3 -c 10.0.0.13 -u -b 100M -t 20
UDP 抖动和丢包实验
原实验未指定 -b,使用默认约 1 Mbit/s:
[root@ubuntu24-16 ~]# iperf3 -c 10.0.0.13 -u -t 20
Connecting to host 10.0.0.13, port 5201
[ 5] local 10.0.0.16 port 54669 connected to 10.0.0.13 port 5201
[ ID] Interval Transfer Bitrate Total Datagrams
[ 5] 0.00-1.00 sec 129 KBytes 1.05 Mbits/sec 91
...
[ 5] 19.00-20.00 sec 129 KBytes 1.05 Mbits/sec 91
- - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams
[ 5] 0.00-20.00 sec 2.50 MBytes 1.05 Mbits/sec 0.000 ms 0/1811 (0%) sender
[ 5] 0.00-20.00 sec 2.50 MBytes 1.05 Mbits/sec 0.279 ms 0/1811 (0%) receiver
iperf Done.
Total Datagrams 是报文数;Jitter 为接收端依据到达间隔估算的抖动,不是 RTT;Lost/Total 为该流的丢失/总报文数。这里接收端在本次负载下收到全部 1811 个报文,抖动约 0.279 ms。发送端的零抖动和零丢失不能替代接收端观测;1 Mbit/s 测试也无法证明满负载下仍然无丢包。
4.2 netperf
netperf 使用 socket 测试网络吞吐及请求/响应事务能力。客户端是 netperf,服务端是 netserver,默认控制端口为 12865;数据测试还可能协商其他端口,不能只放行控制端口就保证全部测试可用。
netperf [全局选项] -H 服务端IP -- [测试选项]
netserver [选项]
-H host 服务端地址
-p port 服务端控制端口
-t TESTNAME 测试类型
-l N 测试秒数
-P 0 不输出测试标题
-- 后面是具体测试的选项
测试选项示例:
-r req,resp RR 请求、响应字节数
-m size STREAM 本地发送调用大小
-M size STREAM 远端接收调用大小
| 模式 | 含义 |
|---|---|
TCP_STREAM | TCP 批量吞吐 |
UDP_STREAM | UDP 吞吐 |
TCP_RR | 一条 TCP 长连接反复请求/响应 |
TCP_CRR | 每笔事务建立连接、请求/响应,再关闭连接 |
UDP_RR | UDP 请求/响应 |
RR 适合研究数据库/RPC 等小包交互模型,但不执行真实 SQL 或业务逻辑。netperf 也能测大带宽,并非只能测小包。事务率偏低可能受到 CPU、调度、缓冲区、报文大小和链路共同影响。
安装与服务端
apt install netperf
课件实验使用软件包服务:
[root@ubuntu24-13 ~]# systemctl is-active netperf.service
active
[root@ubuntu24-13 ~]# netstat -tnulp | grep netserver
tcp6 0 0 :::12865 :::* LISTEN 23807/netserver
切换手工启动:
[root@ubuntu24-13 ~]# systemctl stop netperf.service
[root@ubuntu24-13 ~]# netserver
Starting netserver with host 'IN(6)ADDR_ANY' port '12865' and family AF_UNSPEC
TCP_RR 实验
请求和响应各 256 字节,持续 20 秒:
[root@ubuntu24-16 ~]# netperf -t TCP_RR -H 10.0.0.13 -l 20 -- -r 256,256
MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to
10.0.0.13 () port 0 AF_INET : demo : first burst 0
Local /Remote
Socket Size Request Resp. Elapsed Trans.
Send Recv Size Size Time Rate
bytes Bytes bytes bytes secs. per sec
16384 131072 256 256 20.00 2845.13
16384 131072
带 -l 20 的正常测试在约 20 秒后自行输出结果,不需要 Ctrl+C。字段含义:
Socket Size Send/Recv:本地和远端的发送/接收缓冲区。Request/Resp. Size:请求和响应字节数。Elapsed Time:测试时间。Trans. Rate:每秒完成事务数 TPS,是 RR 模式的主要指标。
默认串行、单笔在途 RR 模式下,每笔事务平均耗时可粗略估算:
1 / 2845.13 秒 ≈ 0.351 ms
它包括两端协议栈、调度和数据收发处理,不是纯链路传播 RTT。TCP_RR 复用连接,TCP_CRR 包含反复建连的成本,后者通常事务率更低,但应在相同条件下比较。TCP 的重传不会直接显示为 UDP 式丢包率。
实践
# 长连接,每笔请求/响应 256 字节
netperf -t TCP_RR -H 10.0.0.13 -l 20 -- -r 256,256
# 每笔新建连接,请求/响应 128 字节
netperf -t TCP_CRR -H 10.0.0.13 -l 20 -- -r 128,128
# UDP 请求/响应,各 64 字节
netperf -t UDP_RR -H 10.0.0.13 -l 20 -- -r 64,64
# 去掉标题
netperf -t TCP_RR -H 10.0.0.13 -l 10 -P 0 -- -r 64,64
# 模拟 Redis 小报文交互
netperf -t TCP_RR -H 10.0.0.13 -l 15 -- -r 64,64
# 保存结果
netperf -t TCP_RR -H 10.0.0.13 -l 20 -- -r 256,256 > netperf_rr.log
# 自定义控制端口:服务端与客户端分别执行
netserver -p 2333
netperf -H 10.0.0.13 -p 2333 -t TCP_STREAM -l 20
netperf 的 -P 是标题开关,端口使用小写 -p;STREAM 应使用明确的测试名,如 TCP_STREAM。-b 不是 iperf3 式目标带宽参数,某些构建用它设置 burst/节拍,不能写成 -b 1G 来指定 UDP 带宽。
4.3 qperf
qperf 是客户端/服务端网络性能工具,支持 TCP、UDP 及部分 RDMA 测试。它输出时延、带宽等链路测试结果,不直接输出业务 TPS。
# 服务端
qperf
# 客户端:参数放在需要应用这些参数的测试项之前
qperf [选项] server_ip test1 test2
-m, --msg_size N 消息大小,字节
-t, --time N 每项测试持续时间
-lp, --listen_port N 控制端口,默认 19765
-ip, --ip_port N TCP/UDP 测试数据端口
-vu 显示使用的参数,便于核对
保存日志使用 Shell 重定向。普通 TCP/UDP 带宽测试使用 --msg_size 设置消息大小,不使用不通用的 -io_sz;-o file 也不是通用的保存文件方式。
| 测试项 | 含义 |
|---|---|
tcp_lat | TCP ping-pong 时延 |
tcp_bw | TCP 带宽 |
udp_lat | UDP ping-pong 时延 |
udp_bw | UDP 发送和接收带宽 |
qperf 的 TCP/UDP latency 通常按一次往返的两个消息方向平均,近似 RTT/2,不能直接把输出值标成完整往返时延;该估算也不能替代精确时钟同步的一程测量。带宽默认显示的 MB/sec 是字节速率,不能与 Mbit/s 混用。
安装与服务端
apt install qperf -y
# 前台运行
qperf
# 或放到后台
qperf &
# 或脱离终端并保存日志
nohup qperf > /var/log/qperf.log 2>&1 &
三种启动方式任选一种。课件环境未提供配套 systemd 服务。
[root@ubuntu24-13 ~]# netstat -tnulp | grep qperf
tcp6 0 0 :::19765 :::* LISTEN 25022/qperf
参数位置与实验输出
qperf 按命令行顺序处理参数,遇到测试项就执行该项;设置的参数对后续测试生效。要让四项测试都使用 256 字节,应写成:
qperf 10.0.0.13 -vu -m 256 tcp_lat tcp_bw udp_lat udp_bw
不能把 --msg_size 256 放到所有测试项之后,后置参数不会回头改变已经完成的测试。参数放在第一个测试前时,也不是只影响第一项。
课件保留的两组实测输出如下。其原有报文大小归因不可靠,因此只保留测量值,不能据此认定是两组“统一 256 字节”和“默认报文”的严格对照。
第一组:
tcp_lat:
latency = 163 us
tcp_bw:
bw = 849 MB/sec
udp_lat:
latency = 193 us
udp_bw:
send_bw = 200 MB/sec
recv_bw = 198 MB/sec
第二组:
tcp_lat:
latency = 190 us
tcp_bw:
bw = 309 MB/sec
udp_lat:
latency = 147 us
udp_bw:
send_bw = 32.2 MB/sec
recv_bw = 31.6 MB/sec
带宽受 CPU、调度、消息大小及网络实现等影响,不能仅凭 849 MB/sec 就确认“虚拟网卡已经跑满”。做对比应固定消息大小、方向、并发和测试时间,并输出参数验证。
实践
# TCP,两项都使用 256 字节
qperf 10.0.0.13 -m 256 tcp_lat tcp_bw
# UDP,两项都使用 64 字节
qperf 10.0.0.13 --msg_size 64 udp_lat udp_bw
# 四项都使用 256 字节
qperf 10.0.0.13 -m 256 tcp_lat tcp_bw udp_lat udp_bw
# 单独测 10 秒并保存结果
qperf 10.0.0.13 -t 10 -m 256 tcp_lat > qperf_tcp_lat.log
# 只测 TCP 时延
qperf 10.0.0.13 -m 1024 tcp_lat
# 自定义控制端口:分别在服务端和客户端执行
qperf -lp 20001
qperf 10.0.0.13 -lp 20001 tcp_lat tcp_bw
三类工具的侧重点:
| 需求 | 优先考虑 |
|---|---|
| 测网络吞吐上限、并行流、UDP 抖动和丢包 | iperf3 |
| 测请求/响应模型的事务能力、长短连接成本 | netperf RR/CRR |
| 测小消息时延、带宽,或适用的 RDMA 项目 | qperf |
测试正常时再结合业务协议、连接复用、数据库与中间件指标继续定位,不能由单次网络压测排除所有网络问题。
5. RuoYi-Vue 业务压测、故障复现与指标复盘
本节使用课件配套改造项目,复现大结果集、慢 SQL、纯 CPU 计算、Redis key 持续增长和日志膨胀。下列调用依赖配套脚本、控制器和 SQL;没有这些文件时,使用第 13 节的独立实验。
5.1 准备工作
改造服务
课件提供 update_export_report_ruoyi_ubuntu24.sh:
/bin/bash update_export_report_ruoyi_ubuntu24.sh
# 编译项目
cd RuoYi-Vue/
mvn clean package -DskipTests
# 前台测试,应避免与已有服务同时监听同一端口
java -jar ruoyi-admin/target/ruoyi-admin.jar
前台测试退出后,更新服务包并启动:
cp ruoyi-admin/target/ruoyi-admin.jar /data/ruoyi/server/
systemctl restart ruoyi-admin.service
压测准备
apt update
apt install -y linux-tools-common linux-tools-$(uname -r) netperf qperf iperf3
apt install apache2-utils
# 导入课件配套的大表 SQL,按提示输入数据库密码
mysql -uruoyi -p < test_ry_cpu_high.sql
数据库密码使用实际环境配置。内核 tools 软件包须与实际内核和发行版对应。
5.2 常见内存与 CPU 故障
内存问题
| 问题 | 现象与原因 | 检查方向 |
|---|---|---|
| 系统或 cgroup OOM | 可用内存不足、限制过小或突发大额申请,内核选择并杀死任务 | 内核日志、cgroup memory events、限制及 OOM 评分;不一定杀死占用最大的进程 |
| JVM 堆 OOM | Java 堆无法满足分配,出现 OutOfMemoryError: Java heap space | 堆容量、对象保留、查询结果规模、GC;不等同于内核 OOM kill |
| 内存泄漏 | 占用持续增长,重启后暂时恢复 | 对象/资源生命周期、引用关系、缓存和保留策略 |
| 内存抖动 | 工作集超过内存,反复换入换出,系统卡顿、I/O 增长 | si/so、回收、工作集;不一定发生 OOM |
| 物理页碎片 | 总空闲量尚可,但高阶连续物理页申请失败 | dmesg 的 allocation failure、/proc/buddyinfo、/proc/pagetypeinfo |
| Slab 占用过高 | 内核对象大量占用,可能正常缓存也可能泄漏 | slabtop、/proc/slabinfo、可回收与不可回收 Slab |
| 段错误 | 非法地址访问导致进程崩溃,也可能涉及硬件错误 | core、调用栈、日志和硬件诊断 |
空闲内存不为 0 仍可能因 cgroup、NUMA、分配阶数或保留内存约束触发失败。/proc/buddyinfo 观察伙伴系统空闲块,不是直接检查 Slab 对象碎片的工具。
CPU 与负载问题
- 用户态高:业务计算、循环、GC 等,进一步看进程/线程和调用栈。
- 内核态高:系统调用、内核处理、上下文切换等,需进一步定位。
- 硬/软中断高:检查设备中断、网络报文、RSS 队列分布。
- 上下文切换高:检查线程数、锁竞争和频繁唤醒,结合有效工作量判断。
- 可运行队列长:运行和等待 CPU 的任务较多,可能存在 CPU 排队。
- 大量 D 状态:任务不可中断睡眠,可能与磁盘、NFS 等等待有关;致命信号可能要等任务退出不可中断等待才生效。
Linux load average 包含可运行任务及不可中断睡眠任务,不等同于 CPU 使用率。因此 load 很高而 CPU 使用率不高时,也要检查 D 状态和 I/O,不能把 load 全当作等待 CPU 的队列。
5.3 大结果集触发 JVM 堆 OOM
访问导出接口:
curl 'http://127.0.0.1:8080/demo/exportReport?beginTime=2026-01-01%2000:00:00&endTime=2026-12-31%2023:59:59'
原实验返回的 JSON 错误信息整理如下,保留错误链和 SQL:
{
"msg": "\n### Error querying database. Cause: java.sql.SQLException: Java heap space\n### The error may exist in URL [jar:nested:/root/RuoYi-Vue/ruoyi-admin/target/ruoyi-admin.jar/!BOOT-INF/lib/ruoyi-system-3.9.2.jar!/mapper/system/ReportMapper.xml]\n### The error may involve defaultParameterMap\n### The error occurred while setting parameters\n### SQL: select * from report_table where create_time between ? and ?\n### Cause: java.sql.SQLException: Java heap space\n; Java heap space",
"code": 500
}
检查日志:
[root@ubuntu24-13 ~]# grep -I OutOfMemory /data/ruoyi/logs/sys-error.log
Caused by: java.lang.OutOfMemoryError: Java heap space
数据库有数百万行,查询把大结果集一次性装入 JVM 堆,可能超过堆容量。这是 JVM 堆分配失败的证据,不能据此认定内核已杀进程。
解决方向:使用真正限制数据库返回行数的分页或游标/流式读取,并及时处理和释放结果;流式导出后也不能把所有结果重新聚合到大集合中。
systemctl restart ruoyi-admin.service
curl -s 'http://127.0.0.1:8080/demo/exportReportPage?beginTime=2026-03-01%2000:00:00&endTime=2026-12-31%2023:59:59&pageSize=100' >/dev/null
应结合接口结果、堆和 GC 指标确认修复效果;把响应重定向到 /dev/null 只是避免打印,不能证明服务端未发生 OOM。
5.4 慢查询与高 CPU 复现
压测
ab -n 40000 -c 8 'http://127.0.0.1:8080/demo/exportReportPage?beginTime=2026-03-01%2000:00:00&endTime=2026-12-31%2023:59:59&pageSize=100'
-n 为总请求数,-c 为并发数。并发不必机械限制为 CPU 数,应从较小值逐步增加,结合业务容量和实验目的选择,避免压测端先成为瓶颈。
整机和进程指标
[root@ubuntu24-13 ~]# vmstat -n 1
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
r b swpd free buff cache si so bi bo in cs us sy id wa st gu
5 0 608652 1314848 37904 737620 2 45 5122 2570 1249 12 1 2 96 0 0 0
13 0 608652 1403008 37904 737752 0 0 0 16 6179 12445 83 14 4 0 0 0
...
第一行可能是开机平均,应重点看后续间隔采样。r=13 且用户态/内核态繁忙,提示可运行任务正在竞争 CPU;原始输出的百分比存在四舍五入偏差。
[root@ubuntu24-13 ~]# top
top - 21:05:22 up 11:32, 6 users, load average: 1.84, 1.24, 0.65
...
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
33500 root 20 0 3917576 1.3g 26368 S 149.0 33.5 3:50.27 java
...
多核环境中 top 的进程 %CPU 可以超过 100%,此例约消耗 1.49 个逻辑 CPU。高 CPU 的线程归属和执行内容仍需继续确认:
# 先通过 jps -lv 确认若依 PID;此例为 33500
jps -lv
top -H -p 33500
# 同时观察 MySQL;pidof 可能返回多个 PID,按实际进程选择
top -p $(pidof mysqld)
# JVM GC 指标
jstat -gc 33500 1000
GC 结果复盘
课件记录:
| 指标 | 观察 |
|---|---|
MC/MU | MC=96256 K,MU 约在 21504~86016 K 波动 |
CCSC/CCSU | CCSC=12352 K,CCSU 约 11992.5 K |
YGC | 213 → 254 |
YGCT | 0.605 → 0.667 秒 |
FGC/FGCT | 0 / 0.000 |
CGC/CGCT | 8 / 0.013 秒 |
GCT | 0.618 → 0.680 秒 |
这些样本中 GC 累计耗时增长很小:41 次年轻代 GC 增加约 0.062 秒,没有 Full GC 记录,说明采样期间 GC 暂未呈现主要耗时。不能仅凭短时元空间波动排除所有类加载泄漏,也不能由 FGC=0 推断永远不会 OOM;字段和计时口径依 JVM/收集器而异。
继续采样调用栈:
perf record -g -p 33500 -- sleep 30
perf report
perf record 只是采样,生成火焰图还需合适的 Java 栈解析和可视化工具,不会自动得到火焰图。
MySQL 侧排查
SHOW PROCESSLIST;
EXPLAIN SELECT * FROM ry_vue.report_table
WHERE create_time BETWEEN '2026-01-01' AND '2026-12-31'\G
原输出摘录:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: report_table
partitions: NULL
type: ALL
...
type=ALL 说明该计划为全表扫描;是否低效需结合扫描行数、过滤比例和真实执行耗时。日期条件还应确认是否包含期望的最后一天。
检查现有索引,再按查询模式添加索引:
SHOW INDEX FROM ry_vue.report_table;
CREATE INDEX idx_report_createtime
ON ry_vue.report_table(create_time);
EXPLAIN SELECT create_time FROM ry_vue.report_table
WHERE create_time >= '2026-01-01'
AND create_time < '2027-01-01'\G
半开区间覆盖完整年度,避免 '2026-12-31' 仅表示当天零点。建索引耗时及锁影响取决于数据量、版本、硬件和 DDL 方式,不能保证数百万行只需几秒。
只查询 create_time 可能使用覆盖索引,但必须由实际业务需要决定字段,不能为了性能丢掉导出所需列。若接口采用 lastId 游标分页,还需检查真实 SQL 的过滤和排序,可能需要不同的复合索引。
修复后测试
# 测试第一页
curl 'http://127.0.0.1:8080/demo/exportReportIndex?beginTime=2026-01-01%2000:00:00&endTime=2026-12-31%2023:59:59&lastId=0&pageSize=100'
# 相同请求量与并发下复测
ab -n 40000 -c 8 'http://127.0.0.1:8080/demo/exportReportIndex?beginTime=2026-01-01%2000:00:00&endTime=2026-12-31%2023:59:59&lastId=0&pageSize=100'
API 的结束时间是否包含最后一秒及小数部分,应由接口的日期语义统一处理。复测时比较正确性、错误率、吞吐、分位延迟、查询计划和每请求成本。top 仍可用于观察资源,但仅凭 CPU 是否下降无法判断优化是否有效:吞吐提高时 CPU 也可能保持较高。反复请求第一页也不能代表完整导出或深分页性能。
5.5 纯 CPU 飙高
ab -n 40000 -c 8 'http://127.0.0.1:8080/demo/cpuCalc'
# 确认若依实际 PID 后观察线程
jps -lv
top -H -p 33500
jstack 33500
课件堆栈摘录:
"http-nio-8080-exec-8" ... RUNNABLE
at com.ruoyi.web.controller.ReportController.cpuCalc(ReportController.java:61)
"http-nio-8080-exec-11" ... RUNNABLE
at com.ruoyi.web.controller.ReportController.cpuCalc(ReportController.java:61)
结合线程 CPU 和重复采样,如果高 CPU 线程反复停在 cpuCalc 同一计算位置,可定位该接口为重点检查对象。单次 RUNNABLE 栈本身不能证明占用最高。
解决方向:检查 cpuCalc 的循环、算法、重复计算及输入规模,修复后在相同负载下比较响应时间和每请求 CPU 成本。
5.6 Redis key 持续增长
触发实验接口:
curl 'http://127.0.0.1:8080/demo/redisLeak?count=1000'
# 使用配套监控脚本
/bin/bash redis_leak_monitor.sh
请输入压测目标(redisLeak|redisLeakok):
===== Redis内存泄漏压测+监控脚本 [Ctrl+C 停止] =====
默认测试 redisLeak 时,课件观察到 key 数量持续上涨。原因是业务不断写新 key,没有过期或清理策略,形成数据无界增长;这与 Redis 分配器本身泄漏是不同问题。
为临时数据设置合适 TTL,并检查写入和过期是否符合业务语义。固定 key 覆盖、容量上限及主动清理也可按数据用途选择;不应对所有持久业务数据一律设过期。
修复后使用相应接口:
[root@ubuntu24-13 ~]# /bin/bash redis_leak_monitor.sh
请输入压测目标(redisLeak|redisLeakok): redisLeakok
===== Redis内存泄漏压测+监控脚本 [Ctrl+C 停止] =====
预期在持续写入与到期删除达到平衡后,key 数量和有效数据内存趋于稳定。还应观察实际 Redis 内存、过期数和错误,不能只看 key 数。该调用需要配套脚本。
5.7 I/O 日志膨胀
复现与检查
while true
do
curl -s 'http://127.0.0.1:8080/demo/ioHeavy?loopCount=100000' >/dev/null
sleep 0.3
done
另开终端观察:
du -sh /data/ruoyi/logs/
ls -lh /data/ruoyi/logs/
大量写日志且没有轮转/保留策略,会造成磁盘占用增长,并可能增加 I/O 压力。先减少不必要的业务日志,再配置轮转。
logrotate 配置
cat > /etc/logrotate.d/ruoyi <<'EOF'
/data/ruoyi/logs/access.log {
size 500M
rotate 7
dateext
dateformat -%Y%m%d%H%M%S
compress
delaycompress
missingok
copytruncate
}
EOF
| 参数 | 作用 |
|---|---|
size 500M | logrotate 运行检查时,达到指定大小条件则轮转,不是持续实时监听 |
rotate 7 | 保留 7 份轮转归档,不等于固定保留 7 天 |
dateext | 归档文件附加日期后缀 |
dateformat -%Y%m%d%H%M%S | 日期精确到秒,减少多次轮转时同名冲突;同一秒仍可能冲突 |
compress | 压缩归档 |
delaycompress | 最近一份归档暂不压缩,到下一次轮转再压缩 |
missingok | 原日志不存在时不报错 |
copytruncate | 复制后截断原文件,应用无需重新打开文件,但复制与截断窗口可能丢日志 |
如果应用支持日志框架内置轮转,或支持收到信号后重新打开日志,应优先结合应用能力设计;copytruncate 不是无损保证。
验证和调度
# 调试模式,不实际执行轮转
logrotate -d /etc/logrotate.d/ruoyi
# 强制执行一次实际轮转
logrotate -f /etc/logrotate.d/ruoyi
# 查看结果
du -sh /data/ruoyi/logs/
ls -lh /data/ruoyi/logs/
先检查已有调度,避免覆盖系统自带的 /etc/cron.daily/logrotate 或重复创建定时任务:
systemctl list-timers --all | grep logrotate
ls -l /etc/cron.daily/logrotate
如果没有现成调度,并且需要按分钟检查大小,可新建独立 cron 文件:
cat > /etc/cron.d/ruoyi-logrotate <<'EOF'
* * * * * root /usr/sbin/logrotate /etc/logrotate.d/ruoyi
EOF
同一配置不必同时由多套调度重复执行。按分钟检查仍可能超过 500M,超出量由写入速度决定;需更及时地控制大小时,可使用应用日志滚动机制,或设计带并发和失败处理的监听方案。不能只设置 size 却只每天执行一次,就认为日志会在恰好 500M 时切割。
6. 内存压力、进程内存与 OOM
6.1 判断系统是否缺内存
free -h
grep -E 'MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab|SReclaimable|SUnreclaim' /proc/meminfo
vmstat -w 1 10
pidstat -r 1 10
| 指标 | 含义与判断 |
|---|---|
MemAvailable | 内核估算的不发生交换即可供新应用使用的内存;比只看 MemFree 更有用 |
Cached、SReclaimable | 文件缓存和可回收内核缓存的一部分;回收有成本,也并非都能立即释放 |
Dirty、Writeback | 待写回和正在写回的内存;持续积压时结合写入延迟分析 |
SUnreclaim | 不可回收 Slab,占用持续增长时进一步检查内核对象 |
si/so | 交换活动速率;已使用 swap 不等于当前正在频繁交换 |
majflt/s | 需要 I/O 的重大缺页速率;可能涉及文件页或交换页,不单独等于 swap-in |
低 MemAvailable、持续回收/交换、内存 PSI 上升、请求延迟增加同时出现时,更能说明内存压力。不能靠清理 Page Cache 作为日常“优化”;缓存被丢弃后重新读取可能加重 I/O。
6.2 RSS、PSS 与 JVM 堆
# 将 33500 替换为目标进程的实际 PID
TARGET_PID=33500
ps -p "$TARGET_PID" -o pid,ppid,etimes,%cpu,%mem,rss,vsz,cmd
cat "/proc/$TARGET_PID/status"
cat "/proc/$TARGET_PID/smaps_rollup"
pmap -x "$TARGET_PID"
- RSS 是驻留物理内存,共享页会在多个进程中重复计算。
- PSS 按共享比例分摊页面,汇总多个进程时更便于估算占用。
- VSZ/VIRT 是虚拟地址空间,不能直接当作物理内存。
- JVM RSS 还包含线程栈、元空间、直接内存、代码缓存和本地库等,不等于 Java 堆。
smaps_rollup 的可用性与权限依内核及运行环境而定。跟踪占用趋势时应同时记录请求量、线程数、缓存规模和进程运行时长;一次 RSS 较高不足以证明泄漏。
6.3 区分三种 OOM
journalctl -k --since '-1 hour' --no-pager | grep -Ei 'out of memory|oom|killed process|memory cgroup'
journalctl -u ruoyi-admin.service --since '-1 hour' --no-pager
| 类型 | 关键证据 | 排查重点 |
|---|---|---|
| 内核全局 OOM | 内核 OOM 上下文及 Killed process | 整机内存、回收、交换、内核占用及其他进程 |
| cgroup OOM | 对应 cgroup 的 memory.events、限制及内核日志 | 容器/服务限额、实际工作集、同组进程 |
| JVM OOM | 应用日志中的 OutOfMemoryError 及具体类型 | 堆、直接内存、元空间或线程创建限制 |
memory.events 默认包含子 cgroup 的事件,需结合 memory.events.local 和实际进程归属区分来源。oom_kill 增长说明统计范围内有进程被 OOM killer 杀死,还需结合内核日志区分触发背景。容器退出码 137 只表明收到 SIGKILL,不能单独证明 OOM。
Java 可在启动参数中配置:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/ruoyi/dumps
目录需提前创建并授予服务用户写权限,同时预留磁盘空间。Heap dump 可能很大并包含业务数据;内核直接 SIGKILL 进程时,JVM 通常没有机会生成 dump。
6.4 PSI:观察资源等待造成的停顿
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io
典型输出格式:
some avg10=2.50 avg60=1.20 avg300=0.40 total=12345678
full avg10=0.30 avg60=0.10 avg300=0.02 total=345678
some 表示至少有任务因该资源停顿;内存和 I/O 的 full 表示所有非空闲任务同时停顿的时间比例。avg10/60/300 是对应窗口的平均停顿百分比,total 是累计停顿微秒数。整机 CPU 的 full 不按内存、I/O 的方式解读,具体字段取决于内核版本。
PSI 反映等待影响,不直接指出哪个进程有问题。内存 PSI 升高时继续检查回收、缺页、swap 和 cgroup;I/O PSI 升高时检查设备、文件系统及阻塞任务。文件不存在时,先确认内核 PSI 支持和启动配置。
7. 磁盘空间、inode 与文件系统故障
7.1 容量用尽和 inode 用尽
df -hT /data
df -i /data
findmnt -T /data
sudo du -xhd1 /data | sort -h
df -hT 看文件系统空间,df -i 看 inode。大量小文件可能耗尽 inode,即使剩余容量充足也无法创建文件。du -x 不跨越其他文件系统,避免把另一个挂载点混进统计。
du 会遍历目录,大目录排查先限定范围,不要频繁扫描整盘。如果容量和 inode 都充足但写入仍报 Disk quota exceeded,检查用户、组或项目配额,而不是继续删除文件。
7.2 文件删除后空间没有释放
sudo lsof -nP +L1
常见原因是文件已从目录中删除,但进程仍持有打开的 fd。确认大文件、所属进程和设备后,让应用按支持的方式重新打开日志,或在允许中断的情况下重启对应服务。
不要直接清空不明用途的 /proc/PID/fd/N。数据库文件、映射文件和正在写入的日志有不同语义,错误截断可能损坏数据。容器环境还要确认检查的是同一个挂载命名空间和数据卷。
df/du 的差异也可能来自文件系统元数据、保留空间、快照或挂载点遮蔽,不能仅凭数值不同就断定是已删除文件。
7.3 只读文件系统与设备错误
findmnt -T /data -o TARGET,SOURCE,FSTYPE,OPTIONS
journalctl -k --since '-1 hour' --no-pager | grep -Ei 'I/O error|read-only|EXT4-fs error|XFS.*error|nvme|reset|timeout'
lsblk -f
出现 Read-only file system 时先确认挂载选项和内核错误。文件系统可能因设备或一致性错误转为只读;直接强制重新挂为可写可能扩大损坏。
检查物理盘时可使用适用的 SMART/NVMe 工具;虚拟机和云盘不一定暴露完整硬件信息,需要同时检查宿主机或云平台事件。fsck 等修复工具必须按文件系统要求,在卸载或专用维护环境下执行。
7.4 D 状态与慢存储
ps -eo pid,ppid,stat,wchan:32,comm | awk 'NR==1 || $3 ~ /^D/'
iostat -xz -y 1 10
cat /proc/pressure/io
# 替换为已确认的阻塞进程 PID
sudo cat /proc/33500/stack
D 状态可能涉及块设备、NFS 或其他内核等待。结合 wchan、内核栈、设备日志定位等待点;iostat 没有明显本地磁盘异常,也不能排除远端文件系统问题。对 NFS 可继续检查挂载状态、nfsiostat 和服务端情况。
8. 服务启动失败、异常退出与资源限制
8.1 从 systemd 找失败原因
systemctl status ruoyi-admin.service --no-pager -l
journalctl -u ruoyi-admin.service -b --since '-30 min' --no-pager -n 200
systemctl cat ruoyi-admin.service
systemctl show ruoyi-admin.service \
-p Result -p ExecMainCode -p ExecMainStatus -p NRestarts \
-p User -p Group -p WorkingDirectory -p LimitNOFILE -p TasksMax
ss -ltnp '( sport = :8080 )'
| 线索 | 检查方向 |
|---|---|
203/EXEC | 启动程序路径、可执行权限、解释器、挂载 noexec 及安全策略 |
200/CHDIR | WorkingDirectory 是否存在,服务用户能否进入 |
Address already in use | 监听者是否为旧实例或其他服务,先确认再处理 |
Permission denied | 文件和父目录权限、服务用户、SELinux/AppArmor、systemd 沙箱限制 |
start-limit-hit | 反复启动失败;先查首个错误,修复后再清除失败状态 |
Too many open files | 目标进程 fd 数量、实际软硬限制和资源泄漏 |
Resource temporarily unavailable | 线程/进程限制、TasksMax、pids.max,以及内存是否不足 |
Shell 中 ulimit 的结果不能代替服务进程实际限制。修改 unit 的 drop-in 后需执行 systemctl daemon-reload;哪些参数需要重启服务才能生效,应按具体配置确认。
8.2 崩溃和 core dump
coredumpctl list
coredumpctl info 33500
这些命令适用于已配置 systemd-coredump 的系统;其他系统可能由 apport 或 kernel.core_pattern 指定的处理程序接收。没有记录不代表没有崩溃,也可能是 core 限制、处理器配置或磁盘限制导致未保存。
分析本地程序崩溃需要匹配的可执行文件、共享库和调试符号。Java 还要检查 hs_err_pid*.log;普通应用 OOM、SIGKILL 和原生崩溃应分别处理。
9. Docker 与 cgroup v2 排障
9.1 容器状态与宿主机进程
# 将 halo-halo-1 替换为目标容器名
TARGET_CONTAINER=halo-halo-1
docker stats --no-stream "$TARGET_CONTAINER"
docker inspect "$TARGET_CONTAINER" --format '{{json .State}}'
docker inspect "$TARGET_CONTAINER" --format '{{json .HostConfig}}'
docker logs --since 15m --tail 200 "$TARGET_CONTAINER"
关注退出原因、重启次数、OOMKilled、CPU/内存限制及挂载。HostConfig 和日志可能包含应用配置,不应直接公开。
docker stats 的内存值可能按平台和版本扣除部分缓存,与 cgroup 的 memory.current 不完全相同。容器 CPU 的 100% 通常约对应一个逻辑 CPU,不能与整机归一化百分比混用。
9.2 查找实际 cgroup
以下命令在 Linux 宿主机执行,适用于统一的 cgroup v2,且挂载点为 /sys/fs/cgroup:
stat -fc %T /sys/fs/cgroup
TARGET_PID=$(docker inspect -f '{{.State.Pid}}' "$TARGET_CONTAINER")
test "$TARGET_PID" -gt 0 || { echo '容器未运行'; exit 1; }
CGROUP_REL=$(awk -F: '$1=="0" {print $3}' "/proc/$TARGET_PID/cgroup")
CGROUP_DIR="/sys/fs/cgroup$CGROUP_REL"
printf '%s\n' "$CGROUP_DIR"
cat "$CGROUP_DIR/cpu.max"
cat "$CGROUP_DIR/cpu.stat"
cat "$CGROUP_DIR/cpuset.cpus.effective"
cat "$CGROUP_DIR/memory.current"
cat "$CGROUP_DIR/memory.max"
cat "$CGROUP_DIR/memory.events"
cat "$CGROUP_DIR/pids.current"
cat "$CGROUP_DIR/pids.max"
stat 输出 cgroup2fs 才按本节路径解释;cgroup v1 的文件名与控制器路径不同。容器内的 /proc/PID/cgroup 可能被命名空间虚拟化,不能直接代替宿主机路径。
9.3 CPU 配额限流
cpu.max:
100000 100000
前一个数是每周期允许使用的 CPU 时间,后一个数是周期,单位均为微秒。示例配额约等于一个 CPU 的持续算力;max 100000 表示本层没有 CPU 带宽配额。可用 CPU 还受 cpuset 和祖先 cgroup 共同限制。
间隔采样:
cat "$CGROUP_DIR/cpu.stat"
sleep 10
cat "$CGROUP_DIR/cpu.stat"
nr_throttled 和 throttled_usec 的增量用于判断是否发生限流。不能用开机累计值直接证明当前限流,也不能把节流累计时间简单当作请求延迟。限流与请求排队同时增长时,再评估配额、并发和计算成本。
9.4 内存上限与压力
memory.current 是组内当前内存用量,memory.max 是硬上限,max 表示本层未设该上限;memory.high 可触发回收和节流,而不直接作为 OOM kill 上限。祖先 cgroup 仍可能施加更严格的约束。
cat "$CGROUP_DIR/memory.high"
cat "$CGROUP_DIR/memory.stat"
cat "$CGROUP_DIR/memory.pressure"
cat "$CGROUP_DIR/io.pressure"
查看 memory.events 的 high、max、oom、oom_kill 增量,再与内核日志、重启时间和业务曲线对齐。不要仅把 JVM 的 -Xmx 设置成容器内存上限,还需给堆外内存、线程栈及运行时留出空间。
10. DNS、TCP、TLS 与 HTTP 分层定位
10.1 DNS 与路由
TARGET_HOST=www.mrwnderboy.top
getent ahosts "$TARGET_HOST"
dig +short A "$TARGET_HOST"
dig +short AAAA "$TARGET_HOST"
ip route
ip -6 route
getent 走系统名称解析链,可能受 /etc/hosts 和 NSS 配置影响;dig 直接查询 DNS,两者结果可能不同。存在 AAAA 记录时分别测试 curl -4 与 curl -6,避免只验证 IPv4 后就判定客户端链路正常。
代理/CDN 域名要分别检查公共入口和源站。不同递归解析器返回结果不同时,进一步核对权威记录、TTL 和代理状态。
10.2 分解 HTTP 请求耗时
curl -sS -o /dev/null --connect-timeout 5 --max-time 20 \
-w 'code=%{http_code} ip=%{remote_ip} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} first_byte=%{time_starttransfer} total=%{time_total}\n' \
"https://$TARGET_HOST/"
这些时间以秒为单位,都是从请求开始累计计算:
- TCP 建连耗时可近似看
time_connect - time_namelookup。 - 新建 HTTPS 连接的 TLS 耗时可近似看
time_appconnect - time_connect。 time_starttransfer包含前面的 DNS、建连、握手等时间,并非纯服务端处理时间。- HTTP 状态码、响应内容与耗时应一起检查;拿到 200 不等于业务结果正确。
以上差值适用于普通直连、新建连接场景。代理、连接复用和重定向会改变解释方式;需要跟随重定向时单独检查每一跳,不把全链路耗时归给一个后端。
10.3 对比源站,保留 Host 与 SNI
# 设置为实际源站 IP
ORIGIN_IP=154.219.105.150
curl --resolve "$TARGET_HOST:443:$ORIGIN_IP" \
--connect-timeout 5 --max-time 20 -v "https://$TARGET_HOST/"
openssl s_client -connect "$ORIGIN_IP:443" \
-servername "$TARGET_HOST" -verify_hostname "$TARGET_HOST" \
-verify_return_error </dev/null
--resolve 固定本次请求连接的 IP,同时保留域名对应的 Host、SNI 和证书校验。直接请求 https://IP/ 可能命中错误虚拟主机,不能作为等价测试。使用代理环境时还要确认请求是否实际经过代理;直连对照应明确设置绕过代理。
TLS 错误分别检查证书有效期、域名匹配、完整证书链、SNI、协议和密码套件。源站握手成功只证明源站链路,代理入口仍需独立验证。关闭证书校验只会隐藏部分错误,不能作为修复。
10.4 TCP 重传、丢包与队列
ss -tin
ss -ltn
ip -s link
nstat -az | grep -E 'TcpRetransSegs|TCPSynRetrans|ListenOverflows|ListenDrops'
sar -n TCP,ETCP 1 10
nstat -az 是绝对计数,应前后采样计算增量。重传可能来自丢包、拥塞和其他链路因素,需结合具体连接与抓包;网卡丢包和 TCP 重传不一定一一对应。
监听队列溢出时检查 accept 速度、应用线程、连接峰值和 backlog,不能只把队列上限调大。对于 NAT/连接跟踪场景,可再检查 nf_conntrack_count 与 nf_conntrack_max;这些参数仅在对应模块和路径存在时可用。
11. CPU 不高但服务很慢
11.1 从等待和排队入手
| 等待类型 | 证据 | 后续检查 |
|---|---|---|
| Java 锁竞争 | 多次线程栈中的相同锁和阻塞线程 | 锁持有者、临界区耗时、锁内 I/O |
| 线程池饱和 | 活跃线程接近上限,任务队列持续增长 | 到达速率、任务耗时、拒绝次数 |
| 数据库连接池耗尽 | 等待连接数、获取连接耗时及超时 | 慢 SQL、长事务、连接释放、数据库容量 |
| 下游调用慢 | 客户端耗时、超时、链路追踪 | 下游服务、连接复用、重试放大 |
| I/O 阻塞 | D 状态、I/O PSI、进程/内核栈 | 文件系统、本地盘和远端存储 |
| CPU 配额不足 | cgroup 限流增量、请求排队 | CPU 配额、并发及祖先限制 |
平均在途请求数近似等于请求速率乘以平均停留时间,这是稳定条件下的 Little 定律。下游变慢时,即使入口速率不变,在途请求也会增加,进而占满线程和连接池;单纯扩大池子可能进一步压垮依赖。
11.2 将高 CPU 线程对应到 Java 栈
TARGET_PID=33500
pidstat -t -u -p "$TARGET_PID" 1 5
# 将 33542 替换为上一步观察到的高 CPU 线程 TID
TARGET_TID=33542
printf 'nid=0x%x\n' "$TARGET_TID"
jcmd "$TARGET_PID" Thread.print -l > /tmp/ruoyi-threads-1.txt
sleep 5
jcmd "$TARGET_PID" Thread.print -l > /tmp/ruoyi-threads-2.txt
在两份线程栈中查找对应十六进制 nid,关联线程状态、方法和锁。使用与目标 JVM 兼容的 JDK 工具,以有权附着目标进程的用户执行;容器场景需保证 PID 命名空间和工具所见 PID 一致。
RUNNABLE 不保证线程正在消耗 CPU,也可能处于原生 I/O 调用;WAITING 可能只是正常的线程池空闲。必须把同一时段线程 CPU、栈及业务请求联系起来。
11.3 JFR 与火焰图
对支持 JFR 的 JDK,可限时采集:
# 路径由目标 JVM 写入,需在其文件系统内可写
jcmd "$TARGET_PID" JFR.start name=incident settings=profile \
duration=60s filename=/tmp/ruoyi-incident.jfr
jcmd "$TARGET_PID" JFR.check
使用 JDK Mission Control 打开记录,检查 CPU 样本、分配、GC、锁和 I/O 事件。具体可用事件及阈值由 JDK 和录制配置决定,不能保证每次等待都会被记录。
已安装 async-profiler 且目标环境支持对应事件时,可分别采集 CPU 与 wall-clock 火焰图:
asprof -d 30 -e cpu -f /tmp/ruoyi-cpu.html "$TARGET_PID"
asprof -d 30 -e wall -f /tmp/ruoyi-wall.html "$TARGET_PID"
火焰图横向宽度表示样本权重,纵向表示调用栈深度,不是请求时间线。CPU 图关注运行热点,wall-clock 图可帮助观察等待路径;两者不能使用相同口径解释。采样只覆盖采集时段,需留意丢失样本、未知符号和权限限制。
12. 持续监控、历史回溯与告警
12.1 让 sar 真正留下历史
# 查看本机实际存在的服务、定时器和 cron
systemctl list-unit-files 'sysstat*'
systemctl list-timers --all | grep -i sysstat
ls -l /etc/cron.d/sysstat /etc/default/sysstat /etc/sysstat/sysstat /etc/sysconfig/sysstat 2>/dev/null
ls -lh /var/log/sysstat/ /var/log/sa/ 2>/dev/null
Debian/Ubuntu 部分版本需在 /etc/default/sysstat 中设置 ENABLED="true";其他版本可能由 systemd 定时器管理。按已安装软件包的配置启用采集,不要同时创建重复任务。
确认采集周期、保存目录和保留天数后,至少等待一个周期,验证历史文件大小和记录时间确实更新。十分钟采集无法还原持续数秒的尖峰,短时故障需要更高频指标或针对性记录。
# 文件路径及日期按故障当天调整
sar -u -f /var/log/sysstat/sa08 -s 01:00:00 -e 02:00:00
sar -q -f /var/log/sysstat/sa08 -s 01:00:00 -e 02:00:00
sar -r -f /var/log/sysstat/sa08 -s 01:00:00 -e 02:00:00
sar -d -p -f /var/log/sysstat/sa08 -s 01:00:00 -e 02:00:00
sar -n DEV -f /var/log/sysstat/sa08 -s 01:00:00 -e 02:00:00
历史文件可能只保留日号,也可能包含完整日期,读取前先确认文件对应的年月日。日志、应用、监控系统应校准时间,跨时区排查时统一换算。
12.2 指标与采集层次
| 层次 | 应采集的指标 |
|---|---|
| 业务入口 | 请求速率、失败率、p50/p95/p99、超时、在途请求 |
| CPU | 各核使用率、运行队列、steal、中断、cgroup 限流 |
| 内存 | 可用内存、交换活动、PSI、OOM、容器当前用量与上限 |
| 存储 | 可用容量、inode、读写延迟、吞吐、队列和错误 |
| 网络 | 流量、错误、丢包、重传、连接状态与监听队列溢出 |
| 应用 | 线程池、连接池、GC、堆和堆外、缓存、依赖调用 |
| 采集系统 | 目标是否可抓取、数据是否缺失、采集耗时、磁盘和留存 |
可用 Prometheus + node_exporter 采集主机指标,配合应用指标和 Grafana 展示;容器限额与使用量需增加适用的容器/cgroup 指标采集。已有 Zabbix 的环境也可以按相同层次组织监控,无需同时部署多套相同功能。
node_exporter 不会自动提供业务接口分位延迟、JVM 全部指标或每个进程的历史数据。应用需暴露相应计数器和直方图,进程级长期数据则需要额外采集方案。
12.3 告警规则示例
以下规则使用 Prometheus 和 node_exporter 常见指标,阈值是起点,应根据业务基线调整:
groups:
- name: linux-host
rules:
- alert: HostMemoryAvailableLow
expr: node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 0.10
for: 10m
labels:
severity: warning
annotations:
summary: '主机 {{ $labels.instance }} 可用内存持续偏低'
description: '检查内存压力、交换活动、进程和容器用量。'
- alert: HostFilesystemSpaceLow
expr: |
(
node_filesystem_avail_bytes{fstype!~"tmpfs|devtmpfs|squashfs|overlay"}
/ node_filesystem_size_bytes{fstype!~"tmpfs|devtmpfs|squashfs|overlay"}
< 0.10
)
and on(instance, job, device, mountpoint)
(node_filesystem_readonly == 0)
for: 10m
labels:
severity: warning
annotations:
summary: '主机 {{ $labels.instance }} 挂载点 {{ $labels.mountpoint }} 可用空间不足'
description: '检查增长速度、inode、日志和已删除但仍打开的文件。'
在主机上采集真实文件系统时,示例排除了常见伪文件系统和 overlay;实际纳管的容器层、远端挂载和其他类型需要另行配置。只读挂载应设独立告警,不能因为本规则过滤掉就忽略。
规则保存为实际配置文件后,部署前执行:
promtool check rules /etc/prometheus/rules/linux-host.yml
内存低告警需要结合 PSI 和回收判断;磁盘告警同时考虑剩余绝对容量、增长速度和预计耗尽时间。业务失败率与延迟应独立告警,低流量场景增加最小请求量条件,避免单次失败被放大为高失败率。
12.4 告警与复测的判定
告警需包含目标实例、资源或接口、持续时间、关键数值和排查入口,并配置分组、抑制及通知路由。目标不可抓取、数据缺失要单独告警,不能让“没有数据”被当成健康。
恢复以指标持续回到可接受范围且业务验证通过为依据。优化前后使用相同的数据范围、并发、请求模型和采样窗口;先预热,多次测量,确认压测端未成为瓶颈。
| 对照项 | 需要记录的结果 |
|---|---|
| 正确性 | 响应内容、返回行数、完整性和业务校验 |
| 服务能力 | 实际请求速率、成功率、超时率 |
| 延迟 | p50、p95、p99、最大值及测试时长 |
| CPU | 总用量、每请求 CPU 成本、线程热点、限流 |
| 内存 | 峰值、稳定工作集、回收/交换、GC 和 OOM |
| 数据库 | 执行计划、扫描行数、查询耗时、连接等待 |
| 存储与网络 | 延迟、队列、吞吐、重传和错误 |
线程池、缓存或超时参数每次只改一项,保存原值和回退方式。复盘保留故障时间线、影响、证据、根因、恢复操作及防止复发的具体措施。
13. 不依赖若依源码的最小实验
以下实验在独立测试机执行,不运行在生产主机上。
13.1 单线程 CPU 热点
# 前台运行,20 秒后结束,也可以 Ctrl+C 停止
timeout 20s python3 -c 'while True: pass'
另一个终端观察 mpstat -P ALL 1、pidstat -u 1。预期单进程约占一个 CPU,进程可能在不同 CPU 之间迁移;整机平均利用率受总核数和配额影响。不要仅凭整机利用率不高就排除单线程瓶颈。
13.2 已删除文件仍占空间
先确认 /var/tmp 对应文件系统有至少 64 MiB 可用空间:
df -hT /var/tmp
python3 - <<'PY'
import os
import tempfile
with tempfile.NamedTemporaryFile(prefix='deleted-file-demo-', dir='/var/tmp', delete=False) as f:
path = f.name
try:
for _ in range(64):
f.write(b'\0' * 1024 * 1024)
f.flush()
os.fsync(f.fileno())
os.unlink(path)
print(f'PID={os.getpid()} FD={f.fileno()},文件已删除但仍打开', flush=True)
input('另一个终端检查完后按 Enter 关闭文件:')
finally:
if os.path.exists(path):
os.unlink(path)
PY
另一个终端使用 lsof -nP +L1,按上一步 PID 确认 deleted 文件。按 Enter 关闭文件后,该记录应消失;文件系统的释放和统计更新可能存在延迟。启用压缩、去重等特性的文件系统,实际占用未必等于写入的逻辑大小。
13.3 容器 CPU 配额
使用已存在或可正常拉取的 python:3.12-alpine 测试镜像:
docker run --rm --name cpu-quota-demo --cpus=0.5 \
python:3.12-alpine python -c 'import time; end=time.monotonic()+30
while time.monotonic()<end: pass'
在运行的 30 秒内,另一个终端观察 docker stats cpu-quota-demo,并按第 9 节方法查找 cgroup、采样 cpu.stat。没有更严格的祖先限制和其他瓶颈时,预期使用量约为半个 CPU,限流计数持续增加。容器结束后自动删除,cgroup 路径也随之消失。
评论