线上服务响应变慢、load 飙高、进程被 OOM Killer 干掉——这些问题每个运维都会遇到。性能排查不是靠猜,而是靠方法论和工具链。本文基于 Brendan Gregg 提出的 USE 方法论(Utilization 利用率 / Saturation 饱和度 / Errors 错误数),从 CPU、内存、磁盘 IO、网络四个维度,给出一套完整的分层排查流程。
USE 方法论的核心思路:对每一类系统资源,依次检查三个指标:
实测下来,按这个框架排查,90% 以上的性能问题能在 15 分钟内定位到根因。
# 检查系统版本cat /etc/os-release# 检查内核版本(perf 和 bcc-tools 对内核版本有要求)uname -r# 检查资源状况free -hdf -hnproc# CentOS 7yum install -y sysstat perf iotop dstat strace lsof net-tools# CentOS 8 / Rocky Linux 8dnf install -y sysstat perf iotop dstat strace lsof# 安装 bcc-tools(CentOS 8)dnf install -y bcc-tools bcc-tools-doc# 启动 sysstat 数据采集(默认每 10 分钟采集一次)systemctl enable --now sysstatapt updateapt install -y sysstat linux-tools-$(uname -r) iotop dstat strace lsof# 安装 bcc-toolsapt install -y bpfcc-tools linux-headers-$(uname -r)# 启用 sysstat 数据采集sed -i 's/ENABLED="false"/ENABLED="true"/' /etc/default/sysstatsystemctl enable --now sysstat# 逐个确认mpstat -Viostat -Vpidstat -Vperf --version注意:perf 版本必须和内核版本匹配。如果 perf --version 报错,检查是否安装了对应内核版本的 linux-tools 包。Ubuntu 上经常因为内核升级后没装新的 linux-tools 导致 perf 不可用。
拿到一台"有问题"的服务器,先用 60 秒做全局概览,确定瓶颈方向。这套流程来自 Netflix 的 Brendan Gregg,我们团队在此基础上做了调整。
uptime输出示例:
14:23:15 up 45 days, 3:12, 2 users, load average: 28.53, 18.74, 12.01解读要点:
top -bn1 | head -20重点关注第一屏的几个指标:
top - 14:23:15 up 45 days, 3:12, 2 users, load average: 28.53, 18.74, 12.01Tasks: 312 total, 3 running, 308 sleeping, 0 stopped, 1 zombie%Cpu(s): 72.3 us, 12.1 sy, 0.0 ni, 8.2 id, 5.8 wa, 0.0 hi, 1.6 si, 0.0 stMiB Mem : 15921.4 total, 312.8 free, 12847.2 used, 2761.4 buff/cacheMiB Swap: 4096.0 total, 2048.0 free, 2048.0 used. 2156.3 avail Mem关键指标解读:
us | ||
sy | ||
wa | ||
st | ||
si | ||
vmstat 1 10输出示例:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 8 3 204800 320480 12048 2832640 2 12 128 2048 8521 15234 72 12 8 6 2 6 5 204800 318240 12048 2832640 0 0 0 4096 9012 16432 68 15 5 10 2关键列解读:
r:运行队列长度。大于 CPU 核数说明 CPU 饱和b:不可中断睡眠进程数(通常是 IO 等待)。持续 > 0 说明有 IO 瓶颈si/so:swap in/out。持续 > 0 说明内存不足,正在使用 swapbi/bo:块设备读写(KB/s)in:中断数/秒cs:上下文切换数/秒。实测超过 10 万/秒需要关注wa:IO 等待百分比dstat -tcmsdnl --top-cpu --top-io 5dstat 的优势是把 CPU、内存、磁盘、网络整合在一个视图里,颜色高亮,适合实时观察。参数说明:
-t:显示时间戳-c:CPU 统计-m:内存统计-s:swap 统计-d:磁盘统计-n:网络统计-l:load average--top-cpu:显示 CPU 占用最高的进程--top-io:显示 IO 最高的进程当全局概览发现 CPU 是瓶颈时(us+sy 高、wa 低、r 值大),进入 CPU 深入排查。
mpstat -P ALL 1 5输出示例:
CPU %usr %nice %sys %iowait %irq %soft %steal %idleall 72.31 0.00 12.08 5.82 0.00 1.58 0.00 8.21 0 98.02 0.00 1.98 0.00 0.00 0.00 0.00 0.00 1 45.10 0.00 8.82 2.94 0.00 0.00 0.00 43.14 2 95.05 0.00 3.96 0.00 0.00 0.99 0.00 0.00 3 51.49 0.00 33.66 20.79 0.00 5.94 0.00 0.00排查要点:
/proc/interrupts)%sys 某个核特别高 → 可能是锁竞争或系统调用密集%soft 某个核特别高 → 网络软中断集中在一个核上,需要配置 RPS/RFS 或 irqbalance# 每秒采样,显示 CPU 使用率pidstat -u 1 5# 只看 CPU 使用率 > 10% 的进程pidstat -u 1 5 | awk 'NR<=3 || $8>10'# 查看指定进程的线程级 CPU 使用pidstat -u -t -p <PID> 1 5# 实时查看系统级 CPU 热点perf top# 只看指定进程perf top -p <PID># 只看用户态函数perf top -p <PID> --no-children -U注意:perf top 默认需要 root 权限。如果普通用户需要使用,调整内核参数:
# 0=不限制 1=限制内核态 2=限制内核态+用户态# 生产环境建议设为 1,不要设为 0echo 1 > /proc/sys/kernel/perf_event_paranoid# 持久化echo'kernel.perf_event_paranoid = 1' >> /etc/sysctl.d/99-perf.confsysctl -p /etc/sysctl.d/99-perf.conf# 对整个系统采样 30 秒perf record -ag -- sleep 30# 对指定进程采样 30 秒perf record -p <PID> -g -- sleep 30# 查看报告perf report --no-children --sort comm,dso,symbol# 生成火焰图(需要 FlameGraph 工具)perf script | /opt/FlameGraph/stackcollapse-perf.pl | /opt/FlameGraph/flamegraph.pl > cpu_flame.svg火焰图安装:
git clone https://github.com/brendangregg/FlameGraph.git /opt/FlameGraph火焰图的解读:
# 系统级上下文切换统计vmstat 1 | awk '{print $12, $13}'# 进程级上下文切换pidstat -w 1 5# cswch/s:自愿上下文切换(IO 等待等)# nvcswch/s:非自愿上下文切换(时间片用完被抢占)经验值:
当发现 swap 使用量增长、available 内存持续下降、或者进程被 OOM Killer 杀掉时,进入内存排查。
free -h输出示例:
total used free shared buff/cache availableMem: 15Gi 12Gi 312Mi 256Mi 2.7Gi 2.1GiSwap: 4.0Gi 2.0Gi 2.0Gi关键点:
# 查看关键内存指标cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Slab|SReclaimable|SUnreclaim|Dirty|Writeback|AnonPages|Mapped|Shmem|HugePages"重点关注:
Slab / SUnreclaim:内核 slab 分配器使用的内存,SUnreclaim 不可回收,如果持续增长可能是内核内存泄漏Dirty / Writeback:脏页和正在回写的页,过高说明 IO 跟不上AnonPages:匿名页(进程堆栈等),这是应用实际使用的内存HugePages_Total:大页配置,数据库场景常用# 按内存大小排序查看 slabslabtop -o | head -20# 持续监控slabtop -d 2如果 dentry 或 inode_cache 占用异常大,通常是文件系统缓存导致,可以手动释放:
# 释放 pagecache + dentries + inodesecho 3 > /proc/sys/vm/drop_caches# 生产环境建议只释放 dentries 和 inodesecho 2 > /proc/sys/vm/drop_caches这个操作会导致短暂的 IO 升高,业务高峰期慎用。
# 安装yum install -y smem # CentOSapt install -y smem # Ubuntu# 按 PSS 排序查看进程内存smem -rkt -s pss | head -20为什么用 PSS 而不是 RSS:
# 查看指定进程的内存映射pmap -x <PID> | tail -1# 查看进程内存详情cat /proc/<PID>/status | grep -E "VmSize|VmRSS|VmSwap|Threads"# 查看进程的 smaps 汇总cat /proc/<PID>/smaps_rollup# 查看 OOM 日志dmesg | grep -i "oom\|out of memory" | tail -20# 查看被 OOM 杀掉的进程dmesg | grep "Killed process"# 查看当前进程的 OOM 分数(分数越高越容易被杀)cat /proc/<PID>/oom_score# 调整 OOM 优先级(-1000 到 1000,-1000 表示永不 OOM)echo -1000 > /proc/<PID>/oom_score_adj当 %wa 偏高、vmstat 的 b 列持续 > 0、或者应用日志出现 IO 超时时,进入磁盘 IO 排查。
# 每秒采样,显示扩展统计,忽略无活动的设备iostat -xz 1 5输出示例:
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz svctm %utilsda 12.00 285.00 48.00 18432.00 0.00 42.00 0.00 12.84 1.25 8.52 2.43 4.00 64.67 3.12 92.80关键指标:
%util | ||
r_await / w_await | ||
aqu-sz | ||
svctm | ||
r/s + w/s | ||
rkB/s + wkB/s |
注意:%util 对于并行设备(SSD、RAID)可能不准确。SSD 的 %util 100% 不一定表示饱和,要结合 await 判断。
# 只显示有 IO 活动的进程iotop -oP# 批量模式,适合脚本采集iotop -oP -b -n 5 -d 1# 每秒采样进程 IOpidstat -d 1 5# 查看指定进程pidstat -d -p <PID> 1 5# 追踪 sda 设备 10 秒blktrace -d /dev/sda -o trace -w 10# 解析追踪数据blkparse -i trace.blktrace.0 -o trace.txt# 生成 IO 延迟分布btt -i trace.blktrace.0 -o btt_outputblktrace 会产生大量数据,生产环境短时间采样即可,不要长时间运行。
# 查看文件系统使用情况df -hT# 查看 inode 使用情况(inode 耗尽也会导致无法写入)df -i# 查看哪个目录占用空间最大du -sh /* 2>/dev/null | sort -rh | head -10# 查看已删除但未释放的文件(进程还在占用)lsof +L1当出现连接超时、丢包、带宽打满等问题时,进入网络排查。
# 连接状态汇总ss -s# 查看 TIME_WAIT 数量ss -tan state time-wait | wc -l# 查看 ESTABLISHED 连接,按目标端口统计ss -tn state established | awk '{print $4}' | awk -F: '{print $NF}' | sort | uniq -c | sort -rn | head -10# 查看监听端口ss -tlnp经验值:
# 实时查看网卡流量sar -n DEV 1 5# 查看网络错误统计sar -n EDEV 1 5# 查看 TCP 统计sar -n TCP,ETCP 1 5关键指标:
rxkB/s / txkB/s:收发带宽,对比网卡标称带宽rxpck/s / txpck/s:收发包数,万兆网卡实测小包极限约 1400 万 pps%ifutil:网卡利用率retrans/s:TCP 重传数,> 0 说明有丢包# 查看 TCP 相关计数器nstat -az | grep -i tcp# 重点关注nstat -az | grep -E "TcpRetransSegs|TcpExtTCPLostRetransmit|TcpExtListenOverflows|TcpExtListenDrops|TcpExtTCPAbortOnMemory"关键计数器:
TcpRetransSegs | ||
TcpExtListenOverflows | ||
TcpExtListenDrops | ||
TcpExtTCPAbortOnMemory |
# 抓取指定端口的包,保存到文件tcpdump -i eth0 port 80 -w /tmp/capture.pcap -c 10000# 抓取指定 IP 的包tcpdump -i eth0 host 10.0.0.1 -nn# 只看 TCP SYN 包(排查连接问题)tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0' -nn# 只看 TCP RST 包(排查连接被重置)tcpdump -i eth0 'tcp[tcpflags] & tcp-rst != 0' -nn注意:生产环境抓包一定要加 -c 限制包数量或 -G 限制时间,否则 pcap 文件会撑爆磁盘。我们团队的做法是最多抓 10 万个包或 60 秒。
# 查看网卡错误统计ethtool -S eth0 | grep -i error# 查看网卡丢包ethtool -S eth0 | grep -i drop# 查看网卡 ring buffer 大小ethtool -g eth0# 查看软中断统计(网络包处理)cat /proc/net/softnet_stat/proc/net/softnet_stat 每行对应一个 CPU 核,三列分别是:
netdev_budget 用完而退出的次数(> 0 说明 CPU 处理不过来)netdev_max_backlog)线上出问题时手忙脚乱敲命令容易遗漏信息。我们团队的做法是在每台机器上放一个快照脚本,出问题时一键执行,把所有关键数据收集到一个文件里,事后慢慢分析。
#!/bin/bash# 文件名:perf_snapshot.sh# 功能:一键收集系统性能快照,输出到指定目录# 用法:sudo bash perf_snapshot.sh [输出目录]set -euo pipefailOUTPUT_DIR="${1:-/tmp/perf_snapshot_$(date +%Y%m%d_%H%M%S)}"mkdir -p "$OUTPUT_DIR"log() {echo"[$(date '+%Y-%m-%d %H:%M:%S')] $*"}collect() {local name="$1"shiftlog"采集 $name ...""$@" > "$OUTPUT_DIR/$name.txt" 2>&1 || echo"采集 $name 失败: $?" >> "$OUTPUT_DIR/errors.log"}log"开始采集,输出目录: $OUTPUT_DIR"# 基础信息collect "uname" uname -acollect "uptime" uptimecollect "date" date '+%Y-%m-%d %H:%M:%S %Z'collect "hostname" hostname -f# CPU 相关collect "top_snapshot" bash -c "top -bn1 | head -50"collect "mpstat" mpstat -P ALL 1 5collect "pidstat_cpu" pidstat -u 1 5collect "pidstat_context_switch" pidstat -w 1 5# 内存相关collect "free" free -hcollect "meminfo" cat /proc/meminfocollect "slabtop" slabtop -ocollect "smem" bash -c "smem -rkt -s pss 2>/dev/null || echo 'smem not installed'"# 磁盘 IO 相关collect "iostat" iostat -xz 1 5collect "pidstat_io" pidstat -d 1 5collect "df" df -hTcollect "df_inode" df -i# 网络相关collect "ss_summary" ss -scollect "ss_established" bash -c "ss -tn state established | head -100"collect "ss_time_wait_count" bash -c "ss -tan state time-wait | wc -l"collect "ss_listen" ss -tlnpcollect "netstat_stats" bash -c "nstat -az 2>/dev/null || netstat -s"# vmstat 采样collect "vmstat" vmstat 1 10# dmesg 最近的错误collect "dmesg_errors" bash -c "dmesg -T 2>/dev/null | tail -100"collect "dmesg_oom" bash -c "dmesg | grep -i 'oom\|out of memory\|killed process' || echo 'No OOM events'"# 进程列表collect "ps_aux" bash -c "ps aux --sort=-%mem | head -30"collect "ps_d_state" bash -c "ps aux | awk '\$8~/D/' || echo 'No D state processes'"# 系统日志collect "journal_recent" bash -c "journalctl --since '10 minutes ago' --no-pager 2>/dev/null | tail -200 || tail -200 /var/log/syslog 2>/dev/null || echo 'No syslog access'"log"采集完成,文件列表:"ls -la "$OUTPUT_DIR/"# 打包tar czf "${OUTPUT_DIR}.tar.gz" -C "$(dirname "$OUTPUT_DIR")""$(basename "$OUTPUT_DIR")"log"已打包: ${OUTPUT_DIR}.tar.gz"log"大小: $(du -sh "${OUTPUT_DIR}.tar.gz" | awk '{print $1}')"用于在问题复现窗口期持续采集数据,每隔 N 秒记录一次关键指标。
#!/bin/bash# 文件名:perf_monitor.sh# 功能:持续采集性能数据,适合等待问题复现时使用# 用法:sudo bash perf_monitor.sh [采样间隔秒数] [持续时间秒数]set -euo pipefailINTERVAL="${1:-5}"DURATION="${2:-3600}"OUTPUT="/tmp/perf_monitor_$(date +%Y%m%d_%H%M%S).csv"echo"timestamp,load1,load5,load15,cpu_us,cpu_sy,cpu_wa,cpu_st,mem_used_pct,swap_used_mb,disk_util,net_rx_kb,net_tx_kb,tcp_estab,tcp_tw,context_switch,interrupts" > "$OUTPUT"END_TIME=$((SECONDS + DURATION))echo"开始监控,间隔 ${INTERVAL}s,持续 ${DURATION}s,输出: $OUTPUT"while [ $SECONDS -lt $END_TIME ]; do TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')# load averageread LOAD1 LOAD5 LOAD15 <<< $(awk '{print $1, $2, $3}' /proc/loadavg)# CPU(取 vmstat 第二行数据)read CPU_US CPU_SY CPU_WA CPU_ST <<< $(vmstat 1 2 | tail -1 | awk '{print $13, $14, $16, $17}')# 内存使用率 MEM_USED_PCT=$(free | awk '/Mem:/{printf "%.1f", ($3/$2)*100}')# swap 使用量(MB) SWAP_USED=$(free -m | awk '/Swap:/{print $3}')# 磁盘最大 %util DISK_UTIL=$(iostat -xz 1 2 | awk '/^[a-z]/{if($NF+0 > max) max=$NF+0} END{print max+0}')# 网络流量(取第一个非 lo 网卡)read NET_RX NET_TX <<< $(sar -n DEV 1 1 2>/dev/null | awk '/Average:/ && !/lo/ && !/IFACE/{print $5, $6; exit}' || echo"0 0")# TCP 连接数 TCP_ESTAB=$(ss -tn state established 2>/dev/null | wc -l) TCP_TW=$(ss -tan state time-wait 2>/dev/null | wc -l)# 上下文切换和中断read CS INTR <<< $(vmstat 1 2 | tail -1 | awk '{print $12, $11}')echo"$TIMESTAMP,$LOAD1,$LOAD5,$LOAD15,$CPU_US,$CPU_SY,$CPU_WA,$CPU_ST,$MEM_USED_PCT,$SWAP_USED,$DISK_UTIL,$NET_RX,$NET_TX,$TCP_ESTAB,$TCP_TW,$CS,$INTR" >> "$OUTPUT" sleep "$INTERVAL"doneecho"监控结束,数据文件: $OUTPUT"echo"共 $(wc -l < "$OUTPUT") 条记录"场景描述:线上 Java 服务突然 CPU 飙到 100%,接口响应超时。这是 Java 应用最常见的性能问题之一。
排查步骤:
# 第一步:确认是哪个 Java 进程top -bn1 | grep java# 输出:PID 12345 占用 CPU 398%(4 核机器)# 第二步:找到 CPU 最高的线程top -Hp 12345 -bn1 | head -20# 输出:线程 12378 占用 CPU 98%# 第三步:将线程 ID 转为 16 进制(jstack 用 16 进制)printf"%x\n" 12378# 输出:305a# 第四步:用 jstack 导出线程栈jstack 12345 > /tmp/jstack_12345.txt# 第五步:搜索对应线程grep -A 30 "nid=0x305a" /tmp/jstack_12345.txt实际输出:
"http-nio-8080-exec-15"#78 daemon prio=5 os_prio=0 tid=0x00007f8a3c012800 nid=0x305a runnable [0x00007f8a1c5f9000] java.lang.Thread.State: RUNNABLE at java.util.regex.Pattern$GroupHead.match(Pattern.java:4658) at java.util.regex.Pattern$Loop.match(Pattern.java:4785) at java.util.regex.Pattern$GroupTail.match(Pattern.java:4717) at java.util.regex.Pattern$Loop.matchInit(Pattern.java:4801) at java.util.regex.Pattern$Loop.match(Pattern.java:4785) ... at com.example.service.UserService.validateEmail(UserService.java:142)根因:正则表达式回溯导致 CPU 死循环(ReDoS)。validateEmail 方法里用了一个有缺陷的邮箱正则,遇到特定输入会触发指数级回溯。
解决方案:
进阶方法 — 用 perf + perf-map-agent 生成 Java 火焰图:
# 安装 perf-map-agent(需要 JAVA_HOME)git clone https://github.com/jvm-profiling-tools/perf-map-agent.gitcd perf-map-agent && cmake . && make# 生成 /tmp/perf-<pid>.map 符号映射文件bin/create-java-perf-map.sh 12345# 用 perf 采样perf record -p 12345 -g -F 99 -- sleep 30# 生成火焰图perf script | /opt/FlameGraph/stackcollapse-perf.pl | /opt/FlameGraph/flamegraph.pl > java_flame.svg场景描述:数据库服务器 iowait 持续 30%+,MySQL 慢查询增多,业务接口超时。
排查步骤:
# 第一步:确认 IO 瓶颈iostat -xz 1 3# 输出:sda %util 95%, w_await 45ms# 第二步:确认是 MySQL 导致的 IOiotop -oP -b -n 3 -d 1# 输出:mysqld 进程写入 180MB/s# 第三步:查看 MySQL 当前执行的查询mysql -e "SHOW PROCESSLIST\G" | grep -B5 "Query"# 第四步:查看慢查询日志tail -100 /var/log/mysql/slow.log# 第五步:分析慢查询(用 pt-query-digest)pt-query-digest /var/log/mysql/slow.log --since '1h' | head -50发现的问题:
-- 慢查询 TOP1:全表扫描,每次扫描 500 万行SELECT * FROM orders WHERE create_time > '2024-01-01'ANDstatus = 'pending';-- Rows_examined: 5000000, Query_time: 12.5s解决方案:
# 1. 添加复合索引mysql -e "ALTER TABLE orders ADD INDEX idx_status_create_time (status, create_time);"# 2. 调大 InnoDB Buffer Pool(当前 1GB,实际数据 8GB)# 编辑 /etc/mysql/mysql.conf.d/mysqld.cnf# innodb_buffer_pool_size = 6G (物理内存的 60-70%)# 3. 调整脏页刷新参数# innodb_io_capacity = 2000 (SSD 建议 2000-4000)# innodb_io_capacity_max = 4000# innodb_flush_neighbors = 0 (SSD 关闭邻近页刷新)# 4. 重启 MySQL 生效systemctl restart mysqld# 5. 验证效果iostat -xz 1 5# %util 从 95% 降到 15%,w_await 从 45ms 降到 2ms场景描述:服务器 load average 飙到 50+(8 核机器),但 top 显示 CPU 使用率只有 30%。
排查步骤:
# 第一步:查看 vmstat,注意 b 列vmstat 1 5# 输出:b 列持续 40+,说明大量进程在 D 状态(不可中断睡眠)# 第二步:找出 D 状态进程ps aux | awk '$8~/^D/{print $0}'# 输出:大量 rsync 进程处于 D 状态# 第三步:确认 IO 情况iostat -xz 1 3# 输出:sda %util 100%, await 850ms# 第四步:查看是什么在做 IOiotop -oP -b -n 3# 输出:40 个 rsync 进程同时在写磁盘# 第五步:查看 crontabcrontab -l# 发现:40 台服务器的备份任务同时触发,全部 rsync 到这台机器根因:备份任务没有错峰,40 台机器同时 rsync 到同一台备份服务器,磁盘 IO 被打满,所有 IO 操作排队等待,进程进入 D 状态,load average 飙高。
解决方案:
--bwlimit=50000(限速 50MB/s)场景描述:应用日志出现大量连接超时,ping 目标机器偶尔丢包。
# 第一步:确认丢包ping -c 100 10.0.0.2# 输出:5% packet loss# 第二步:查看网卡错误统计ethtool -S eth0 | grep -E "drop|error|fifo"# 输出:rx_dropped: 128456(持续增长)# 第三步:查看 ring buffer 是否太小ethtool -g eth0# 输出:Current RX: 256, Maximum RX: 4096# 第四步:调大 ring bufferethtool -G eth0 rx 4096# 第五步:查看软中断是否集中在单核cat /proc/net/softnet_stat# 输出:第 0 行第 2 列持续增长,说明 CPU0 处理不过来# 第六步:启用 RPS 分散软中断echo"ff" > /sys/class/net/eth0/queues/rx-0/rps_cpus# 第七步:验证ping -c 100 10.0.0.2# 输出:0% packet loss以下参数在生产环境中线上验证过,适用于大部分 Web 服务和数据库场景。改之前先备份当前值,改错了可能导致系统不稳定。
# 备份当前内核参数sysctl -a > /tmp/sysctl_backup_$(date +%Y%m%d).conf内存相关:
# vm.swappiness:控制 swap 使用倾向# 默认值 60,生产环境建议设为 10# 设为 0 在内核 3.5+ 表示"尽量不用 swap 但 OOM 前还是会用"# 数据库服务器建议设为 1(MySQL/PostgreSQL 官方建议)sysctl -w vm.swappiness=10# vm.dirty_ratio:脏页占总内存的比例上限,超过后进程写操作会阻塞# 默认值 20,IO 密集型服务建议降到 10,避免突发大量刷盘sysctl -w vm.dirty_ratio=10# vm.dirty_background_ratio:后台刷脏页的触发阈值# 默认值 10,建议设为 5,让脏页更早开始刷盘,避免堆积sysctl -w vm.dirty_background_ratio=5# vm.overcommit_memory:内存超分配策略# 0=启发式(默认),1=总是允许,2=不允许超过 swap+物理内存*ratio# Redis 官方要求设为 1,否则 fork 子进程做 RDB 时可能失败# 数据库服务器建议保持 0sysctl -w vm.overcommit_memory=0网络相关:
# 全连接队列大小,默认 128,高并发场景远远不够# Nginx/Tomcat 等 Web 服务器建议 65535sysctl -w net.core.somaxconn=65535# 半连接队列大小sysctl -w net.ipv4.tcp_max_syn_backlog=65535# 网卡接收队列长度,默认 1000# 万兆网卡高流量场景建议 50000sysctl -w net.core.netdev_max_backlog=50000# TIME_WAIT 相关# 开启 TIME_WAIT 快速回收(NAT 环境下不要开,会导致连接异常)sysctl -w net.ipv4.tcp_tw_reuse=1# TIME_WAIT 最大数量,超过后直接销毁sysctl -w net.ipv4.tcp_max_tw_buckets=50000# TCP keepalive 参数# 默认 7200 秒才发第一个探测包,太慢了sysctl -w net.ipv4.tcp_keepalive_time=600sysctl -w net.ipv4.tcp_keepalive_intvl=30sysctl -w net.ipv4.tcp_keepalive_probes=3# TCP 内存参数(单位:页,每页 4KB)# min/pressure/max,16GB 内存机器的参考值sysctl -w net.ipv4.tcp_mem="262144 524288 786432"# 单个 socket 缓冲区大小sysctl -w net.core.rmem_max=16777216sysctl -w net.core.wmem_max=16777216sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"文件描述符:
# 系统级文件描述符上限sysctl -w fs.file-max=2097152# 用户级限制(/etc/security/limits.conf)cat >> /etc/security/limits.conf << 'EOF'* soft nofile 1048576* hard nofile 1048576* soft nproc 65535* hard nproc 65535EOF持久化所有参数:
cat > /etc/sysctl.d/99-performance.conf << 'EOF'vm.swappiness = 10vm.dirty_ratio = 10vm.dirty_background_ratio = 5net.core.somaxconn = 65535net.ipv4.tcp_max_syn_backlog = 65535net.core.netdev_max_backlog = 50000net.ipv4.tcp_tw_reuse = 1net.ipv4.tcp_max_tw_buckets = 50000net.ipv4.tcp_keepalive_time = 600net.ipv4.tcp_keepalive_intvl = 30net.ipv4.tcp_keepalive_probes = 3net.core.rmem_max = 16777216net.core.wmem_max = 16777216net.ipv4.tcp_rmem = 4096 87380 16777216net.ipv4.tcp_wmem = 4096 65536 16777216fs.file-max = 2097152EOFsysctl -p /etc/sysctl.d/99-performance.confperf 权限控制:perf 可以采集内核符号和其他进程的数据,生产环境必须限制
# 限制非 root 用户只能采集用户态数据echo 2 > /proc/sys/kernel/perf_event_paranoid# 禁止非 root 用户使用 kprobesecho 1 > /proc/sys/kernel/kptr_restrict审计日志:记录谁在什么时候执行了性能排查命令
# 配置 auditd 监控关键命令auditctl -a always,exit -F path=/usr/bin/perf -F perm=x -k perf_usageauditctl -a always,exit -F path=/usr/sbin/tcpdump -F perm=x -k tcpdump_usagetcpdump 抓包安全:抓包文件可能包含敏感数据(密码、token 等),用完立即删除,不要传到不安全的地方
排查问题靠临时命令,预防问题靠常态化监控。我们团队的标准监控栈:
node_exporter + Prometheus + Grafana:
# 安装 node_exporterwget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gztar xzf node_exporter-1.7.0.linux-amd64.tar.gzcp node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/# 创建 systemd 服务cat > /etc/systemd/system/node_exporter.service << 'EOF'[Unit]Description=Node ExporterAfter=network.target[Service]Type=simpleUser=node_exporterExecStart=/usr/local/bin/node_exporter \ --collector.systemd \ --collector.processes \ --collector.tcpstat \ --web.listen-address=:9100Restart=alwaysRestartSec=5[Install]WantedBy=multi-user.targetEOFuseradd -r -s /sbin/nologin node_exportersystemctl daemon-reloadsystemctl enable --now node_exporter注意:以下参数改错了可能导致严重后果,操作前务必备份。
perf: command not found | apt install linux-tools-$(uname -r) | |
perf record | echo 1 > /proc/sys/kernel/perf_event_paranoid | |
iostat | systemctl start sysstat | |
iotopCONFIG_TASK_IO_ACCOUNTING not enabled | pidstat -d 替代 | |
bcc-tools | linux-headers-$(uname -r) | |
smem |
# 查看系统日志(systemd 系统)journalctl -xe --since "30 minutes ago" --no-pager# 查看内核日志(带时间戳)dmesg -T | tail -50# 查看 syslogtail -f /var/log/syslog # Ubuntu/Debiantail -f /var/log/messages # CentOS/RHEL# 查看认证日志(SSH 登录失败等)tail -f /var/log/auth.log # Ubuntu/Debiantail -f /var/log/secure # CentOS/RHEL问题一:load average 高但 CPU 使用率不高
这是最容易误判的场景。load average 包含 D 状态进程,所以 load 高不等于 CPU 忙。
# 诊断:查看 D 状态进程ps aux | awk '$8~/^D/{print}'# 如果有大量 D 状态进程,查看它们在等什么cat /proc/<PID>/wchan# 通常是 IO 等待,确认磁盘状态iostat -xz 1 3解决方案:
mount -o soft 避免硬挂载导致进程不可杀问题二:iowait 高 vs steal 高的区别
# 查看 CPU 各状态占比mpstat -P ALL 1 5%iowait 高:本机磁盘 IO 慢,排查磁盘和文件系统%steal 高:虚拟机被宿主机抢占 CPU,说明宿主机超卖。这个问题你解决不了,找云厂商换机器或升配问题三:OOM Killer 日志分析
# 查看 OOM 事件dmesg | grep -i "out of memory" -A 20# 典型 OOM 日志解读# Out of memory: Kill process 12345 (java) score 850 or sacrifice child# 含义:内核选择了 OOM score 最高的 java 进程杀掉# score 850/1000 说明这个进程占了大量内存# 查看当前各进程的 OOM scorefor pid in $(ls /proc/ | grep -E '^[0-9]+$'); do name=$(cat /proc/$pid/comm 2>/dev/null) score=$(cat /proc/$pid/oom_score 2>/dev/null) [ -n "$score" ] && [ "$score" -gt 100 ] && echo"$pid$name$score"done | sort -k3 -rn | head -10解决方案:
oom_score_adj = -1000 防止被杀(如数据库进程)问题四:网络丢包排查流程
网络丢包要从外到内逐层排查:物理层 → 网卡 → 内核协议栈 → 应用层。
# 第一层:物理层/网卡层ethtool -S eth0 | grep -E "rx_dropped|rx_errors|rx_fifo_errors|rx_missed_errors"# rx_missed_errors > 0:网卡 ring buffer 满了,调大 ring buffer# rx_fifo_errors > 0:网卡 FIFO 溢出,通常是 CPU 处理不过来# 第二层:内核网络栈cat /proc/net/softnet_stat# 第二列 > 0:netdev_budget 不够,调大# 第三列 > 0:backlog 满了,调大 netdev_max_backlog# 第三层:TCP/IP 协议栈nstat -az | grep -E "Drop|Overflow|Prune"# TcpExtListenDrops:全连接队列满# TcpExtTCPBacklogDrop:socket backlog 满# TcpExtPruneCalled:socket 接收缓冲区满# 第四层:iptables/nftablesiptables -nvL | grep DROP# 检查是否有防火墙规则在丢包# 第五层:conntrack 表满dmesg | grep "nf_conntrack: table full"# 如果满了:sysctl -w net.netfilter.nf_conntrack_max=1048576# strace 追踪进程系统调用(定位进程卡在哪个系统调用)strace -p <PID> -f -T -tt -o /tmp/strace.log# -f:跟踪子进程/线程# -T:显示系统调用耗时# -tt:显示微秒级时间戳# 只看文件相关的系统调用strace -p <PID> -e trace=file -T# 只看网络相关的系统调用strace -p <PID> -e trace=network -T# ltrace 追踪库函数调用ltrace -p <PID> -f -T -o /tmp/ltrace.log注意:strace 会显著降低目标进程性能(实测降低 50%-80%),生产环境短时间使用,用完立即 detach。高性能场景用 perf trace 替代。
# CPU 使用率(按进程)top -bn1 -o %CPU | head -15# 内存使用(按进程)top -bn1 -o %MEM | head -15# 网络连接数ss -s# 磁盘 IOiostat -xz 1 1# 系统负载趋势(sar 历史数据)sar -u # CPU 历史sar -r # 内存历史sar -b # IO 历史sar -n DEV # 网络历史# 文件路径:/etc/prometheus/rules/node_alerts.ymlgroups:-name:node_alertsrules:-alert:HighCpuUsageexpr:100-(avgby(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))*100)>85for:5mlabels:severity:warningannotations:summary:"CPU 使用率过高 ({{ $labels.instance }})"description:"CPU 使用率 {{ $value | printf \"%.1f\" }}% 持续超过 85% 达 5 分钟"-alert:HighMemoryUsageexpr:(1-node_memory_MemAvailable_bytes/node_memory_MemTotal_bytes)*100>90for:5mlabels:severity:warningannotations:summary:"内存使用率过高 ({{ $labels.instance }})"description:"可用内存不足 10%,当前使用率 {{ $value | printf \"%.1f\" }}%"-alert:HighLoadAverageexpr:node_load1/countwithout(cpu,mode)(node_cpu_seconds_total{mode="idle"})>2for:5mlabels:severity:warningannotations:summary:"系统负载过高 ({{ $labels.instance }})"description:"1 分钟 load average 超过 CPU 核数的 2 倍"-alert:HighDiskUtilizationexpr:rate(node_disk_io_time_seconds_total[5m])*100>90for:1mlabels:severity:criticalannotations:summary:"磁盘 IO 饱和 ({{ $labels.instance }})"description:"磁盘 {{ $labels.device }} 利用率超过 90%"-alert:DiskSpaceRunningOutexpr:(1-node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}/node_filesystem_size_bytes)*100>85for:5mlabels:severity:warningannotations:summary:"磁盘空间不足 ({{ $labels.instance }})"description:"挂载点 {{ $labels.mountpoint }} 使用率 {{ $value | printf \"%.1f\" }}%"-alert:HighNetworkErrorsexpr:rate(node_network_receive_errs_total[5m])>10for:5mlabels:severity:warningannotations:summary:"网卡接收错误 ({{ $labels.instance }})"description:"网卡 {{ $labels.device }} 接收错误率 {{ $value | printf \"%.1f\" }}/s"-alert:SwapUsageIncreasingexpr:rate(node_memory_SwapFree_bytes[10m])<-1048576for:10mlabels:severity:warningannotations:summary:"Swap 使用量持续增长 ({{ $labels.instance }})"description:"Swap 使用量在持续增长,可能存在内存泄漏"sar 的历史数据是排查"昨天发生了什么"的关键证据,默认只保留 7 天。
# 修改 sar 数据保留天数(默认 7 天,建议 30 天)sed -i 's/^HISTORY=.*/HISTORY=30/' /etc/sysstat/sysstat# 备份 sar 数据到远程rsync -az /var/log/sysstat/ backup-server:/backup/sar/$(hostname)/# 查看历史某天的 CPU 数据sar -u -f /var/log/sysstat/sa15 # 查看 15 号的数据# 查看历史某天的 IO 数据sar -b -f /var/log/sysstat/sa15在系统正常运行时采集基线数据,出问题时对比基线快速发现异常。
#!/bin/bash# 文件名:collect_baseline.sh# 功能:采集性能基线数据# 建议每周执行一次,保留最近 4 周的基线set -euo pipefailBASELINE_DIR="/opt/perf_baseline"DATE=$(date +%Y%m%d)OUTPUT="$BASELINE_DIR/baseline_$DATE"mkdir -p "$OUTPUT"# 采集 5 分钟的数据echo"开始采集基线数据(5 分钟)..."vmstat 1 300 > "$OUTPUT/vmstat.txt" &iostat -xz 1 300 > "$OUTPUT/iostat.txt" &mpstat -P ALL 1 300 > "$OUTPUT/mpstat.txt" &sar -n DEV 1 300 > "$OUTPUT/sar_net.txt" &pidstat -u -d -r 5 60 > "$OUTPUT/pidstat.txt" &wait# 采集快照数据free -h > "$OUTPUT/free.txt"df -hT > "$OUTPUT/df.txt"ss -s > "$OUTPUT/ss.txt"cat /proc/meminfo > "$OUTPUT/meminfo.txt"sysctl -a > "$OUTPUT/sysctl.txt" 2>/dev/null# 清理 30 天前的基线find "$BASELINE_DIR" -name "baseline_*" -mtime +30 -exec rm -rf {} +echo"基线数据已保存到 $OUTPUT"eBPF/BCC 工具集:比 perf 更灵活的内核追踪方案,可以自定义追踪点
火焰图深入应用:不只是 CPU 火焰图,还有 Off-CPU 火焰图、内存火焰图、IO 火焰图
内核调优深入:理解 Linux 内核的内存管理、进程调度、网络协议栈工作原理
# === 全局概览 ===uptime # 查看 load averagetop -bn1 | head -20 # CPU/内存概览vmstat 1 10 # 系统级快照(CPU/内存/IO/上下文切换)dstat -tcmsdnl 5 # 综合实时监控# === CPU ===mpstat -P ALL 1 5 # 每个 CPU 核心使用率pidstat -u 1 5 # 进程级 CPU 使用率pidstat -w 1 5 # 进程级上下文切换perf top -p <PID> # 实时 CPU 热点函数perf record -p <PID> -g -- sleep 30 # CPU 采样# === 内存 ===free -h # 内存概览(看 available)smem -rkt -s pss # 进程真实内存(PSS)slabtop -o # 内核 slab 内存cat /proc/<PID>/status # 进程内存详情dmesg | grep -i oom # OOM 事件# === 磁盘 IO ===iostat -xz 1 5 # 磁盘 IO 统计iotop -oP # IO 进程排名pidstat -d 1 5 # 进程级 IOdf -hT # 文件系统使用率df -i # inode 使用率lsof +L1 # 已删除未释放的文件# === 网络 ===ss -s # 连接状态汇总ss -tn state established # ESTABLISHED 连接ss -tan state time-wait | wc -l # TIME_WAIT 数量sar -n DEV 1 5 # 网卡流量nstat -az | grep -i tcp # TCP 协议栈计数器tcpdump -i eth0 port 80 -c 1000 -w /tmp/cap.pcap # 抓包ethtool -S eth0 # 网卡统计