Linux 性能优化实战:从方法论到 CPU / 内存 / I/O / 网络逐层调优

面向有 Linux 使用经验的运维与 DBA。文中命令请在目标环境核实设备名、路径、版本后再执行;涉及内核参数与不可逆操作的步骤,均先说明前提、影响与回退。


一、为什么"优化"常常变成"瞎调"

很多性能问题并不是"参数没调好",而是一开始就定位错了瓶颈。典型误判有:

  • 看到 load average 很高,就认定 CPU 不够------其实大量进程处于 D 状态在等 I/O;
  • 看到 %util 100%,就认定磁盘打满------在 NVMe/多队列设备上几乎必然如此;
  • 看到 wa(iowait)下降,就以为 I/O 变好了------可能只是换成了更快的设备;
  • 把应用问题当成系统问题,或反过来。

所以性能优化的第一步不是"调参",而是用正确的指标把瓶颈层定位准确,再逐层动手。


二、一句话理解

性能优化 = 先定位瓶颈层(应用 / 语言框架 / 内核 / 硬件),再在该层做"减少等待、减少拷贝、提高并行度"三件事。

定位靠的是"从全局到局部、从现象到根因"的工具链;优化靠的是分层框架,而不是盲目堆参数。


三、核心方法论

3.1 四层次优化框架

层次 优化方向 常用手段
第一层:应用层 找热点函数、删减逻辑、优化算法 火焰图、内存对齐、避免微服务过细
第二层:语言与框架 选对语言、网络模型、内存分配器 C/Rust 做基础软件、Go/Java 做业务;epoll/gnet;tcmalloc/jemalloc
第三层:内核 减少系统调用、关闭 swap、大页、调度策略 taskset、nice、chrt、容器 throttle、离线调度器
第四层:硬件 主频、缓存、睿频、超线程、硬件卸载 选新微架构、SIMD(AVX512)、网卡/存储卸载

要点:优化要有先后。应用层的问题,堆再多内核参数也救不回来;反之,应用已经很优,内核层再抠收益也有限。

3.2 定位方法:先分清"负载"和"利用率"

  • 平均负载 :统计 R(运行)+ D(不可中断睡眠) 状态进程数,按指数加权移动平均计算,输出 1/5/15 分钟三个值。多核系统上,负载值 ÷ 核心数 = 活跃核心比例。
  • 关键陷阱 :D 状态进程在等 I/O,不消耗 CPU。所以"负载高"既可能是 CPU 不够,也可能是 I/O 不够。高负载 + CPU 空闲(id% 高)→ 去查 iowait。
  • 负载高不一定有问题,要结合响应时间判断。
bash 复制代码
uptime                      # 看 1/5/15 分钟平均负载
nproc                       # 核对核心数,算活跃核心比例

3.3 标准分析流程

复制代码
1) 全局体检:uptime / dmesg / vmstat 1 5 / mpstat -P ALL 1
2) 判定瓶颈层:
   - us 高 → CPU(应用/系统调用)
   - sy 高 → 内核(系统调用、中断、锁)
   - wa 高 + 磁盘忙 → I/O
   - 内存 free 低 + swap 活跃 → 内存
   - si/so 高 → swap 换入换出
3) 下钻到进程/线程:top -H / pidstat / iotop
4) 下钻到函数/调用栈:perf / 火焰图 / strace
5) 定位根因 → 在该层优化 → 复测验证

四、性能分析工具总览

维度 工具 用途
CPU top、mpstat、pidstat、perf 全局/单核/进程/函数级 CPU
内存 free、vmstat、/proc/meminfo、/proc/zoneinfo 内存、swap、水位线
I/O iostat、iotop、sar -d、strace、lsof 设备/进程/系统调用级 I/O
网络 ss、netstat、sar -n DEV、tcpdump、ethtool 连接、流量、丢包、网卡状态
综合 sar、vmstat 历史与实时全维度
轻量采集 IBM nmon + nmonanalyser、collectl+Graphite、Oracle OSWatcher(OSW) 长期趋势留档
追踪 perf、ftrace、eBPF(bpftrace/BCC) 内核与应用动态追踪

建议:生产环境常驻一个轻量采集器(如 OSWatcher/nmon)保留历史数据,故障复盘时才有"案发现场"。


五、CPU 优化

5.1 定位

bash 复制代码
# 全局与每核
top -1                       # 看 us/sy/wa/id 分布,按 1 展开每核
mpstat -P ALL 1 5            # 每核使用率,识别单核打满/负载不均
uptime; nproc

# 进程/线程级
pidstat -u 1 5               # 按进程看 CPU
top -H -p <pid>              # 看进程内线程

判读:

  • us 高 → 应用自身逻辑/算法/内存分配;
  • sy 高 → 系统调用、上下文切换、中断、锁争用;
  • 单核打满、其他核空闲 → 并行度不足或中断/软中断集中。

5.2 下钻到函数:perf + 火焰图

bash 复制代码
# 采样(交互分析用 -F 99,高精度定位用 -F 999,但必须检查 Lost 计数)
perf record -F 99 -g -p <pid> sleep 30
perf report                  # 交互查看热点函数与调用链

# 生成火焰图
perf script | stackcollapse-perf.pl | flamegraph.pl > cpu.svg

火焰图读法:

  • 从顶部 开始找宽度最大的函数------那是 CPU 时间的主要消耗者;
  • 区分自身消耗 与子函数调用消耗:宽度大但由子函数撑起来的,不是优化重点;顶部自身消耗大的才是目标。

示例(来自知识库案例) :某应用 us 达 80%,perf record -g -a sleep 5 后 perf report 显示 __libc_malloc 与某业务函数占比最高,展开调用链确认该函数频繁分配内存;引入内存池后 us 降到约 50%。(示例数据用于说明方法,实际收益以现场复测为准。)

5.3 优化手段与命令

bash 复制代码
# 1) 调整调度优先级(对 CPU 密集、需优先调度的进程)
nice -n -5 <cmd>                       # 提高优先级(普通用户只能调低)
renice -n 10 -p <pid>
chrt -f 50 <cmd>                       # 实时调度策略(谨慎,可能饿死其他进程)

# 2) 绑定 CPU(减少跨核迁移、cache 失效)
taskset -c 0-3 <cmd>
taskset -cp 0-3 <pid>

# 3) 中断/软中断亲和性(网络高 PPS 场景)
cat /proc/interrupts                   # 看中断在各核分布
# 将网卡中断绑定到固定核,避免与业务争抢

# 4) 频率与电源策略
cpupower frequency-info
cpupower frequency-set -g performance  # 设为性能模式(关闭节能降频)

# 5) 内核态占用高时,用 perf 看系统调用/上下文切换
perf stat -e context-switches,cpu-migrations,page-faults -p <pid> sleep 5

5.4 硬件层方向

  • 同代 CPU 主频越高越好 ,开启睿频自动升频;
  • 新微架构提升 IPC(每时钟指令数);
  • 核数适合并行任务 ;服务器选"单核强"还是"核多",取决于应用并行度;
  • 向量计算用 SIMD(AVX512) 加速。

5.5 注意事项

  • chrt 实时调度可能饿死其他进程,生产慎用;
  • taskset 绑定不当反而破坏负载均衡;
  • 关闭超线程要按应用类型评估,不总是收益;
  • 高 CPU 利用率场景下,wake_affine 可能失效(离线混部尤甚),进程迁移开销增加、吞吐不随 CPU 线性增长------需结合 perf 观察 context-switches。

六、内存优化

6.1 定位

bash 复制代码
free -m                      # 总/已用/空闲/buffers/cached,以及 swap
vmstat 1 5                   # si/so(swap 换入换出)、si/so 高说明内存紧张
cat /proc/meminfo            # MemAvailable、Dirty、Writeback 等
cat /proc/zoneinfo           # 各 zone 水位线,判断是否水位过高
cat /proc/pressure/memory    # PSI:内存 stall 真实程度(较新内核)

判读:

  • 物理内存接近用满 + swap 使用极少:不一定是问题(页缓存可回收);
  • si/so 持续非零:内存真的紧张,性能会因换页显著下降;
  • MemAvailable 才是判断"可用"的关键,不要只看 MemFree。

6.2 优化手段与命令

bash 复制代码
# 1) 调整 swap 倾向(数据库/内存敏感服务常设较小值)
sysctl vm.swappiness
sysctl -w vm.swappiness=10
echo 'vm.swappiness=10' >> /etc/sysctl.conf

# 2) 脏页回写控制(降低突发的写回抖动)
sysctl vm.dirty_ratio vm.dirty_background_ratio vm.dirty_expire_centisecs

# 3) 透明大页(THP)------数据库场景通常建议按需评估
cat /sys/kernel/mm/transparent_hugepage/enabled
# 评估后再决定是否设为 madvise 或 never

# 4) OOM 保护(避免关键进程被杀)
cat /proc/<pid>/oom_score_adj
echo -1000 > /proc/<pid>/oom_score_adj   # -1000 尽量不被 OOM 选中

# 5) cgroup 限制(容器/多租户)
# 通过 memory.max / memory.limit_in_bytes 限制内存上限

6.3 NUMA 优化

bash 复制代码
numactl --hardware           # 看 NUMA 节点与内存分布
numactl --cpunodebind=0 --membind=0 <cmd>   # 绑核绑内存,避免跨节点访问
numastat -p <pid>            # 看进程内存跨节点分布

跨节点访问内存延迟明显更高,内存密集型应用应尽量本地分配。

6.4 注意事项

  • 不要盲目关闭 swap:极端内存压力下没有 swap 会直接 OOM;
  • 调整 swappiness 需结合工作负载,数据库与桌面场景差异很大;
  • 大页开启后要评估是否固定内存、影响内存弹性;
  • 内存"看起来用满"不等于有问题,要结合 MemAvailable、si/so、PSI 综合判断。

七、磁盘 I/O 优化

7.1 定位

bash 复制代码
iostat -xz 1 5               # -x 扩展,-z 隐藏空闲设备
# 关键列:r_await/w_await(平均延迟)、aqu-sz(队列深度)、%util、r/s w/s
iotop -o -d 1                # 按进程看谁在读写(-o 只显示有 I/O 的)
sar -d 1 5                   # 历史/实时设备 I/O
pidstat -d 1 5               # 进程级 I/O

重要判读:

  • %util 在 RHEL9/NVMe/多队列 设备上,IOPS ≥1000 时几乎必然接近 100%,不能用它做绝对阈值;
  • 判断饱和应改用 await(延迟)+ aqu-sz(队列深度)+ PSI io.full 增量。

7.2 下钻到系统调用

bash 复制代码
strace -T -tt -p <pid>       # -T 显示每个系统调用耗时
lsof -p <pid>                # 看进程打开的文件

示例(来自知识库案例) :某应用 I/O 延迟高,iostat 确认存在 I/O 瓶颈后,用 strace 与 lsof 定位到应用正在疯狂写调试日志,调回日志级别即解决。(示例用于说明方法。)

7.3 优化手段与命令

bash 复制代码
# 1) 选择 I/O 调度器(按设备类型)
cat /sys/block/<dev>/queue/scheduler
# NVMe/SSD 常用 none(mq-deadline);HDD 可用 bfq/mq-deadline
echo mq-deadline > /sys/block/<dev>/queue/scheduler

# 2) 预读调整
cat /sys/block/<dev>/queue/read_ahead_kb
echo 4096 > /sys/block/<dev>/queue/read_ahead_kb

# 3) 挂载选项(减少元数据写)
# noatime:不记录访问时间;data=writeback 等按场景评估
mount -o remount,noatime /<mp>

# 4) 文件系统与块大小
# XFS 适合大文件高并发;ext4 通用;按业务选择块大小与 inode 数量

7.4 压测与验证

bash 复制代码
# fio 做定向压测(示例参数,按目标调整)
fio --name=randread --ioengine=libaio --direct=1 --rw=randread \
    --bs=8k --iodepth=32 --numjobs=4 --size=4G --runtime=60 \
    --group_reporting --filename=/dev/<dev>

7.5 注意事项

  • 严重缺页中断(major faults) 也会表现为磁盘 I/O 延迟:内存不足频繁 swap、或首次访问大 mmap 区域时,perf stat 的 major-faults 会升高。此时要解决的是内存,不是磁盘。
  • 用 dd 做磁盘基准要谨慎,oflag=direct 才绕过缓存;
  • 改调度器/预读后务必复测,不同设备最优值差异很大。

八、网络优化

8.1 定位

bash 复制代码
ss -s                        # 连接统计(比 netstat 更快)
ss -antp                     # 连接详情
sar -n DEV 1 5               # 网卡收发包、字节、丢包
sar -n TCP,ETCP 1 5          # TCP 重传、错误
ethtool -S <nic>             # 网卡硬件计数(drop/error)
cat /proc/net/dev            # 接口统计原始数据
cat /proc/net/softnet_stat   # 软中断/backlog 丢包

判读:

  • 丢包通常意味着网络拥塞和重传,会放大延迟、降低吞吐;
  • 区分是网卡驱动丢包 (ethtool -S 有 error/drop)还是内核协议栈丢包 (softnet_stat 的 dropped);
  • 高并发下内核线程 ksoftirqd CPU 高,通常是网络收发软中断导致。

8.2 下钻

bash 复制代码
# 软中断/内核线程 CPU 高时
sar -n DEV 1 5               # 看流量是否异常
tcpdump -i <nic> -nn ...     # 抓包确认协议行为
# 或用 eBPF(bpftrace/BCC)直接追踪内核函数,比 sar+tcpdump 组合更快定位

8.3 优化手段与命令

bash 复制代码
# 1) 网卡多队列 + 中断绑定(高 PPS 场景)
ethtool -l <nic>             # 查看队列数
ethtool -L <nic> combined <N>
# 将各队列中断分散绑定到不同核

# 2) RPS/RFS(单队列网卡分摊软中断到多核)
echo <mask> > /sys/class/net/<nic>/queues/rx-0/rps_cpus
echo <flow_entries> > /sys/class/net/<nic>/queues/rx-0/rps_flow_cnt

# 3) 协议栈与队列参数(按场景评估)
sysctl net.core.somaxconn
sysctl net.core.netdev_max_backlog
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.ipv4.tcp_tw_reuse
sysctl net.ipv4.tcp_fin_timeout

8.4 注意事项

  • RPS 并非总是提升吞吐:开启后可能因跨核缓存失效反而下降,需实测;
  • 中断/软中断绑核要避开业务核,否则会与业务争抢;
  • 调 TCP 参数前先确认瓶颈是带宽、连接数、还是延迟,三者调法不同;
  • 注意 RHEL7 内核(3.10) 上高负载读取 /proc/net/sockstat 可能延迟本 CPU 网络软中断,监控脚本应避免高频读取。

九、常见误区与注意事项(重点)

误区 真相 正确做法
负载高 = CPU 不够 负载含 D 状态,可能在等 I/O 结合 iowait / PSI 判断
%util 100% = 磁盘打满 RHEL9/NVMe 上几乎必然 100% 用 await/aqu-sz/PSI io.full
wa 下降 = I/O 变好 可能只是换了更快的设备 看 r_await/w_await 绝对值
free 可用内存少 = 内存不足 页缓存可回收 看 MemAvailable + si/so
跨版本工具数值应一致 不同版本口径/实现不同 同版本内比较,或读源码确认
%util、wa 可直接设阈值告警 与核数/设备强相关 用复合判据与增量趋势

版本差异提醒(RHEL7/8/9 常见):

  • free 与 sar -r 的可用内存:RHEL7 无内核 MemAvailable,两工具回退策略不同,数值对不上是预期;
  • load average:新版内核把 TASK_IDLE 的 kworker 排除出负载,升级后负载"下降"不一定是变好;
  • %pK 日志显示 (ptrval):是新内核指针哈希策略,不是权限问题。

十、优化检查清单

动手前

  • 明确优化目标(延迟?吞吐?资源占用?),有可量化的基线
  • 保留历史监控数据,便于对比
  • 确认瓶颈层,不跨层乱调

CPU

  • us 高 → 火焰图找热点函数,优化算法/内存分配
  • sy 高 → 看系统调用、上下文切换、锁
  • 单核瓶颈 → 评估并行度、中断绑核

内存

  • 看 MemAvailable,不只看 MemFree
  • si/so 持续非零 → 内存紧张
  • NUMA 绑定,避免跨节点

I/O

  • 用 await/aqu-sz/PSI 判饱和,不用 %util 阈值
  • iotop/strace 定位高 I/O 进程与文件
  • 调度器、预读、挂载选项按设备实测

网络

  • 区分网卡丢包与协议栈丢包
  • 软中断集中时评估多队列/RPS/RFS
  • TCP 参数按瓶颈类型调整

收尾

  • 每次只改一项,改后复测
  • 记录变更与回退方案
  • 关注副作用(实时调度、绑核、关 swap 的风险)

十一、小结

  1. 先定位,后优化:用"负载 vs 利用率、延迟 vs 吞吐、设备 vs 进程 vs 调用栈"三层下钻,把瓶颈层找准。
  2. 按四层框架动手:应用 → 语言框架 → 内核 → 硬件,逐层收益递减,别本末倒置。
  3. 警惕指标陷阱 :%util、wa、load、free 都有各自的适用边界与版本差异,复合判据 + 增量趋势比单一阈值可靠。
  4. 小步验证:每次只改一项,改后复测,保留回退。

性能优化不是一次性的"调参",而是一套可复现的定位---优化---验证闭环。工具会随内核版本演进,但"从全局到局部、从现象到根因"的方法论始终适用。