Linux性能调优全流程:基线采集、瓶颈定位与验证闭环
简介系统性能优化是保障服务稳定性的核心工程它并非靠背几条命令就能解决而是需要一套从观测到决策的完整闭环。通过掌握vmstat、iostat等手段先对CPU、内存、磁盘、网络四层建立基线数据再结合sysctl参数按需调整能够准确区分负载高与CPU跑满、内存不足与free为0等常见误区。这种以数据驱动的方法可帮助运维人员在高并发Web服务、MySQL数据库等典型场景中快速定位瓶颈避免盲目调参带来的新风险。从指标采集、瓶颈定位到落地调整与压测验证结合真实踩坑案例揭示调优中最容易翻车的关键细节为Linux服务器性能优化提供可复用的方法论。1. Linux性能调优方法总结不是背命令是把排查走成一条闭环Linux性能调优方法总结听起来像一份内部文档的名字可真正落到服务器上能救场的从来不是一两句命令而是一整套判断逻辑。我见过不少刚接手运维的同事拿到一台慢机器第一反应就是翻一个sysctl大礼包整体粘贴最后业务没变快反而把swap和文件句柄调出一堆新问题。这里要讲的方法是把性能调优拆成一条闭环先采集基线再定位瓶颈再动手调参最后用数据验证回归。这套做法适合刚接触Linux服务器、手头MySQL或者Java服务总被说慢想自己动手查一查的人。在x86服务器和嵌入式Linux上这条路子都跑得通。2. 建立基线用这些linux常用命令给系统四层画像调优最忌讳上来就改。我自己的习惯是任何一台机器准备做优化先在业务低峰期花十五分钟到半小时把CPU、内存、磁盘、网络四层的数据各采一组落盘保存。后面不管改什么都有这批基线和改完后的数据做对比才知道自己有没有白调。这里不讨论多复杂的监控平台一条linux常用命令链就够了关键是几个命令怎么配合采样参数怎么设。2.1 CPU与负载基线uptime、vmstat、top怎么配合读拿到一台机器我一般先跑uptime看三个负载均值。注意负载是运行队列的线程数和CPU使用率是两回事单核机器负载超过1就说明排队了四核机器负载长期在4以上也要警惕。光看load不具体接着用vmstat看细节。# 每2秒采样一次共60次把CPU、内存、上下文切换一起记下来 vmstat 2 60 /tmp/baseline_vmstat_$(date %Y%m%d_%H%M).log # 批处理模式采样top-b适合落盘记录2分钟内的进程排行 top -b -d 2 -n 60 /tmp/baseline_top_$(date %Y%m%d_%H%M).logvmstat输出的r列是正在运行的线程数b列是阻塞在IO或者锁上的进程数这两个数才是负载高不高最直接的证据。us、sy、id、wa四列则是CPU时间在不同方向上的分配us是用户态sy是内核态wa是等待IO。top的批量模式主要用来留存峰值期间的进程排行事后可以用grep挑出某个时间点占用最高的进程。采样间隔我习惯用2秒而不是1秒因为1秒的采样对CPU的瞬时抖动太敏感2秒可以看到更稳定的趋势。时间窗口至少取60秒如果业务周期是分钟级的就取10分钟甚至更长日志落盘时加上时间戳避免覆盖。负载高和CPU跑满经常被混为一谈这里有个实用的判断法先看vmstat的r列r接近CPU核数说明计算确实饱和r不大但load很高说明排队等IO或锁的情况更突出。到了这一步不要急着改参数先把同样的采样再跑一遍业务高峰期两轮数据放一起看。2.2 内存水位与swapfree、sar、meminfo三层确认内存这块最容易误判。free -h出来发现free列接近0不代表内存真不够因为Linux会把空闲内存尽量拿去做page cache而cache是可回收的。真正要看的指标是available它是内核估算的、在不触发swap的情况下还能分给新进程的内存。# 查看内存概览重点是available而不是free free -h # 每2秒采一次内存水位归档成sar文件便于回头对比 sar -r -o /tmp/sar_mem_sa 2 30 # 查看内存超额分配情况 cat /proc/meminfo | grep -E CommitLimit|Committed_ASsar -r里的kbmemfree和kbmemused反映的是物理内存的分配情况kbcached表示缓存占用。多采几轮后能看到内存水位是平稳还是持续爬升持续爬升说明某个进程在积累内存常见于Java堆或者Python进程的占用膨胀这种问题靠调参数解决不了要回到应用层。CommitLimit和Committed_AS这一对值容易被忽视Committed_AS表示所有进程当前承诺使用的内存总量一旦超过CommitLimit内核在分配时会弹出overcommit拒绝直接导致进程启动失败。swap的实际情况要看vmstat的si和so两列两列长期非零才是真的在换页偶尔闪一下不必紧张。2.3 磁盘IOPS与延迟iostat、pidstat、fio摸底磁盘性能调优最常踩的坑是把%util当成磁盘使用率。%util高到100%不代表磁盘满了它只是采样周期内设备有请求在处理的占比对机械盘接近真实负载但对SSD和NVMe多队列并发下%util到100%很常见带宽和延迟未必有瓶颈。# 扩展模式采集磁盘IO含await、svctm、队列长度 iostat -dx 1 30 /tmp/baseline_io.log # 按进程统计IO读写速率定位是哪个进程在写盘 pidstat -d 2 30 /tmp/baseline_pidio.logiostat -d后面的x必须带上扩展模式下才有awaitIO请求平均处理时间、svctm实际服务时间现在很多文档建议忽略它、aqu-sz平均队列长度。await大于20ms对机械盘来说已经算慢但同样的值在SSD上可能是异常要结合设备的基准能力判断。pidstat -d可以把IO消耗定位到具体进程经常发现写日志的进程才是磁盘瓶颈的源头。如果要主动摸磁盘上限得用fio压测压测方法放到最后一章这里先只做被动采集。提示采集基线期间保证业务负载是稳定的低峰状态否则基线和压测数据都会带噪声后面对比时看不出真实差异。2.4 网络吞吐与重传sar、ss这一对搭档网络层定位瓶颈我先看吞吐再看重传。吞吐高但延迟正常多半是带宽接近上限重传率高才是真的链路出问题比如丢包、缓冲区太小、或者对端处理不过来。# 按网卡统计吞吐量rxkB/s、txkB/s、带宽利用率 sar -n DEV 1 30 /tmp/baseline_netdev.log # 统计TCP层连接与重传 sar -n TCP,ETCP 1 30 /tmp/baseline_nettcp.logsar -n DEV的输出里rxkB/s和txkB/s是网卡收发的速率通过速率除以网卡带宽可以估算利用率。sar -n TCP,ETCP里的retrans就是重传次数结合TCP连接数变化就能判断是不是并发连接把协议栈压垮了。怀疑连接堆积时用ss -s和ss -tan把当前连接状态数出来time-wait状态的连接数量如果到了几万说明短连接请求量非常大这时候考虑的是TIME_WAIT复用和连接队列参数而不是盲目调网卡。网络这一层还有个坑在虚拟化环境里网卡型号显示virtio的话带宽上限要看宿主机配置物理网卡的速度参数参考不了。3. 从指标定位瓶颈区分CPU、内存、IO各自翻车的现场基线数据采集完之后下一步是回答“它到底慢在哪”。这一章把三类最常见的误判写清楚CPU跑满与负载高不是一回事内存不足与free为0不是一回事应用层慢与系统瓶颈也不是一回事。只要这三层分清了调优方向就不会偏。3.1 CPU跑满和负载高是两回事us、sy、wa的比例怎么读CPU方向的排查我习惯按这个顺序走。第一步看vmstat的第一行r和b哪个突出第二步看CPU时间分配us、sy、wa谁的比例异常第三步才是看top里的进程排行。如果us一直高于80%说明用户态的进程真的在大量计算去top里找CPU占用最高的进程一般会看到某个Java或者Python进程抢占单核。这时候先确认进程是不是配了合理的线程数而不是马上加机器很多情况下是单线程的循环在空转加核没用要修代码。如果sy占比高到30%以上常见原因是系统调用频繁、锁竞争激烈或者linux进程间通信里的消息队列、共享内存操作太多。用pidstat -w可以看每个进程的上下文切换次数cswch和nvcswch非自愿切换nvcswch高说明线程在抢CPU时间片自愿切换cswch高说明线程在频繁让出CPU往往是锁等待。如果wa持续在20%以上IO就是瓶颈这时候去iostat看哪个设备等待时间最长。关键结论先给到us高的性能问题靠优化业务进程sy高的问题先查锁和IPCwa高的问题回到磁盘和文件系统。把这三类归错位后面改什么都白搭。3.2 内存不足的现场不是free0swap与OOM才是实锤在Linux上内存不足的实锤证据是两个swap在换页或者OOM killer在杀进程。free等于0只表示没有多余的page cache可以再分配不代表内存紧张。真正要盯的是available这个数它低于总内存的10%并且还在下降才说明需要干预。# 查看是否发生OOMdmesg和日志文件都看一眼 dmesg -T | grep -i -E out of memory|oom grep -i -E out of memory|oom-killer /var/log/messages | tail -20OOM日志里会列出被杀的进程名、内存占用和oom_score但被杀的往往不是内存占最大的一方而是oom_score_adj调整后“性价比最高”的那个这也是很多人看到日志后觉得冤枉的原因。内存不足还有一种隐蔽现场available没有暴跌但进程启动时报内存分配失败这时候查CommitLimit和Committed_AS确认是不是overcommit策略导致的分配拒绝。判断是物理内存不够还是某个进程泄漏方法很简单把available、used、cache三列连续观测30分钟。cache保持稳定、available缓慢下降是有一个进程在积累内存cache大幅波动、available跟着波动是正常的缓存回收行为不用处理。遇到前一种情况用top按RES排序找到那个进程再回到应用日志去查它的内存设置。3.3 应用层背锅还是系统背锅mysql性能调优是绕不开的例子系统指标都正常业务还是慢这类问题最容易让调优变成玄学。我遇到最多的场景是一台装了MySQL的Linux服务器被反馈查询变慢。这里有一条很实用的顺序先查慢查询日志再查InnoDB状态最后才考虑系统层。-- 在MySQL客户端里开启慢查询日志超过2秒的SQL会被记录 SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 2; -- 查看当前慢查询数量 SHOW GLOBAL STATUS LIKE Slow_queries;如果慢查询日志里出现了大量单条SQL把这条SQL的explain执行计划翻出来看90%的情况是某个字段没走索引或者join的关联字段字符集不一致这属于应用层问题内核参数怎么调都不会有改善。如果慢查询数量不多但系统里的iowait偏高那就要去看InnoDB的缓冲池命中率用SHOW GLOBAL STATUS中的Innodb_buffer_pool_reads和Innodb_buffer_pool_read_requests两个值相除命中率低于95%时buffer pool太小调整innodb_buffer_pool_size比调vm.swappiness有效得多。这里要注意的是OS层的内存调优和MySQL自身的内存调优经常被混在一起。OS层的内存优化目标是让缓存更合理MySQL层的内存目标是把热数据留在buffer pool里两层应该是配合关系而不是互相替代。步步排查的顺序不能反先看SQL再看buffer最后看系统。提示慢查询日志长时间开着有性能开销排障期间开排完记得关。4. 落地调整从内核参数到文件系统、调度器与资源上限瓶颈定位清楚了这才轮到动手改。我在生产环境上的调整顺序固定为先内核参数再文件系统再资源上限最后是服务自身的配置。这个顺序的考虑是改动的影响范围从小到大出了问题也好回滚。每次只改一组参数别一口气把网上的配置大礼包全倒进去。4.1 sysctl参数按需调一张能直接对照的参数表sysctl是内核参数的入口但修改前必须看一眼当前值不同内核版本的默认值差异很大。比如net.core.somaxconn在旧内核默认128新内核可能是4096如果盲改可能把本来就挺合适的配置改乱了。# 先查当前值再决定改不改 sysctl vm.swappiness net.core.somaxconn net.ipv4.tcp_max_syn_backlog sysctl -a | grep -E somaxconn|tcp_max_syn|rmem_max|wmem_max确认要改之后统一写在/etc/sysctl.d/下面的独立配置文件里不要直接改/etc/sysctl.conf。两个文件的加载顺序有优先级写在独立文件里既清楚又可以随时单文件回滚。参数常见默认值调整方向适用场景vm.swappiness6030左右内存充足、避免频繁换页的服务器net.core.somaxconn12840961024Nginx/Java入口的高并发连接队列net.ipv4.tcp_max_syn_backlog102420488192短连接请求量大、SYN排队net.core.rmem_max2129924M16M高带宽网络传输net.core.wmem_max2129924M16M高带宽网络传输net.ipv4.tcp_tw_reuse1或2确认按需开启TIME_WAIT连接堆积明显时上面的“常见默认值”在不同内核上会变拿到机器先sysctl -a确认再决定调不调。vm.swappiness从60调到30不是说30就不换页了只是降低内核把匿名页换出去的倾向缓存回收仍然正常进行。somaxconn决定的是每个监听socket的accept队列能排多长队列满了之后新连接会被内核直接丢弃这就是高并发时客户端连不上的一种典型现场。tcp_max_syn_backlog管的是SYN半连接队列在突发连接时如果大量SYN重传这个数加大的收益更明显。这几个参数的共性是改错不会让服务崩掉但行为会变得难以解释比如连接建立变慢、握手成功率下降回头排查的代价比改的时候大得多。4.2 文件系统与IO调度器noatime、barrier、none/mq-deadline文件系统层的优化收益最稳的是挂载选项。默认挂载下文件系统会在每次读取文件时更新atime访问时间这对读多写少的场景是不必要的IO开销noatime可以关掉它。# 看当前挂载选项重点确认是否已带noatime mount | grep -E /data|/ # 修改/etc/fstab中对应分区的挂载参数加上noatime,nodiratime后重新挂载 mount -o remount,noatime,nodiratime /datanoatime和nodiratime对数据库的数据文件目录、日志目录效果最明显尤其是MySQL的binlog、innodb数据文件这类随机读频率高的路径。修改fstab时注意把挂载选项用逗号拼接每个选项只出现一次。对于SSD设备日志型文件系统可以优先考虑定期fstrim而不是实时discard实时discard在部分老内核上会引入额外的写放大。IO调度器这块争议最多先说结论机械盘场景用mq-deadlineSSD保持noneNVMe上不要手动改。新内核里blk-mq框架已经接管了多队列设备传统调度器在NVMe上基本是摆设手动改成noop反而可能干扰内部调度逻辑。# 查看当前调度器 cat /sys/block/sda/queue/scheduler # 临时切换为mq-deadline写入立即生效重启失效 echo mq-deadline /sys/block/sda/queue/scheduler嵌入式linux上如果跑的是eMMC或flash调度器用none比较常见因为这类存储没有机械寻道合并算法省不了多少。但嵌入式场景更值得关注的是文件描述符和内存上限这个放到下一节一起说。4.3 连接与进程资源上限ulimit、systemd、线程数一起调资源上限是Linux性能调优里最容易被忽略、也最容易翻车的一层。表现很典型服务日志里报too many open files或者线程创建失败但top显示CPU和内存都很空闲。这类问题十次有九次是文件描述符上限撞了。# 临时生效把当前shell的上限提高到1048576 ulimit -n 1048576 # 永久生效写到limits.conf对登录会话有效 cat /etc/security/limits.conf EOF * soft nofile 1048576 * hard nofile 1048576 EOF注意limits.conf只能影响登录会话和通过pam登录启动的进程用systemd托管的服务完全不看这个文件。如果你改完limits.conf发现Java服务还是报句柄不够要去服务的unit文件里单独配置。[Service] LimitNOFILE1048576 LimitNPROC65536LimitNPROC对应的是进程数上限高并发下每个连接不一定是一个进程但线程池场景下还是要给足。systemd服务配置改完执行daemon-reload再restart配置才生效。嵌入式linux上还有个常见坑默认的ulimit通常只有1024跑网络服务上来就撞句柄可以写一个启动脚本在服务拉起前统一用ulimit调高或者直接改busybox的编译默认值。容器里的服务还要看cgroup的pids.maxhost上的ulimit再高也没用要去容器编排层调pids限制。提示生产环境改动前先备份原值改动窗口放到低峰期预留回滚命令。5. 调优避坑五个常翻车的地方每个都是付过学费的这一章专门写踩坑记录。每一条我都实际遇到过按现象、原因、解决三步记录下来能帮你在调优路上少走弯路。5.1 sysctl配置重启后丢了一半sysctl -p还报错现象改完/etc/sysctl.conf执行sysctl -p提示某个参数路径不存在重启后部分配置没加载。原因一个参数在当前内核版本根本不存在或者名字改了。不同内核版本对一些参数做过重命名和迁移网上教程里的名字直接贴过来就会踩这个坑。另一个隐藏原因是同时改了sysctl.conf和sysctl.d下的文件systemd加载时d目录优先级更高悄悄覆盖了conf里的同名参数。解决所有修改统一放/etc/sysctl.d/99-performance.conf一个文件管到底。改之前先用sysctl -a确认参数在这个内核上真实存在改完执行sysctl --system再sysctl -a | grep验证实际值别只看配置文件里写了什么。5.2 把vm.swappiness调到0高峰期服务反而OOM现象为了不让Linux用swap把vm.swappiness设为0业务高峰期进程被OOM killer杀掉日志里能看到out of memory记录。原因vm.swappiness0不是完全不换页只是让内核极大程度避免回收匿名页宁可回收page cache。当业务突发需要大量内存内核回收cache的速度跟不上又没及时把匿名页换出分配压力直接触发OOM。这个现象在内存大、业务有突刺分配的节点上出现过不止一次。解决服务器场景把vm.swappiness调到10~30留一点换页能力给内核兜底。不要迷信0这个配置真要防OOM给关键服务配上systemd-oomd或者earlyoom做预警比把一个内核参数调绝更靠谱。5.3 NVMe上手动改IO调度器延迟不降反升现象给NVMe设备改成noop调度器用fio测P99延迟反而比默认更高。原因现代内核用blk-mq管理多队列设备NVMe默认就是none调度器也就是不经过传统调度器的排队逻辑。手动改成noop等于把设备又拉回旧模型打乱了内核原本的多队列分发延迟变大是必然的。解决NVMe设备保持none不做任何修改。如果真觉得IO延迟有问题先看队列深度和fio压测参数再检查是不是热电节流或固件问题跟调度器没关系。盲改调度器是这段调优经验里浪费最多时间的一次。5.4 ulimit -n明明改了服务还报too many open files现象终端里ulimit -n显示的已经是1048576Java服务启动后照样报too many open files。原因服务不是由你的shell启动的而是systemd拉起的。systemd服务进程不会读取shell的ulimit也不会读取/etc/security/limits.conf它只认unit文件里的LimitNOFILE字段。另外容器里的进程还要看cgroup的pids限制host上改高了容器里不一定生效。解决确认服务由谁托管systemd服务在unit文件的[Service]段写LimitNOFILE1048576改完daemon-reload并restartDocker容器在docker run里加--ulimit nofile1048576:1048576K8s里则要调整Pod的resources字段和cgroup pids限制。以后再遇到句柄不够先ps -p -o cmd看进程的启动方式再决定改哪里。5.5 压测机和业务机同一台数据忽高忽低没法解释现象在同一台机器上一边跑业务一边跑压测测出的数据波动大换一个时间点再测结果对不上。原因压测进程本身会抢占CPU、内存带宽和page cache业务进程和压测进程互相干扰。fio这种工具如果不加direct1读测试会命中page cache测出的是缓存速度不是磁盘速度数字虚高一倍都很常见。解决压测放到业务低峰期或者用独立机器做隔离。fio跑磁盘性能必须加direct1绕过page cache写模式加sync1确保落盘sysbench跑CPU压测时固定核数用taskset把压测进程绑到和业务不同的物理核上。数据要能复现压测参数和环境必须保持一致这是验证调优效果的大前提。6. 用数据验证调优压测与复盘形成闭环调优的最后一步不是改完就算而是把数据和前后对比拿出来。我在调优前会先导一份sysctl原始值存档改完再导一份两份diff一下清清楚楚看到了什么。# 调优前的原始值存档文件名带日期方便和调优后diff sysctl -a /tmp/sysctl_before_$(date %Y%m%d).conf # 调优后再导一份diff两个文件看差异 sysctl -a /tmp/sysctl_after_$(date %Y%m%d).conf diff /tmp/sysctl_before_*.conf /tmp/sysctl_after_*.conf验证压测我一般分三块CPU和内存用sysbench磁盘用fio网络用iperf3。跑的时候保持调优前和调优后的参数完全一致比如线程数、队列深度、压测时长否则没法对比。# CPU压测16线程跑60秒记录events和总耗时 sysbench cpu --threads16 --time60 run # 磁盘随机读压测4K块、队列深度32、绕过缓存、持续60秒 fio --namerandread --rwrandread --bs4k --size1G --iodepth32 \ --runtime60 --direct1 --numjobs4对比时不只盯平均值我会重点看P99和峰值趋势平均值变好但P99变差说明调优牺牲了稳定性这种调整会被我回滚。养成一个习惯记在笔记本上每次调优只动一组参数记录日期、原值、新值、压测结论两周后回看时这份记录比当时的感觉可靠得多。调优从来不是一次性工程业务模型变了、内核升级了参数都可能要跟着变。希望这套从基线到验证的闭环方法能帮你在下次遇到Linux性能问题的时候不用再靠感觉和运气调参。本文还有配套的精品资源点击获取