简介:一份面向Linux运维及系统管理人员的性能调优方法总结文档,聚焦网络与磁盘两个关键子系统。文档以实用内核参数与工具为主线,涵盖TCP同步Cookie、窗口缩放、缓冲区调整、端口范围扩展等网络优化配置,并给出写入sysctl.conf与生效命令;磁盘部分则介绍noatime挂载选项、hdparm基准测试及文件系统选择策略,可直接用于服务器环境排查与配置参考。资源为单个docx文件,共1份文档,压缩包大小51KB,内容结构清晰,兼顾原理说明与操作命令,适合初中级运维者快速上手。该文档已有216人学习下载,可在实际调优时作为速查笔记使用。
1. 调优不是调参:先搞懂 LINUX 性能问题的分布规律
接手一台 LINUX 服务器,最常见的情况是:业务说慢,但 top 一开 CPU 好像也没满,load average 却飘在核数边界上。性能调优方法总结这类标题的资料很多,可真照着抄 sysctl.conf 的人大多翻车了,因为瓶颈往往不在 CPU 主频,而在某个看不见的排队环节——磁盘队列、网络 backlog、内存回收、锁竞争。这篇文章要讲的是怎么把「LINUX 性能调优」从玄学变成可复现的操作:先诊断定位,再逐项调整 CPU、内存、文件系统与网络参数,最后用压测和火焰图验证收益。适合给跑业务的服务器做一次系统化体检的运维和开发,也适合嵌入式 Linux 上做资源受限场景调优的同学。
2. 先诊断后动手:用 USE 方法定位 CPU、内存、IO 与网络瓶颈
性能调优最大的禁忌是一上来就改参数。系统是个黑匣子,你不先搞明白它在等什么,改什么都像在猜。常见做法是先按 USE 方法(利用率、饱和度、错误)从资源利用率、排队情况和错误日志三个维度过一遍:先看每个资源有没有在排队,再看到底被用掉了多少,最后查有没有硬错误。linux 常用命令里能完成这件事的,其实就十来条。
2.1 uptime 与 load average:单核 1.0 到底意味着什么
先执行 uptime,它给出了 1 分钟、5 分钟和 15 分钟三个 load average 值。很多人把 load 直接理解为 CPU 使用率,这是最常见的误读。load 统计的是运行队列里可运行任务加上不可中断任务的数量,也就是说它既包含排队等 CPU 的任务,也包含在等磁盘 IO 的任务。一个进程卡在磁盘读写上,同样会把 load 顶起来。
uptime cat /proc/loadavg/proc/loadavg 的第一段数字就是 1 分钟 load,跟 uptime 一致;第四段是当前运行任务数/总任务数,最后一个是最近创建的 PID 号,可用来粗略判断进程创建频率。
判断经验值:单核机器 load 到 1 就已经满载,四核机器 load 到 4 才是满载。一般 load 超过核数的 0.7 就要开始关注,超过 1 说明已经有人在排队。更关键的是看趋势:如果 1 分钟 load 远高于 15 分钟,说明是短时流量冲击或突发任务;如果 15 分钟也一直高,那就是持续性的资源瓶颈,得往下查了。
2.2 vmstat 与 mpstat:CPU 空闲但系统卡顿的假象怎么看穿
vmstat 是定位资源瓶颈的第一现场。vmstat 1 5表示每秒采样一次,连采五次。重点看 r、b、si、so、cs 这几列:r 是运行队列里的任务数,明显大于 CPU 核数说明 CPU 在排队;b 是不可中断睡眠的任务数,持续偏高说明有任务阻塞在 IO 上;si、so 只要非 0,内存换页已经发生;cs 是上下文切换次数,每秒几十万次时基本可以断定有锁竞争或线程频繁唤醒。
vmstat 1 5 mpstat -P ALL 1 3mpstat 用来看每一个逻辑核。多核机器上经常出现一种假象:整体 CPU 使用率不高,但业务就是卡。mpstat 一看,发现某个核已经 100%,其余核空闲——这是典型的单线程热点或中断集中。此时要特别留意 %soft 列,软中断占用过高基本指向网卡收包或磁盘中断集中在单个核上,这就是后面要调的网卡多队列和中断绑核问题。
2.3 iostat 与 sar:把磁盘延迟和软中断排队摊开看
磁盘问题的判断不能只看空闲率。使用iostat -x 1 5 /dev/sda看扩展统计:%util 是设备忙碌时间占比,await 是平均 IO 延迟,avgqu-sz 是平均队列长度。机械盘上 %util 到 100、await 明显升高,基本可以断定磁盘饱和;但在 SSD 和 NVMe 上,%util 到 100 未必是瓶颈,因为多队列并发下设备一直忙是正常的,要结合 avgqu-sz 和真实延迟趋势判断。
iostat -x 1 5 /dev/sda sar -n DEV 1 1 sar -d 1 1sar 的价值在历史数据。故障复盘时最怕没有基线,sar 默认会记录当天和历史的数据,通过sar -d -f /var/log/sysstat/sa$(date +%d)可以倒回去看故障时间点的磁盘和网络情况。没有历史数据,调优就永远只能靠猜。系统管理的基本功里,养成留存 sar 历史是成本最低的一步。
2.4 pidstat 与 perf:从进程热点追到函数级
资源层面的定位只能告诉你哪个资源有问题,真正要回答的是「哪个进程在制造问题」。pidstat 可以把 CPU、内存、IO 的账单精确到进程和线程:
pidstat -u -r -d -p 12345 1 pidstat -wt 1-u 看 CPU,-r 看内存,-d 看读写速率,-wt 看线程级上下文切换。发现某个线程的 cswch/s 特别高,说明它在不停被唤醒和挂起,旁证是锁竞争或队列积压。再往下追热点函数,用 perf:
perf top -p 12345 perf record -F 99 -g -p 12345 -- sleep 30 perf report --stdioperf record 以 99Hz 采样并记录调用栈,30 秒后生成报告。perf 在很多容器环境里需要 CAP_SYS_ADMIN 权限,权限不足时退而求其次看/proc/PID/stack或直接观察线程状态。诊断做到函数级,后面所有调优才有靶子。
3. CPU 与内存调优:绑核、NUMA 与内存回收参数的落地姿势
定位完瓶颈后,真正动手的部分集中在 CPU 调度、内存回收和内核自动机制三个方向。这里的原则是:每个参数都要能说清「解决什么问题、副作用是什么」,说不清的宁愿不动。
3.1 CPU 调频策略与中断绑核:从 performance 到 smp_affinity
先看 CPU 当前的调频策略,主流发行版默认是 ondemand 或 schedutil,它们的共同特点是 CPU 频率随负载动态升降。动态调频省电,但代价是负载上来时频率爬升有延迟,对延迟敏感的服务会造成尾延迟抖动。查当前策略:
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor cpupower frequency-set --governor performance对跑业务的服务端,我一般直接切 performance,让 CPU 保持最高频率;省电场景或虚拟机共享宿主机时保持默认即可。嵌入式 Linux 上同样适用,但要注意部分 ARM 平台的 cpufreq 驱动不开放 scaling_governor 节点,那就从设备树或内核 config 里确认。
中断绑核是另一个高频优化点。默认 irqbalance 会动态均衡中断,但高吞吐网卡场景下它经常把中断在核之间搬来搬去,反而造成 cache 失效。先停掉 irqbalance,再手动绑:
systemctl stop irqbalance cat /proc/interrupts echo 1 > /proc/irq/78/smp_affinity/proc/interrupts 会列出所有中断号及在各个 CPU 上的分布次数,连看两次能看出哪个核在持续增长。smp_affinity 写的是十六进制位掩码:CPU0=1,CPU1=2,CPU0+CPU1=3。绑核的原则是把网卡队列的中断分散到不同物理核,避免全部压到 CPU0。多队列网卡对应多个 IRQ 号,一般建议从 CPU1 开始绑,把 CPU0 留给系统进程。
3.2 NUMA 感知:numactl 绑核绑内存,哪些场景才值得绑
NUMA 架构下,CPU 访问本节点的内存比访问远端内存快 20% 到 30%。多路服务器上跑内存密集型应用时,跨节点访问造成的延迟损失非常可观。先用 numactl --hardware 看清拓扑:
numactl --hardware numactl --cpunodebind=0 --membind=0 ./your-app--cpunodebind=0 把进程绑到节点 0 的 CPU,--membind=0 把内存分配也限制在节点 0。这个组合对数据库、缓存服务这类内存访问模型固定的应用收益明显。但要小心:如果应用的线程数远大于一个节点的核数,绑核会把大量线程挤在部分核上排队,反而拉低吞吐。先看 numastat 确认当前内存命中率,miss 比例高的应用才值得绑:
numastat -cm判断标准不复杂:要么你明确知道应用是内存延迟敏感型,要么 numastat 里 node 之间的 miss 很多。两个条件都不满足时,不要为了绑而绑。
3.3 内存回收三件套:swappiness、vfs_cache_pressure 与 dirty 水位
内存调优最容易抄错的就是 swappiness。它控制的是内核在回收页缓存时倾向于换出匿名页还是回收文件页缓存,默认 30,取值范围 0 到 100。值越大越积极使用 swap,值越小越倾向回收页缓存。数据库类服务我一般调到 10 到 20,完全不想换页的场景调到 1,而不是 0——0 的极端行为会在避坑章节展开说。
sysctl -w vm.swappiness=10 sysctl -w vm.vfs_cache_pressure=100 sysctl -w vm.dirty_background_ratio=5 sysctl -w vm.dirty_ratio=10vfs_cache_pressure 控制内核回收目录项和 inode 缓存的积极程度,默认 100。文件打开密集的服务器(编译节点、代码仓库服务)可以调到 150 以上,让缓存更快回收避免堆积;数据库希望保留更多目录缓存,调到 50 左右更合适。dirty_background_ratio 和 dirty_ratio 是页缓存回写的两级水位:前者到 5% 时内核后台开始回写,后者到 10% 时应用写操作会被阻塞等待回写。对 IO 敏感型服务,这两个值要调低,避免突发写入把磁盘打满;对大批量顺序写场景,反而要调高以提升吞吐。改完看 si/so 和 /proc/pressure/memory,内存压力可视化之后再做下一步调整。
3.4 透明大页与自动 NUMA 均衡:两个默认开启的隐形杀手
透明大页(THP)在部分服务上是纯负优化。khugepaged 线程会持续扫描内存并尝试合并成大页,这个过程本身消耗 CPU;更糟的是大页分配失败时会触发内存规整(compact),产生毫秒级的延迟尖刺。Redis、MySQL、Java 应用都是典型的受害者。
echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag这里有个容易漏的点:只写 enabled 不写 defrag,慢路径分配时仍可能触发 compact,延迟尖刺照样存在。两个都要写 never。部分云主机的内核编译时直接禁用了 THP,/sys 下根本没有这个节点,用grep AnonHugePages /proc/meminfo确认大页数量始终为 0 即可。
自动 NUMA 均衡(numa_balancing)是内核的一种动态页面迁移机制,默认在某些发行版开启。它会周期扫描进程的页面访问,把热点页面搬到更近的内存节点。听起来很智能,但对内存访问模式固定的数据库服务,扫描和迁移本身会带来 CPU 开销和抖动。遇到压测结果不稳、但资源利用率看着正常的场景,可以关掉对比:
sysctl -w vm.numa_balancing=0记住一个原则:内核的自动机制是为通用场景设计的,你的业务如果已经明确了自己的内存访问模型,手动绑定通常比自动迁移更可控。
4. 文件系统与网络内核参数:IO 调度、连接队列与 socket 缓冲怎么调
这一章处理的是最容易出现「照着别家配置抄完更慢」的部分。文件系统和网络的参数与硬件类型强相关,SSD 上合理的设置在机械盘上可能是灾难。
4.1 文件系统挂载参数:noatime 与 ext4 日志模式怎么选
文件访问时间(atime)的更新是历史上被诟病最多的写放大来源。现代内核默认的 relatime 已经把 atime 更新限制为一天一次,但高 IO 写场景下,很多团队仍然会直接关闭 atime 更新:
mount -o remount,noatime,nodiratime /data确认当前挂载参数用findmnt -o OPTIONS /data,改完之后需要固化到 /etc/fstab,否则重启后失效。noatime 节省的是每次读文件时对 inode 的写操作,对读多写少和 IO 密集场景收益直接。
ext4 的日志模式也值得关注。默认 data=ordered 保证数据和元数据的写入顺序,崩溃恢复时一致性最好;data=writeback 只保证元数据日志,速度快一些,但掉电后文件内容可能和元数据不一致。数据库服务器不要动这个参数,缓存类、可容忍丢失场景才考虑 writeback。commit 参数控制日志刷盘间隔,默认 5 秒,调大到 30 秒能减少写放大,但代价是掉电丢更多数据——是否值得由业务容忍度决定,别只看着性能调。
4.2 IO 调度器选型:机械盘与 SSD 在调度器上完全相反
先看当前调度器:
cat /sys/block/sda/queue/scheduler机械盘需要调度器把散乱的请求重新排序,减少磁头寻道,所以用 mq-deadline;SSD 和 NVMe 没有寻道成本,调度器反而增加延迟和 CPU 开销,用 none。虚拟化场景下的云盘一般也是 none 更合适。修改和持久化最好用 udev 规则,因为调度器节点在重启后会重置:
# /etc/udev/rules.d/60-io-scheduler.rules ACTION=="add|change", KERNEL=="sd*[!0-9]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="mq-deadline" ACTION=="add|change", KERNEL=="sd*[!0-9]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="none"rotational=1 代表机械盘,0 代表 SSD;但经过 RAID 卡透传或某些虚拟化平台后,这个属性可能不准,以cat /sys/block/sdb/queue/rotational实际输出为准。机械盘上如果想优化随机读延迟,mq-deadline 的 read_expire 可以从默认 500 毫秒调小到 250,让读请求更早被处理。嵌入式 Linux 场景下,eMMC 和 SD 卡本质上也是 Flash,但很多板子的调度器节点不开放,确认走的是 blk-mq 直通后一般不需要额外设置。
4.3 网络连接队列与 socket 缓冲:somaxconn、syn_backlog 与 tcp_tw_reuse 的真实边界
网络参数是最容易被当成「万能药」抄的一类配置。下面这几项是短连接高并发场景下确实有效的一组,但每一项都有明确的适用边界。
| 参数 | 常见默认值 | 建议值 | 适用场景 |
|---|---|---|---|
| net.core.somaxconn | 4096 | 65535 | accept 队列过短,连接被丢弃 |
| net.ipv4.tcp_max_syn_backlog | 1024 | 65535 | SYN 队列溢出,半连接丢失 |
| net.ipv4.tcp_tw_reuse | 0 | 1 | 客户端主动关闭的短连接场景 |
| net.ipv4.tcp_fin_timeout | 60 | 30 | 短连接 TIME_WAIT 堆积 |
| net.core.rmem_max / wmem_max | 212992 | 16777216 | 大吞吐长肥网络 |
somaxconn 决定 accept 队列长度,应用层 listen backlog 超过它也没用。syn_backlog 是半连接队列长度,短连接冲击时溢出会直接丢连接。tcp_tw_reuse 只对发起连接的一端有效,且依赖 tcp_timestamps 开启,作用是复用 TIME_WAIT 状态的连接,不能减少 TIME_WAIT 本身的数量。tcp_tw_recycle 这个参数在较新内核里已经被移除,不要再去配置它,网上大量旧教程还在推荐,照抄就是埋雷。
sysctl -w net.core.somaxconn=65535 sysctl -w net.ipv4.tcp_max_syn_backlog=65535 sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w net.ipv4.tcp_fin_timeout=30改完用ss -lnt和netstat -s看连接队列溢出计数是否变化,如果 overflowed 还在涨,说明问题不在队列大小,而在应用处理速度。
4.4 内核参数持久化:sysctl.d 与 systemd 里的一次配置,长期生效
运行时用 sysctl -w 改参数只能生效到下次重启。所有内核参数都应该落到 /etc/sysctl.d/ 下独立文件,按字典序读取,数字大的文件后读覆盖先读。我习惯建一个 99-tuning.conf 统一管理:
cat > /etc/sysctl.d/99-tuning.conf << 'EOF' vm.swappiness=10 vm.dirty_background_ratio=5 vm.dirty_ratio=10 net.core.somaxconn=65535 EOF sysctl --systemsysctl --system 会在当前机器上重载所有配置,输出里能看到每一项是否正确生效。但注意 THP、中断亲和性、IO 调度器这些不在 sysctl 管辖范围内,要真正开机自启,需要用 systemd oneshot 服务:
# /etc/systemd/system/irq-tuning.service [Unit] Description=IRQ affinity tuning After=network.target [Service] Type=oneshot ExecStart=/usr/local/sbin/irq-tuning.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target脚本里放绑核和 THP 的 echo 命令,注意写成可重复执行的幂等脚本,然后systemctl enable irq-tuning.service。这类服务在容器或云主机里不一定可用,但物理机、嵌入式 Linux 和自建虚拟化环境下是标准的固化手段。
5. 性能调优避坑清单:五个我踩过且反复出现的翻车点
性能调优的坑比参数本身多。这里挑五个典型的 linux 运维故障案例,每条按现象、原因、解决三部分写,都是我见过不止一次的翻车现场。
5.1 现象:Swap 调成 0 之后,数据库实例被 OOM 杀掉了
一个人人爱调的参数:vm.swappiness=0。现象是业务高峰时数据库进程突然消失,journalctl 里看到 oom-killer 记录。
原因:swappiness=0 表示「尽量避免使用 swap」,而不是「完全禁止」。当内存被页缓存和匿名页占满时,内核优先回收页缓存,但如果回收速度跟不上分配速度,就会触发直接回收甚至 OOM。没有 swap 作为缓冲垫,瞬时内存尖峰直接落在 OOM 上。
解决:保留 2G 左右 swap 作为缓冲垫,swappiness 设在 1 而不是 0;同时监控 /sys/fs/cgroup/memory.events 里的 oom_kill 次数,配合 /proc/pressure/memory 的 psi 数据观察内存回收压力。想让数据库多用物理内存,真正该做的是把数据体积和 buffer pool 算清楚,而不是把 swap 关死。
5.2 现象:echo 改完 /proc/sys 参数,重启后全部归零
测试环境里echo 1 > /proc/sys/net/ipv4/ip_forward配好了转发,重启后失效,依赖转发的业务全部异常。
原因:/proc/sys 只是内核参数运行时视图,任何 echo 写入都不持久。
解决:所有参数写入 /etc/sysctl.d/99-tuning.conf 后执行 sysctl --system 验证。注意 sysctl.d 是按文件名排序覆盖的,统一放 99 前缀避免被其他包覆盖。改完参数必须立即验证生效,不要等重启后再发现。
5.3 现象:打开自动 NUMA 均衡后,延迟抖动比原来还大
在 2 路服务器上开启 vm.numa_balancing,压测发现 p99 延迟不降反升,top 里看到 kcompactd 和 khugepaged 类内核线程在周期性占用 CPU。
原因:内核的自动页面迁移会周期性扫描进程页表并搬移页面,把短生命周期的数据也当成热点处理,迁移开销超过了收益。
解决:对数据库、缓存这类热点固定、内存模型清晰的服务,设 vm.numa_balancing=0 并配合 numactl 手动绑核绑内存;比较开启和关闭的 p99 而不是平均延迟。容器或云主机里看不到这个参数时不要强求,宿主机层面的调度已经替你处理了大部分问题。
5.4 现象:全量把 IO 调度器改成 none,机械盘备份存储先翻车
团队统一执行了「SSD 用 none」的脚本,结果机械盘备份存储的随机读写性能明显变差,备份作业耗时翻倍。
原因:机械盘没有调度器做请求合并和排序,大量随机小 IO 变成无序寻道,磁头来回跑。none 只适合 SSD/NVMe,机械盘必须保留 mq-deadline。
解决:按盘类型区分配置,用 udev 规则根据 rotational 属性自动选择调度器,而不是一个脚本套所有盘。RAID 卡透传的盘 rotational 属性可能不准,用 cat /sys/block/sdb/queue/rotational 逐个确认。调完用 fio 做一轮 4K 随机读对照,以数据说话。
5.5 现象:压测工具自身先达到瓶颈,不是服务不行,是工具不行
用 ab 单进程压本机 Nginx,QPS 卡在 2 万上不去,CPU 没打满,以为是服务上限。后来把压测端换成多线程工具,QPS 直接翻倍。
原因:ab 是单线程模型,高并发下自己成为瓶颈,尤其是短连接场景。压测工具和服务在同一台机器上时,ab 占用的也是服务端 CPU,测出来的上限可能是工具的上限,而不是服务的上限。
解决:压测工具换成 wrk 这类多线程工具,命令参照wrk -t 8 -c 200 -d 30s http://127.0.0.1/;压测尽量分开机器。任何压测结论先检查压测端的 CPU 和网络是否已经饱和,压测端没到瓶颈,结果才可信。
6. 验证与迭代:用 perf 和火焰图锁住每次调优的收益
调优里玄学成分不少,但玄学大多来自没有基线的「优化」。我的做法是把每次调整变成一次对照实验:先测基线,再改一个参数,再测一次,中间不混入其他变更。这条流程跑熟了,调优就不再是碰运气。
6.1 用 perf stat 建立可量化的基线
压测前先跑一条 perf stat,把关键指标记录下来:
perf stat -e task-clock,context-switches,cpu-migrations,page-faults,cache-misses ./your-bench重点看 IPC(每条指令的时钟周期数)、cache miss 比例和上下文切换次数。上下文切换特别高,说明锁竞争或线程调度在拖后腿;cache miss 高说明数据访问局部性差或 NUMA 绑核有问题。这组数字是每次调优前后的对照基准,比单看 QPS 更早暴露问题。
6.2 瓶颈定位的闭环:perf record 到火焰图
perf record -F 99 -g -p PID -- sleep 30 perf report --stdioperf report 的输出是调用链统计,把结果交给 FlameGraph 脚本生成 SVG 火焰图,一眼能看到 CPU 时间堆积在哪个函数上。每个参数调整前后各抓一张,对比火焰图里尖条的变化,就能确认改动是否真的解决热点,而不是把 CPU 时间从一处挪到另一处。
我的习惯是每个参数放进独立 sysctl.d 文件,文件名带日期和用途;同一台机器一次只改一个参数;调完至少压测 30 分钟,记录 p99 而不只是平均。这套方法确实慢,但能让你下次面对类似瓶颈时,从变更记录里直接找到可复用的经验,不必再把玄学当直觉。希望帮到你。
本文还有配套的精品资源,点击获取