Files
baim0-learn/linux/03 nethogs用法与CPU-内存-磁盘IO-网络流量异常排查.md
2026-09-16 18:13:56 +08:00

37 KiB
Raw Permalink Blame History

title, date, tags, categories
title date tags categories
CPU / 内存 / 磁盘 IO / 网络流量异常排查(网络篇含 nethogs 详解) 2026-09-16
linux
linux

0. 总结(TL;DR)

  • 四大异常排查的统一套路:先看整体(利用率/饱和度/错误)→ 再定位到进程 → 再深挖到线程/文件/函数。即 Brendan Gregg 的 USE 方法(Utilization、Saturation、Errors)。
  • 一句话记四个入口命令:
    • CPU:top → mpstat -P ALL → pidstat -u 1 → perf top
    • 内存:free -h(看 available 不是 free)→ top 按 M → dmesg 查 OOM
    • 磁盘 IO:iostat -xz 1(看 await / aqu-sz / %util)→ iotop -o / pidstat -d
    • 网络:sar -n DEV 1 定量 → nethogs 定人(按进程) → iftop 定对象(按对端)→ ip -s link + nstat 查错误/重传
  • nethogs 是网络排查链路中「按进程看带宽」的关键工具:回答「哪个进程把带宽吃了」,默认按 KB/s 排序,-t trace 模式可脚本化采集,详见 5.2 节。
  • 关键认知:load 高 ≠ CPU 忙(含 D 状态等 IO 的线程);free 低 ≠ 内存不够(看 available);%util 高 ≠ 磁盘一定慢(看 await);带宽打满前先确认网卡速率上限。

(以下为完整版)


1. 通用方法论:先看整体,再定位进程(USE 方法)

任何资源异常都按三步走,不要上来就 ps aux | grep 蒙:

  1. 整体饱和度:这个资源总共还够不够用?(USE:Utilization 利用率、Saturation 饱和度、Errors 错误)
  2. 定位到进程:谁在消耗?
  3. 定位到根因:进程内部的线程 / 文件 / 系统调用 / 代码在干什么?

对照总表(后文每章展开):

资源 第一步:整体 第二步:找进程 第三步:深挖
CPU uptime、top、mpstat -P ALL 1 pidstat -u 1、top top -H -p PID、perf top -p PID、strace -c -p PID
内存 free -h、cat /proc/meminfo top(按 M)、pidstat -r 1 pmap -x PID、slabtop、dmesg(OOM)
磁盘 IO iostat -xz 1 iotop -o、pidstat -d 1 lsof -p PID、/proc/PID/io、fatrace
网络 sar -n DEV 1、ip -s link nethogs、iftop、ss -tinp nstat -az、ethtool -S、tcpdump

判断异常的辅助维度:

  • 历史基线:sar(sysstat 默认保留近 30 天 /var/log/sa/saDD)能回答「昨天这个时候是不是也这么高」;
  • 压力指标 PSI(内核 4.20+):/proc/pressure/{cpu,memory,io},回答「任务因为等资源被卡了多久」,比利用率更接近「是不是真的不够用」:
$ cat /proc/pressure/io
some avg10=0.00 avg60=0.23 avg300=0.24 total=2677023340
full avg10=0.00 avg60=0.22 avg300=0.23 total=2288460457

avg10/60/300 是 10 秒 / 1 分钟 / 5 分钟内任务因等该资源而停滞的时间占比(%)。some = 至少部分任务被卡,full = 所有非 idle 任务全被卡。any 值持续 > 20% 基本可确认该资源是瓶颈。


2. CPU 异常排查

2.1 第一步:看 load 和整体利用率

uptime
#  17:11:42 up 7 days,  3:30,  1 user,  load average: 0.20, 0.16, 0.14

load average 三个值 = 1/5/15 分钟内平均可运行(R)+ 不可中断睡眠(D)状态的线程数。判断标准:

  • load < CPU 核数:正常;
  • load 持续 > 核数:队列在积压(但不等于 CPU 忙,见 2.3);
  • 1 分钟值 ≫ 15 分钟值:负载在上升;反过来是在回落。
nproc                          # 核数,load 的参照系
top -bn1 | head -5             # 非交互打一份快照

top 头部关键行:

%Cpu(s):  4.5 us,  4.5 sy,  0.0 ni, 86.4 id,  4.5 wa,  0.0 hi,  0.0 si,  0.0 st

逐列含义与异常指向:

列 含义 高了说明什么 / 下一步
us 用户态 CPU 应用本身在算:业务逻辑、死循环、频繁 GC。→ pidstat -u + perf top
sy 内核态 CPU 系统调用/内核路径重:频繁 IO、锁竞争、内存分配。→ strace -c、perf top 看内核函数
ni nice 调整过的用户态 同 us,少见
id 空闲 越高越好
wa 等 IO 的 CPU 磁盘/存储慢,不是 CPU 不够!→ 转第 4 章 iostat
hi 硬中断 网卡/磁盘中断风暴。→ /proc/interrupts
si 软中断 高 PPS 网络收发、ksoftirqd 高。→ /proc/softirqs、sar -n DEV
st 被宿主机偷走(仅虚拟机) 云主机超卖 / 同宿主邻居吵闹。st 持续 > 10% 直接找云厂商,本机查不出东西

top 交互速记:1 展开每核;P 按 CPU 排;M 按内存排;c 显示完整命令行;H 切到线程视图;k kill;W 保存配置。

2.2 确认是单核打满还是整体打满

mpstat -P ALL 1 1
05:11:45 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
05:11:45 PM  all    2.00    0.00    0.50    0.00    0.00    0.50    0.00    0.00    0.00   97.00
05:11:45 PM    0    0.99    0.00    0.99    0.00    0.00    0.99    0.00    0.00    0.00   97.03
05:11:45 PM    1    3.03    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   96.97

典型异常形态:

  • 某一颗核 100%、其余空闲:单线程瓶颈(如单 worker 的 Redis、没开多线程的压缩任务)。优化方向是多开实例 / 多线程,加核没用。
  • 所有核 us 高:应用计算量真上来了,扩容或优化代码。
  • sy 占比高(> 20~30%):系统调用太频繁或内核锁竞争,继续往下查。
  • si 集中在某一核:网卡软中断没有分散,检查 RPS/IRQ affinity(见 5.6)。

2.3 load 高但 CPU 不忙:查 D 状态

load 统计 R+D。如果 load 很高、%Cpu id 也很高,多半是一堆线程卡在 D(不可中断睡眠,通常在等 IO):

# vmstat 的 r 列 = 运行队列长度(R),b 列 = D 状态数
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 gu
 2  0      0  73868  30836 679976    0    0   503    14 1539    8  1  0 99  0  0  0
  • r 持续 > 核数:CPU 真不够 / 调度排队;
  • b 持续 > 0:有线程卡 IO,去第 4 章。

找出具体哪些进程在 D:

ps -eo pid,ppid,stat,wchan:30,comm | awk '$3 ~ /^D/'
# 或直接看卡在哪
for p in $(ps -eo pid,stat | awk '$2~/^D/{print $1}'); do
  echo "== $p $(cat /proc/$p/comm)"; cat /proc/$p/stack 2>/dev/null | head -5
done

/proc/<pid>/stack 里的内核栈能直接看到卡在 io_schedule / nfs_wait / 哪个文件系统路径上。

2.4 第二步:定位到进程

# 每 1 秒输出所有进程 CPU 占用(含 %wait 等待调度的时间)
pidstat -u 1
05:11:59 PM   UID       PID    %usr %system  %guest  %wait    %CPU   CPU  Command
05:11:59 PM  1001     57651    0.99    0.00    0.00   0.00    0.99     0  gitea
05:11:59 PM     0    141362    0.99    0.00    0.00   0.00    0.99     1  AliYunDun

注意 %CPU 是单核百分比:800% = 用满 8 个核。%wait 高说明进程想跑但排不上队(CPU 真饱和)。

老命令同样好用:

ps -eo pid,ppid,pcpu,pmem,comm --sort=-pcpu | head -15
ps aux --sort=-%cpu | head

2.5 第三步:定位到线程和函数

定位到线程(Java/Go 多线程服务必做):

top -H -p <PID>          # 线程视图,找到最热的 TID
pidstat -t -p <PID> 1    # 命令行等价

拿到 TID 后,Java 栈对照:printf '%x\n' <TID> 转十六进制,jstack <PID> 里搜 nid=0x<hex>。

定位到函数(为什么这个进程烧 CPU):

perf top -p <PID>             # 实时看热点函数(用户态+内核态)
perf top -g -p <PID>          # 带调用链(需要 frame pointer 或 dwarf)

没有 perf 或权限不足时,退而用 strace -c -p <PID> 统计系统调用分布——sy 高的进程一眼可见是 futex 风暴、epoll 空转还是读写频繁。

2.6 上下文切换与中断异常

CPU 不高但「卡」,有时是调度/中断层面的问题:

vmstat 1     # 看 cs(每秒上下文切换)和 in(每秒中断)

经验值:cs 和核数×1万 同数量级还算正常;突然暴涨一个数量级要查。

pidstat -w 1       # cswch/s 自愿切换(主动等锁/等IO),nvcswch/s 非自愿(被抢占)
cat /proc/interrupts    # 硬中断按核分布,看是否单核倾斜
cat /proc/softirqs      # 软中断分类(NET_RX/NET_TX/TIMER/RCU...)

非自愿切换 nvcswch/s 爆高 + r 队列长 = 线程数远超核数在互抢(典型:线程池配太大)。

2.7 云主机专属:steal 时间

top       # %Cpu 行看 st
# 或者
sar -u 1 5 | grep -v Average    # %steal 列

st(steal)是虚拟化管理程序把本属于这台 VM 的 CPU 分给了别的 VM。本机一切指标都正常、st 却 10%+,就是宿主机超卖/邻居吵闹,工单找云厂商,别再折腾本机。

2.8 CPU 排查速查表

现象 首选命令 大概率原因
load 高 + id 低 + us 高 pidstat -u 1 应用算力不足 → perf top 找热点
load 高 + id 低 + sy 高 strace -c -p PID 系统调用风暴、锁、IO 路径
load 高 + id 高 ps -eo stat | grep -c ^D、vmstat 看 b D 状态堆叠,查磁盘/NFS
load 高 + wa 高 iostat -xz 1 存储慢,不是 CPU 问题
单核 100% mpstat -P ALL 1 单线程瓶颈
si 高、单核软中断倾斜 cat /proc/softirqs 网卡中断未分散(RPS)
什么都正常就是慢(VM) sar -u 看 %steal 宿主机超卖
cs 暴涨 pidstat -w 1 线程过多互抢

3. 内存异常排查

3.1 第一步:free 的正确读法

free -h
               total        used        free      shared  buff/cache   available
Mem:           1.6Gi       1.1Gi        72Mi        20Mi       694Mi       573Mi
Swap:             0B          0B          0B

唯一正确的看法:看 available,不看 free。

  • free(72Mi)是「完全没被任何人用」的内存,Linux 会把空闲内存拿去做 page cache,所以这个值常年很小是正常的;
  • buff/cache(694Mi)大部分是可回收的:进程要内存时内核立即回收给它;
  • available(573Mi)= 内核估算的「现在启动一个新应用最多能拿到多少」,这个才是剩余内存的真实答案。

经验:available / total 持续 < 10% 才算内存紧张。free 低 + available 高 = 完全健康,不要清缓存。

3.2 /proc/meminfo 关键字段

grep -E 'MemTotal|MemFree|MemAvailable|Buffers|^Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab|SReclaimable|SUnreclaim|Shmem' /proc/meminfo
MemTotal:        1712768 kB
MemFree:           84764 kB
MemAvailable:     602204 kB
Buffers:           31540 kB
Cached:           606828 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                64 kB
Writeback:             0 kB
Shmem:             20872 kB
Slab:             155864 kB
SReclaimable:      77084 kB
SUnreclaim:        78780 kB
字段 含义 异常信号
Cached page cache 高是正常的;「内存莫名消失」先看这
Dirty / Writeback 待回写 / 回写中的脏页 Dirty 长期几个 GB 不落盘 → 存储慢或 writeback 参数问题,断电风险大
Shmem 共享内存 + tmpfs tmpfs(/dev/shm、/run)占用直接吃掉可用内存且不在任何进程 RSS 里体现全量
Slab 内核对象缓存 其中 SUnreclaim(不可回收)暴涨 → dentry/inode 泄漏(海量小文件遍历后常见)
SwapTotal/Free swap 见 3.5

3.3 第二步:谁在吃内存

top              # 按 M 排序,看 RES 列
# 或
ps -eo pid,rss,pmem,comm --sort=-rss | head -15
# 或动态看
pidstat -r 1
05:11:59 PM   UID   PID  minflt/s  majflt/s     VSZ     RSS   %MEM  Command
05:11:59 PM     0  196594 12.87    0.00    10128796  173364  10.12  pi

列辨析(排查时极容易搞错):

  • VSZ/VIRT(虚拟地址空间):包含映射但从未碰过的内存,大不代表有问题(Java 堆、Go runtime 动辄几十 G 很正常);
  • RSS/RES(常驻物理内存):实际占的物理页,看这个;
  • minflt/s(minor fault):页已在内存(如 cache),只是没映射,无害;
  • majflt/s(major fault):要从磁盘读页(swap 换入 / mmap 文件缺页),持续非 0 = 内存不足在抖,性能杀手。

RSS 的坑——共享库被重复计算:多个进程共享的 libc 等页会算进每个进程的 RSS,直接加总 RSS 会远超物理内存。精确口径用 PSS(按比例分摊共享页):

# smem(需安装):按 PSS 排
smem -rtk | tail -15

看单个进程的内存构成:

pmap -x <PID> | tail -20
# [ anon ] 堆/栈等匿名页 —— 泄漏一般长在这
# 文件名开头的行是 mmap 的文件/共享库 —— 不算泄漏

判断「进程内存泄漏」的标准动作:连续采 RSS 画曲线:

while :; do ps -o rss= -p <PID> >> rss.log; sleep 60; done
# RSS 单调只涨不回落(重启才降)→ 泄漏;涨到平台期稳住 → 是 cache 型应用(如 JVM、Redis)的天性

3.4 那些「找不到凶手」的内存消失

RSS 加总对不上 used 的常见去向,按概率排:

  1. page cache / slab:free 的 buff/cache 列,正常,可回收;
  2. tmpfs:df -h -t tmpfs,/dev/shm、/run 里的大文件;
  3. SUnreclaim slab:slabtop -s c | head 看 dentry/inode 是否 GB 级(海量小文件 + 高目录遍历导致),可 echo 2 > /proc/sys/vm/drop_caches 回收(先确认可回收部分确实大);
  4. HugePages:grep Huge /proc/meminfo,给 Oracle/Redis THP 预留的不在 RSS 常规口径;
  5. 内核/驱动:cat /proc/vmallocinfo、slabtop,网卡大 ring buffer、GPU 驱动也会吃。

清缓存命令(仅用于验证「消失的内存在哪」,不要当日常运维手段): sync; echo 3 > /proc/sys/vm/drop_caches

3.5 swap 与 OOM

swap:本机 SwapTotal=0(很多云主机默认关 swap)。有 swap 的机器上:

  • free 里 swap 被用了不代表异常(冷数据换出很正常);
  • 要看的是 vmstat 1 的 si / so(每秒换入/换出):持续非 0 才是「在抖」,说明活跃内存真不够;
  • 伴随症状:pidstat -r 的 majflt/s 飙升、load 高、iowait 高。

OOM Killer——进程「莫名消失」必查:

dmesg -T | grep -iE 'out of memory|oom-kill|killed process'
journalctl -k | grep -i oom
# 典型输出:
# [Tue Sep 16 10:22:01 2026] Out of memory: Killed process 81234 (java) total-vm:... anon-rss:...

容器/cgroup 场景(dmesg 可能没有,看 cgroup 事件):

cat /sys/fs/cgroup/memory.events 2>/dev/null | grep oom        # cgroup v2
cat /sys/fs/cgroup/memory/memory.oom_control 2>/dev/null       # cgroup v1

谁最容易被 OOM 选中:cat /proc/<PID>/oom_score(分数越高越先死),可通过 oom_score_adj(-1000~1000)保护关键进程。

3.6 PSI memory:内存压力的终极判据

cat /proc/pressure/memory
# some avg10=0.00 avg60=0.00 avg300=0.00 total=887003792
# full avg10=0.00 avg60=0.00 avg300=0.00 total=825600394

full 持续 > 0 说明有全系统因内存回收而停滞(direct reclaim),即使 available 看着还行,也说明回收速度跟不上分配速度(典型:大页/连续内存分配、zone 不均)。

3.7 内存排查速查表

现象 首选命令 大概率原因
free 很低 free -h 看 available 多半正常,cache 占用
available < 10% top 按 M / smem -rtk 找出 RSS 大户
RSS 总和 ≪ used df -h -t tmpfs、slabtop -s c tmpfs / slab 泄漏
进程 RSS 只涨不跌 连续采样 + pmap -x 泄漏(anon 段增长)
进程突然没了 dmesg -T | grep -i oom OOM Killer
卡、majflt 高、si/so 非 0 vmstat 1、pidstat -r 1 swap 抖动,内存真不够
内存时有时无地卡 /proc/pressure/memory direct reclaim 停顿

4. 磁盘 IO 异常排查

4.1 第一步:iostat 看盘的整体状况

iostat -xz 1        # -x 扩展列 -z 隐藏空闲盘,1 秒一刷

(新版 sysstat 输出,读写分列):

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
vda      22.00  1332.00  0.00  0.00   1.59    60.55   0.00    0.00   0.00  0.00   0.00    0.00   ...   0.04   1.20

老版只有合并的 await,新版拆成 r_await / w_await,更精确。真正要盯的只有 4 组指标:

列 含义 异常标准(经验值)
r/s w/s 每秒读/写次数(IOPS) 对照盘的规格:SATA SSD 几万、NVMe 几十万、机械盘 100~200。r/s+w/s 顶到规格上限 = IOPS 打满
rkB/s wkB/s 读/写吞吐 顶到接口带宽(SATA ≈ 550MB/s、千兆网盘 ≈ 110MB/s)= 带宽打满
r_await w_await 每个 IO 的平均耗时(ms),含排队 机械盘 > 10ms、SSD > 1~2ms、NVMe > 0.5ms 且持续 → 盘慢或过载。这是「应用感觉卡」最直接的对应指标
aqu-sz 平均 IO 队列深度 持续 > 1(SSD)说明请求在排队;越大堵得越狠
%util 每秒中盘忙的时间占比 机械盘 ≈100% 即饱和;SSD/NVMe 因内部并行,util 高不一定饱和,必须结合 await 看

典型判读组合:

iostat 表现 结论
await 高 + aqu-sz 高 + %util ≈ 100 盘真被打满了,找出是谁(4.2)
await 高 + %util 不高 盘的自身/链路问题(坏道、RAID 重建、云盘限速、网络盘抖动),和应用无关,查硬件/云监控
rkB/s 顶满带宽、await 不高 顺序大流量(备份/日志归档),不一定是故障
r/s 几万但 rkB/s 很小 随机小 IO 风暴(大量小文件/元数据操作),await 会很难看

4.2 第二步:哪个进程在读写

iotop -oP          # -o 只显示正在做IO的进程,-P 只显示进程(不含线程)
# 或非交互打一份
iotop -bon3
Total DISK READ: 12.00 M/s | Total DISK WRITE: 3.50 M/s
    TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN      IO    COMMAND
  81234  be/4  mysql        8.20 M/s    2.10 M/s  0.00 %  5.31 %  mysqld

等价命令行工具:

pidstat -d 1       # kB_rd/s kB_wr/s kB_ccwr/s(ccwr=写了又被取消的,如覆盖写)

注意 iotop 的一个大坑:它的 Total 行统计的是「系统调用层」的读写,和 iostat 看到的「块设备层」经常对不上——page cache 回写(kworker)、文件系统日志(jbd2)、内存回收(kswapd)产生的磁盘 IO 不属于任何业务进程。所以:

  • iostat 高、iotop 业务进程都不高 → 看内核线程:
ps aux | grep -E 'jbd2|kworker|kswapd|kdmflush' | grep -v grep
# jbd2/sda1-8 高 = 文件系统 journal 压力大(大量小写入/ fsync 频繁)
# kswapd 高 + si/so 非 0 = 内存不够在回收,IO 是内存问题的次生灾害(回第 3 章)

4.3 第三步:进程在读写哪些文件

# 某进程当前打开且在用的大文件
lsof -p <PID> -nP | grep -E 'REG' | awk '$7 > 104857600'     # >100MB 的

# 直接看进程 IO 计数器(累计值,取两次差值)
cat /proc/<PID>/io
# read_bytes:  实际从块设备读了多少
# write_bytes: 实际写到块设备多少
# cancelled_write_bytes: 写取消量

# 全盘文件级审计:谁在读写什么文件(需要 root,内核 audit)
fatrace -c 2>/dev/null | head -30    # 部分发行版需安装 fatrace

没有 fatrace 的极简替代:对嫌疑进程 lsof -p 反复采样,或用 strace -e trace=read,write,pread64,pwrite64 -c -p <PID> 统计它读写的 fd,再对照 ls -l /proc/<PID>/fd 把 fd 翻译成文件名。

4.4 文件系统层:空间、inode、「删了文件空间没回来」

IO 排查经常和容量问题混在一起,一并说:

df -h            # 文件系统使用率
df -i            # inode 使用率(海量小文件会先打爆 inode,df -h 却还有空间)

经典悬案:df 说满了,du -sh /* 加总却对不上,三个原因按概率排:

  1. 文件已删但被进程持有句柄(日志被 rm 但服务没重启),空间不释放:
lsof +L1                       # 直接列出 link 数=0 的被删文件
# 或
find /proc/*/fd -lname '*deleted*' -ls 2>/dev/null
# 释放:重启进程,或 echo > /proc/<PID>/fd/<FD> 截断(比 rm 优雅)
  1. 挂载点遮蔽:目录挂载前有旧数据,被挂载的文件系统盖住了。卸载后(或 mount --bind / /mnt 后看 /mnt/<目录>)才能看到。
  2. du 没加 -x 跨了文件系统、硬链接重复计算等口径问题。

4.5 根因定位后的常见处置方向

定位结果 处置
单进程日志/备份把盘打满 ionice 降级:ionice -c3 -p <PID>(idle 类,空闲时才轮到它);rsync/cp 加 --bwlimit
jbd2 高(fsync 风暴) 应用减少 fsync 频率、批量提交;极端可换 noatime/data=writeback(权衡可靠性)
kswapd 引起的 IO 治本在内存(扩内存/降用量),不是盘的问题
盘本身 await 高、负载不高 查 SMART:smartctl -a /dev/sda;云盘查云监控是否限速/突发额度耗尽
随机小 IO 风暴 应用层合并写、增大缓冲、换 SSD/NVMe

4.6 磁盘 IO 排查速查表

现象 首选命令 大概率原因
应用卡 + wa 高 iostat -xz 1 看 await/aqu-sz 盘过载或盘故障
iostat 高、iotop 没进程 ps aux | grep -E 'jbd2|kswapd|kworker' 回写/日志/回收的内生 IO
明确要限速 ionice -c3 -p PID 备份/统计类任务让路
df 满 du 不满 lsof +L1 删除未释放的句柄
写小文件极慢 iostat 看 r/s 与 rareq-sz 随机小 IO / 元数据瓶颈
建不了文件但空间够 df -i inode 耗尽

5. 网络流量异常排查

网络排查的四级定位链:sar 定量(网卡总账)→ nethogs 定人(哪个进程)→ iftop 定对象(和谁传)→ ss/tcpdump 定细节(单连接/报文)。本章按此顺序展开。

5.1 第一步:网卡总量到底满没满

sar -n DEV 1        # 每秒各网卡收发
05:11:58 PM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s   %ifutil
05:11:58 PM      eth0     25.00     24.00      1.55     39.96      0.00      0.00      0.00      0.00

关键列:rxkB/s、txkB/s(吞吐)、rxpck/s、txpck/s(PPS)、%ifutil(半双工估算的利用率,全双工网卡参考价值有限)。

「满没满」要自己算——先拿到网卡速率上限:

ethtool eth0 | grep -i speed        # 物理网卡:Speed: 10000Mb/s
# 虚拟网卡(virtio 等)常显示 Unknown!,那就以云厂商给的实例带宽上限为准

换算:10Gb/s ≈ 1250 MB/s ≈ 1250000 kB/s。txkB/s 达到上限的 80% 且持续 = 带宽打满实锤。

没有 sar 的裸机替代:cat /proc/net/dev(累计字节数,取两次差值)、ip -s link、小工具 ifstat -i eth0 1、nload eth0。

裸 /proc 写法(容器里也能用):

rx1=$(awk '/eth0/{print $2}' /proc/net/dev); sleep 1
rx2=$(awk '/eth0/{print $2}' /proc/net/dev)
echo "RX: $(( (rx2-rx1)/1024 )) KB/s"

5.2 第二步:哪个进程在吃带宽 —— nethogs 详解

sar 只能告诉你网卡跑了多少流量,回答不了「哪个 PID 干的」。nethogs 就是干这个的:按进程分组统计实时带宽,是带宽告警后第一个该敲的命令。

5.2.1 安装与前提

# Debian / Ubuntu
apt install -y nethogs

# RHEL / CentOS
yum install -y nethogs

需要 root 运行(抓包 + 遍历 /proc 匹配 socket inode)。普通用户跑大部分连接会显示 unknown TCP。

5.2.2 基本用法与输出解读

nethogs              # 监控所有 up 的非 loopback 网卡
nethogs eth0         # 只监控 eth0
nethogs eth0 eth1    # 监控多块

交互界面(默认 1 秒刷新,按速率降序):

NetHogs version 0.8.7

    PID USER     PROGRAM                      DEV        SENT      RECEIVED
 196510 root     sshd: root@pts/0             eth0      28.246       0.653 KB/sec
 141362 root     /usr/local/aegis/AliYunDun   eth0       2.208       0.117 KB/sec
      ? root     unknown TCP                             0.000       0.000 KB/sec

  TOTAL                                                 30.454       0.770 KB/sec

列含义:

列 含义
PID 进程号;? 表示没能匹配到进程
USER 进程属主
PROGRAM 程序名(-l 显示完整命令行,-b 只显示 basename)
DEV 流量走的网卡
SENT / RECEIVED 该进程发送 / 接收速率,默认 KB/s

unknown TCP:nethogs 通过 pcap 抓包拿到五元组,再去 /proc/<pid>/fd 反查 socket inode 归属进程。短连接(抓到时进程已退出)、内核线程发的包、权限不足时都会归入 unknown。

5.2.3 命令行参数全解

usage: nethogs [-V] [-h] [-x] [-d seconds] [-v mode] [-c count] [-t] [-p] [-s]
       [-a] [-l] [-f filter] [-C] [-b] [-P pid] [device [device ...]]
参数 作用 使用场景
-d 秒 刷新间隔,默认 1 压平毛刺用 -d 5
-v mode 视图模式:0=KB/s(默认) 1=累计KB 2=累计B 3=累计MB 4=MB/s 5=GB/s 看「谁累计传得最多」用 -v 3;千兆以上环境用 -v 4
-c 次数 刷新 N 次后自动退出,默认 0=无限 配合 -t 采集样本
-t tracemode:类似 top -b 的批处理输出 脚本化 / 落日志必用
-p 混杂模式抓包(不推荐) 默认只抓本机地址的包,够了
-s 按 SENT 列排序 找上行大户(备份/外传)
-a 监控所有设备,含 loopback 和 down 的 排查本机服务间互调流量
-l 显示完整命令行 区分同名进程
-b 只显示程序 basename 输出更窄
-C TCP 和 UDP 都抓(老版本只抓 TCP) 0.8.x 建议显式加上
-f 表达式 pcap 过滤表达式(实验性),如 -f "port 53" 只看某类流量
-P pid 只显示指定进程 盯单个进程
-g N 每 N 次刷新做一次进程 GC,默认 50 一般不用动
-x bughunt 调试模式 排查 nethogs 自身问题

5.2.4 运行中交互按键

按键 作用
q 退出
m 切换显示单位:KB/s → 累计 KB → 累计 B → 累计 MB → MB/s → GB/s 循环(等价于改 -v)
s 按 SENT(发送)排序
r 按 RECEIVED(接收)排序
l 显示完整命令行
b 程序名显示为 basename

5.2.5 实战常用姿势

姿势一:突发带宽告警,现场抓凶手

nethogs -d 2          # 2 秒刷新,按 m 切到累计 MB,等 30 秒看谁涨得最快

姿势二:无人值守采集(挂后台 / 接告警脚本)

# 每 2 秒采样一次,采 30 轮,按时间戳落文件
nethogs -t -d 2 -c 30 eth0 >> /var/log/nethogs.$(date +%F).log

-t trace 模式输出形如:

Refreshing:
sshd: root@pts/0/196510/0	28.2457	0.652734
/usr/local/aegis/aegis_client/aegis_13_03/AliYunDun/141362/0	2.20781	0.117188
unknown TCP/0/0	0	0

格式为 程序/PID/UID<TAB>SENT<TAB>RECEIVED(单位 KB/s),非常规律,一行 awk 就能找出超速进程:

# 找出任意一轮采样中发送速率超过 10000 KB/s 的进程
nethogs -t -d 1 -c 60 eth0 | awk -F'\t' '$2 > 10000 && !/^Refreshing/ {print strftime("%T"), $0}'

姿势三:只关心某个端口 / 某类业务

nethogs -t -f "port 3306" eth0     # 谁在和 MySQL 交互
nethogs -t -f "udp" eth0           # 只看 UDP

姿势四:找「累计流量王」而不是瞬时峰值

定时任务备份、日志外发这类瞬时抓不到的,用累计模式:

nethogs -v 3 -d 10      # 累计 MB,10 秒刷新,挂一会儿按总量排序看

5.2.6 原理与注意事项(避坑)

  1. 工作原理:libpcap 抓包得到 IP+端口 → 解析 /proc/net/tcp(6)/udp(6) 拿到 socket inode → 遍历 /proc/<pid>/fd 匹配 inode → 流量记到进程头上。所以:
    • 必须 root(否则 /proc 下别人的 fd 读不到);
    • 短命进程(php-fpm 子进程、curl 一闪而过)容易漏记或归 unknown;
    • 连接建立后进程退出、fd 被继承(fork 型服务),流量可能记在父进程或显示不全。
  2. 统计口径是「网卡上看到的包」,含协议头开销,和应用层日志里的字节数会有差异,数量级对得上即可。
  3. 默认不监控 loopback,本机服务互调(nginx → 127.0.0.1:9000)看不到,要加 -a。
  4. 容器环境里在宿主机上跑 nethogs 看到的还是宿主机 PID 命名空间里的进程名,veth 对抓包意义不大;更推荐进容器网络命名空间执行:nsenter -t <pid> -n nethogs。

5.3 第三步补充:按连接/对端定位(iftop + ss)

nethogs 按进程、iftop 按连接,两者互补。抓到进程后还想知道「在和谁传」:

iftop -n -P -i eth0        # 按对端 IP:端口 实时排行,-n 不反解域名,-P 显示端口
# 已知是某进程后,看它具体的连接与每连接队列
ss -tinp | grep -A1 pid=81234
# 输出里含 cwnd、rtt、bytes_sent、bytes_received、retrans,能精确到单连接

分工口诀:sar 定量 → nethogs 定人(进程)→ iftop 定对象(对端)→ ss -tinp 定细节(连接)。

5.4 出口流量异常(被打满 / 疑似外泄)的标准流程

  1. sar -n DEV 1 确认是 txkB/s(出向)异常;
  2. nethogs -s eth0(-s 按发送排序)抓进程;
    • 抓到的是认识的业务进程 → 正常业务高峰,考虑限速(tc)或扩容;
    • 抓到的是陌生进程 → ls -l /proc/<PID>/exe 看二进制路径、cat /proc/<PID>/cmdline,高度怀疑被入侵/挖矿外传,先 iptables -I OUTPUT -m owner --pid-owner <PID> -j DROP(或 kill)止血再排查;
  3. nethogs 抓不到(unknown TCP 多)→ iftop -nP 看对端 IP,ss -tanp 找对应五元组归属;短连接一闪而过的情况用抓包兜底:
tcpdump -i eth0 -nn -c 1000 -w /tmp/cap.pcap    # 先采 1000 包
# 事后分析哪个 IP 量大
tcpdump -nn -r /tmp/cap.pcap | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -nr | head

5.5 入口流量异常(疑似被攻击/刷接口)

  • iftop -nP / sar -n DEV 看入口带宽和 PPS:
    • 带宽大、包也大:正常内容被刷(盗链),找业务层解决(Referer 校验、CDN);
    • PPS 巨大、平均包小(rxkB/s ÷ rxpck/s ≈ 几十字节):典型小包攻击/SYN flood,本机协议栈扛不住时救不了,要靠上游清洗/高防;
  • ss -s 看连接概貌,ss -ant state syn-recv | wc -l 飙升 = SYN 攻击(net.ipv4.tcp_syncookies=1 确认开启);
  • 配合第 01 篇的连接状态统计:ss -ant | awk 'NR>1{++S[$1]} END{for(a in S)print a,S[a]}' 看 TIME_WAIT/CLOSE_WAIT 堆积。

5.6 错误、丢包、重传:流量「不大但很卡」

流量没满但业务超时/慢,查质量指标:

ip -s link show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
    RX:  bytes packets errors dropped  missed   mcast
     662667261 1473827      0       0       0       0
    TX:  bytes packets errors dropped carrier collsns
     350565994  983604      0       0       0       0
  • errors(CRC/帧错误,持续增长 → 网线/光模块/对端端口问题)、dropped(缓冲区满丢包 → 本机收包来不及处理)、overruns、carrier 任何一项持续增长都是实锤故障;
  • 更细的计数在驱动层:ethtool -S eth0 | grep -iE 'drop|err|miss|fifo';
  • TCP 层健康度:
nstat -az | grep -iE 'TcpRetransSegs|TcpInErrs|ListenDrops|ListenOverflows|UdpRcvbufErrors'
TcpRetransSegs                  12973              0.0
TcpInErrs                       28                 0.0
TcpExtListenOverflows           0                  0.0
TcpExtListenDrops               0                  0.0
  • 重传率 = TcpRetransSegs ÷ TcpOutSegs,> 1% 说明链路质量差或对端拥塞;
  • ListenOverflows/ListenDrops 增长 → accept 队列溢出(backlog 太小,详见第 01 篇 4.5 节);
  • UdpRcvbufErrors 增长 → UDP 应用读太慢丢包(调大 net.core.rmem_max + 应用 SO_RCVBUF);
  • 软中断是否均衡:cat /proc/softirqs 看 NET_RX 是否全压在 CPU0。单核 si 打满而总带宽没满 → 开 RPS 或调网卡队列 IRQ 绑定:
# 查看与设置网卡队列数
ethtool -l eth0
ethtool -L eth0 combined 4
# 各队列中断绑核:/proc/irq/<N>/smp_affinity_list

5.7 连接数 / conntrack 上限

「流量不大、CPU 不高,但新连接建立失败/丢 SYN」常见元凶:

ss -s                                  # 连接概览
Total: 254
TCP:   17 (estab 5, closed 1, orphaned 0, timewait 1)
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
dmesg -T | grep -i 'conntrack.*full'   # 打满时内核日志会有 "nf_conntrack: table full, dropping packet"

count 接近 max:调大 nf_conntrack_max(同时调 hashsize),或业务侧减少短连接。文件句柄上限同理:cat /proc/<PID>/limits | grep 'open files',ss -s 的 Total 逼近 limit 时应用会报 Too many open files。

5.8 网络排查速查表

现象 首选命令 大概率原因
出口带宽打满 nethogs -s eth0 业务高峰 / 异常进程外传
入口被打 iftop -nP、sar -n DEV 看 PPS 与均包长 盗链 or 小包攻击
知道网卡忙但不知谁 nethogs → iftop → ss -tanp 三级定位
流量不大但慢/超时 ip -s link、nstat -az | grep -iE 'retrans|drop' 丢包/重传/accept 溢出
单核 si 100% cat /proc/softirqs 网卡中断未分散
新连接失败 ss -s、conntrack 计数、ulimit conntrack 满 / 句柄满
短连接凶手抓不到 tcpdump 采样后离线统计 进程生命周期太短

6. 综合决策树(症状 → 命令链)

收到告警/用户报障「机器很卡」
│
├─ ① 先花 10 秒看全局:uptime(load)+ top(CPU 分解)+ free -h + iostat -xz 1 3 + sar -n DEV 1 3
│
├─ ② load 高、CPU us/sy 高 ─────────────→ 第 2 章:pidstat -u 1 → top -H → perf top
│
├─ ③ load 高、CPU 闲(id 高)────────────→ vmstat 看 b 列(D 状态)→ iostat → 第 4 章
│
├─ ④ CPU 正常、内存 available 低/进程消失 → 第 3 章:top M → dmesg OOM → slabtop/tmpfs
│
├─ ⑤ wa 高、await 高 ──────────────────→ 第 4 章:iotop -o → lsof/PID/io → ionice 止血
│
├─ ⑥ 带宽/PPS 告警 ────────────────────→ 第 5 章:sar -n DEV → nethogs(进程)→ iftop(对端)
│
├─ ⑦ 都没满但就是卡 ───────────────────→ st(云超卖)/ PSI /proc/pressure/* / nstat 重传丢包
│
└─ ⑧ 事后复盘(现场没了)───────────────→ sar 历史:sar -u/-r/-d/-n DEV -f /var/log/sa/sa<DD>

7. 工具全家福对照表

维度 CPU 内存 磁盘 IO 网络
整体利用率 top mpstat -P ALL sar -u free -h vmstat sar -r iostat -xz sar -d sar -n DEV ip -s link ifstat
饱和度/压力 vmstat r 列、PSI cpu PSI memory、si/so、sar -q aqu-sz、await、PSI io %ifutil、与速率上限对比
错误 %steal(VM) dmesg OOM、majflt SMART、dmesg IO error ip -s errors/dropped、nstat、重传率
定位进程 pidstat -u、top top M、smem(PSS)、pidstat -r iotop -o、pidstat -d nethogs
定位连接/文件 — pmap -x lsof、/proc/PID/io、fatrace iftop、ss -tinp、tcpdump
定位代码 perf top、strace -c、top -H 采样 RSS + valgrind/heaptrack strace -e trace=%file —
历史回看 sar -u -f /var/log/sa/saDD sar -r sar -d sar -n DEV

8. 一句话总结

四大资源异常排查的通用心法:先用 USE(利用率/饱和度/错误)看整体定方向,再用对应的 per-process 工具(CPU 用 pidstat、内存用 top/smem、磁盘用 iotop、网络用 nethogs)定位到人,最后下沉到线程/文件/连接/函数层找根因;网络这一环记住四级定位链——sar 定量、nethogs 定人、iftop 定对象、ss/tcpdump 定细节,即可闭环。