凌晨两点监控大屏突然开始连环报警某业务接口的响应时间从80毫秒一路飙到3秒。我爬起来登录服务器uptime一看负载从平时的2.5涨到了12top里有一堆进程挤在D状态。那是我这几年做过的最痛苦的一次性能排查也是后来让我养成了“先打基线再动手调优”这个习惯的开始。这篇文章想聊的就是Linux服务器性能优化这件事。不是让你背参数列表也不是丢给你一百个内核调优项而是把我自己压测、排障、调优时的完整思路和判断逻辑讲清楚——怎么快速定位瓶颈在CPU、内存、磁盘还是网络怎么改配置才算有效以及哪些网上流传很广的操作其实是给自己埋雷。适合刚接手服务器运维的工程师也适合那些“top命令会看但不知道下一步该干嘛”的朋友。1. 先摸清底牌没有基线的优化都是耍流氓很多人拿到一台服务器就开始调内核参数这跟没看病历就开药没什么区别。性能优化第一步不是改配置而是先搞清这台机器正常时候长什么样异常的时候又长什么样。1.1 三分钟快速体检从登录开始做些什么我登录服务器后习惯先跑一组基础命令不看别的就看这五个数uptime top vmstat 1 5 free -m df -huptime给出1分钟、5分钟、15分钟的负载均值。这里有个基本判断逻辑如果1分钟负载明显高于15分钟说明系统正承受短时压力如果三个数都居高不下说明问题已经持续很久了。单看负载还不够还要配合top里的CPU百分比看这个负载是由CPU繁忙还是进程阻塞带来的。vmstat 1 5输出比较有讲究。重点看r运行队列、b阻塞进程数、si/so换入换出、cs上下文切换和waI/O等待。如果b列长期大于0说明有进程卡在I/O或者锁上如果cs高到几万甚至十几万说明系统在上下文切换上烧掉大量CPU。看完这步基本能判断问题的大方向。free -m看内存时我重点关注available这一列而不是free。Linux会把空闲内存拿去做page cache这是一种正常使用不是内存泄漏。1.2 把“峰值”变成基线建立长期监控视角临时体检只能发现问题解决不了“这算不算异常”的争议。后来我要求团队在每台服务器上统一部署sysstat让sar定时采集历史数据# Debian/Ubuntu apt install sysstat # RHEL/CentOS yum install sysstat装好后确认/etc/cron.d/sysstat里的采集周期开着一般10分钟一次足够。这样做的价值在于线上出问题时可以直接用sar -q看当时的负载、用sar -r看当时的内存、用sar -d看当时的磁盘I/O而不需要问业务“当时到底发生了什么”。这个习惯帮我们排查过好几次诡异故障。有一次某服务高峰期必抖开发怀疑数据库慢查询结果sar历史数据显示数据库服务器CPU和磁盘都很稳反倒是应用服务器的plr运行队列长度在P99时段明显上涨最终定位到了应用线程池配置问题。没有历史数据这种跨天对比做不出来。给个建议每台机器至少保留30天历史数据。如果你的机器超过100台还是把数据集中起来比较好不然单机查起来也很痛苦。2. CPU优化用户态与系统态的区分才是最值钱的判断CPU问题是最常见的性能瓶颈也是最容易被误解的指标。很多人一看到top里CPU 100%就喊“CPU爆了”但100%可能发生在us用户态也可能发生在sy系统态这两个的调优方向完全不一样。2.1 用mpstat和pidstat把问题定位到具体核、具体进程top只是全局视角进一步就得细分。我常用的两个工具是mpstat和pidstatmpstat -P ALL 1 pidstat -u 1 5mpstat -P ALL 1能告诉你每个CPU核的使用情况如果只有个别核跑到100%而其他核很闲多半是单线程热点、中断绑核不均或者锁竞争。如果所有核一起满载那就要考虑整体算力不足或者发生了死循环。pidstat -u 1 5可以看到具体进程的CPU消耗。注意看%usr和%system两个子项%usr高说明是业务代码在算%system高说明进程在内核态消耗CPU比如频繁系统调用、收发网络包、读写文件。我曾经遇到过一个日志框架在高IOPS下每秒产生上万次write系统调用%sy直接飙到40%整个服务的吞吐被拖垮换了一个异步日志组件后%sy降到5%以下。2.2 上下文切换高系统态一定不低vmstat里的cs列如果持续超过5万这里面一定有可以优化的东西。上下文切换分两类线程切换和中断处理。线程切换需要加锁、切栈、刷新cache成本很高。最常见的上下文切换杀手有两个一是线程池开得太大几百个线程在锁上互相等待二是业务代码里的自旋锁或者频繁的sleep(0)之类的写法。排查方法pidstat -w 1 5这一步会显示每个进程的cswch/s自愿切换和nvcswch/s非自愿切换。nvcswch/s猛增基本可以断定线程数量多且竞争激烈。我遇到过一个案例某服务的线程池配置了400个线程实际上并发只有几十结果大量线程在锁上排队。把核心线程数压到和CPU核数相当之后吞吐量反而翻了一倍。线程不是越多越好这是CPU优化里最容易踩的坑。2.3 用户态CPU高别急着加机器先找热点函数us高但整体负载还没爆的时候别急着扩容。用perf top看内核里的热点函数能帮你判断代码到底在忙什么。perf top如果你的进程是Java的优先用async-profiler这类工具采集火焰图如果是C/C直接用perf record/report定位到具体函数再决定怎么改。有一次我们压测某同步接口perf显示热点在一个字符串序列化函数上改成更高效的序列化库后CPU消耗降了40%。这种优化比盲目加机器有用得多。调整进程优先级也有应用场景对延迟敏感但有备份性质的服务比如日志采集、离线计算任务可以用renice降低它的CPU优先级保证主业务在资源紧张时优先获得调度renice -n 10 -p pid但优先级只影响调度顺序不影响配额。如果你希望硬性限制某个进程最多只能用2颗核配合systemd配置更稳妥[Service] CPUQuota200%CPUQuota200%代表最多使用2个完整CPU核心。这个在混部场景下很好用能防止某个失控进程把整机CPU打满。2.4 软中断与RPS一个经常被忽略的方向top里如果si软中断长期偏高比如超过10%就要关注是哪个中断在消耗CPU。可以看cat /proc/interrupts网卡收发数据包会产生大量中断如果IRQ都绑定在同一个核上那个核就会被打满。现代网卡一般支持多队列配合irqbalance或者手动把IRQ分散到多个核能明显改善多核利用率。如果网卡不支持多队列还可以用内核的RPSReceive Packet Steering机制把收包软中断均匀分发到各个CPUecho f /sys/class/net/eth0/queues/rx-0/rps_cpus这个f是把第0到3号CPU都放进接收队列二进制1111。具体掩码要根据你核数来算别照搬。改完后用mpstat -P ALL 1重新观察很直观。3. 内存与Swap别一看到缓存高就想去“清理”内存优化是误操作重灾区尤其那些到处流传的“清理缓存”命令。先理解清楚Linux的内存管理不是你想象的“空闲内存越多越好”。3.1 正确读内存available才是真正可用内存先解释一个概念top里的RES常驻内存是进程实际占用的物理内存VIRT虚拟内存是进程映射的虚拟地址空间通常远大于RES。真正决定一个进程能否启动的是系统还有多少available内存。Linux尽可能把空闲物理内存用来做page cache磁盘读过的文件都缓存在里面下次命中就直接从内存读开销小得多。所以free显示free只有几百MB、但buff/cache很大时这是正常状态不要慌张。我见过有同事一看free不足就开始“清理缓存”这其实是在损害性能。page cache是性能加速器不是负担。等到真有内存压力时内核会自动回收cache来满足新分配。只有一种场景需要手动drop_caches就是做了某些基准测试需要把页缓存清空以测量冷启动性能echo 3 /proc/sys/vm/drop_caches注意生产环境没事别这么干。3.2 Swap不是洪水猛兽关键是别让正常进程被换出Swap用了一点不代表内存就不够了。真正的问题是你的活跃进程是不是被换到Swap里去了。如果swappiness设得太大默认60内核在内存还有富余时也可能倾向于把冷页换出。对于数据库、缓存类应用建议把vm.swappiness调低sysctl vm.swappiness10这个值越低越偏向于保留匿名内存但不要直接改成0因为在极端内存紧张时Swap可以挽救整个系统免于直接OOM。检查有没有进程被换出可以不停vmstat看si/so列。如果si和so经常大于0说明系统一直在做换入换出这种抖动非常伤性能。这种情况下杀掉一些不用的进程或增加内存才是根本解法。3.3 OOM Killer的判定与规避当内存耗尽内核OOM Killer会按oom_score选一个进程杀掉。问题来了很多时候OOM杀掉的不是内存最大的进程而是分数最高的分数受进程优先级、运行模式影响。遇到OOM先看证据dmesg -T | grep -i oom这里能看到是哪类进程触发了OOM、当时的分配请求是多少。如果单体进程内存本身就超过物理内存说明是要扩容如果是由于另一个进程把内存吃光导致的无差别误杀那就要先处理“元凶”。给Java应用配置-Xmx时我习惯留出30%的余地给堆外内存和线程栈很多OOM其实是堆外内存爆炸单纯调-Xmx没用。还有一个操作很多人忽略为关键进程设置systemd的内存上限[Service] MemoryMax4G这样即使某个进程失控系统也能把它限制在配额内不会拖垮整机。3.4 内存问题排查方向从RSS和线程数入手如果观察到一个进程内存持续上涨先分清是物理内存增长还是虚拟内存增长。ps aux里的RSS列是物理内存VSZ是虚拟内存。线程栈引起的虚拟内存增长一般无害物理内存增长才是问题。排查物理内存增长可以从三处入手是否有线程创建过多每线程默认栈大小就是8MB的虚拟地址空间、是否有堆外缓存、是否有内存池不在GC范围内。Java应用出现RSS比-Xmx明显大时多半是堆外或DirectByteBuffer这种情况换一个更大堆的机器或调小堆外容量才是正途。我遇到过一个比较隐蔽的内存泄漏某个基础库的缓存Map只进不出也没人去查内存慢慢从2G涨到7G再OOM。最后是靠监控RSS趋势发现的。所以强调一下内存问题必须要有趋势监控不能只看一时快照。4. 磁盘I/Oiostat是个好工具但别用错指标磁盘I/O出问题时的表现很典型top里的wa很高很多进程处于D状态接口响应时间明显变慢但CPU使用率其实不高。这种“CPU闲着、系统却在等待”的状态多半是I/O瓶颈。4.1 iostat的正确读法先跑iostat -x 1输出里有几个关键列指标含义判读经验%util磁盘忙碌时间占比对机械盘接近100%很危险对SSD并行能力强100%也不代表一定慢await平均I/O响应时间含排队和实际服务时间超过20ms一般就有感知SSD应在1ms附近svctmI/O设备服务时间排队时间长而svctm短说明是请求太多avgqu-sz平均队列长度持续大于设备并发能力的2倍已经过压r/s, w/s每秒读写次数结合块大小能算出真实吞吐真正要盯的是await和avgqu-sz不在于%util是不是100%。有次我们压测一个基于固态硬盘的数据库%util长期95%以上但await只有2ms请求延迟体验完全没有劣化。当时如果只看%util就会误判为磁盘瓶颈。如果await高而svctm很低说明瓶颈在请求堆积而不是磁盘硬件本身。这时优化方向应该是减少I/O次数加缓存、合并写请求、减少日志同步频率。4.2 挂载参数与文件系统策略文件系统层面的优化很多人容易忽略。默认挂载选项一般是relatime每次读文件会更新访问时间对高IOPS场景有一定开销可以改成noatime# /etc/fstab /dev/sdb1 /data ext4 defaults,noatime 0 2日志模式也值得注意。Ext4默认dataordered保证元数据和数据的一致性但对写性能敏感的业务可以评估是否使用更激进的方式不过这个要确认业务能否容忍部分异常情况下数据丢失别盲目改。4.3 脏页回写与I/O调度器内核写数据会先落到page cache再由后台线程刷到磁盘。两个参数控制回写节奏sysctl vm.dirty_ratio20 sysctl vm.dirty_background_ratio10dirty_background_ratio是后台开始回写的阈值dirty_ratio是同步阻塞写的阈值。跑批量写任务时如果看到top里偶尔卡一下、随后wa飙高往往是dirty_ratio太小、大量脏页堆积后触发同步刷盘造成的。可以适当调低dirty_background_ratio让回写更平缓避免突刺。I/O调度器在机械盘和SSD上的选择策略也不一样。机械盘推荐mq-deadline或bfqSSD/NVMe可以直接用none即noop不做调度减少无意义合并开销。查看和切换cat /sys/block/sda/queue/scheduler echo none /sys/block/sda/queue/scheduler生产环境修改请写入udev规则持久化不要只改运行时。4.4 用fio做一次规范的磁盘压测调优前先知道磁盘本身的性能上限。fio是最常用的御三家工具之一一套典型配置fio --namewrite_test --rwwrite --bs4k --size1G --numjobs4 --runtime60 --ioenginelibaio --iodepth32 --direct1direct1是为了绕过page cache直接测试真实磁盘能力。分别测4k随机读、4k随机写、128k顺序读、128k顺序写得到的IOPS和带宽就是这台机器的硬指标。后面的所有优化效果都要和这个基线对比。我之前测过一台云主机4k随机读只有8000 IOPS换一块云盘后到5万。业务没改一行代码延迟降了几个数量级——硬件选型有时候才是最大的优化空间。5. 网络与内核参数改动之前先读懂这几个很多网上的“性能优化命令大全”喜欢一次性丢几十个sysctl参数里面有一半根本不适合你的场景甚至有的还埋着坑。网络相关的内核参数其实是把双刃剑。5.1 TIME_WAIT多先别急着开tcp_tw_reuse连接数高的服务比如Nginx、网关经常出现大量TIME_WAIT连接。看到这个很多教程让你开net.ipv4.tcp_tw_reuse1但这需要理解前提。tcp_tw_reuse只对主动关闭方有效且主要解决的是“主动连接方发起新连接时重用TIME_WAIT”不是万能钥匙。更糟糕的是tcp_tw_recycle在高版本内核已经移除老版本开启后很容易造成NAT环境下连接被随机丢弃网上很多“优化后服务间歇性不可用”的案例就是它干的。我的建议是tcp_tw_recycle坚决不碰tcp_tw_reuse在明确是客户端主动发大量短连接的时候再考虑。更多时候TIME_WAIT多并不是性能问题而是连接关闭策略问题。优化方向应该是让服务端合理开启keepalive复用连接减少短连接频次。看连接状态ss -s这个命令会汇总各状态连接数比netstat更高效。5.2 队列长度与握手保护瞬时高并发时如果ss -lnt看到Send-Q积压或者客户端频繁出现连接超时需要看两个参数sysctl net.core.somaxconn4096 sysctl net.ipv4.tcp_max_syn_backlog8192somaxconn是每个监听socket的accept队列上限像Nginx这种高并发服务必须调大tcp_max_syn_backlog是半连接队列上限承接SYN洪水时的缓冲。调完记得在Nginx配置里也把listen的backlog同步调大两边不管哪边不够都白搭listen 80 backlog4096;5.3 文件描述符上限最容易被“连接数上不去”卡住网络连接数上去的前提是文件描述符够用。很多人只调了ulimit -n但系统级的fs.file-max没改照样受限。查看当前打开文件数上限cat /proc/sys/fs/file-max ulimit -n进程级临时调大ulimit -n 65535持久化写法编辑/etc/security/limits.conf加一行* soft nofile 65535 * hard nofile 65535如果用的是systemd管理的服务还要在service文件里加LimitNOFILE65535否则limits.conf对systemd服务不生效。这个坑我踩过两次调了半天防火墙结果就是fd不够。5.4 软中断多核分散与网卡队列第2章提过RPS这里补充一下高性能网关类服务通常建议开启网卡多队列。用ethtool -L查看当前队列数ethtool -l eth0如果Combined只有1而这个网卡硬件支持多队列可以调大ethtool -L eth0 combined 4配合RPS或者硬件RSS可以把网络收包中断分散到多核。实测中单队列网卡高流量时某个核si飙到50%甚至更高开启多队列后全局CPU使用率反而下降因为不再有一个核在疯狂处理中断。6. 一次典型性能事故的排障复盘从报警到定位再到落地方案纸上谈兵再多不如完整复盘一次真实的排查过程。这个案例我改写了细节但排查链路是原汁原味的希望能帮你建立“判断顺序”。6.1 现象高峰期接口响应变慢CPU却不高某内部系统的网关服务每天下午3点开始接口P99延迟从50ms涨到800ms。数据库CPU正常应用服务器CPU也不高但用户已经明显感知卡顿。一开始团队怀疑是数据库慢查询加索引后没有改善才转到我这边。接手后我先跑了top确认了CPU不高、load却比平时高出一倍。紧接着vmstat 1 5发现b列有大量进程处于阻塞状态同时wa到了30%。直觉判断是I/O或者锁。先用iostat -x 1看磁盘%util居然很低await也正常。这说明不是物理磁盘的问题。那是什么进程被什么阻塞了6.2 排查链路文件锁、网络、还是线程池耗尽我用pidstat -d 1查看进程I/O发现有个进程的kB_wr/s并不高但它持续处于D状态。D状态代表不可中断睡眠要么是I/O等待要么是内核内等待。再进一步查看/proc/pid/stack和/proc/pid/status里的voluntary_ctxt_switches发现切换数极速增长。我意识到这不是物理I/O而更像是某种锁等待。切换到sar -n DEV 1看网络流量发现这个网关的收包速率并不高但/proc/net/softnet_stat里的dropped字段在持续增长。这一下就通了软中断处理不过来网络包在协议栈排队进程等待数据包表现就是D状态。根因是网卡单队列所有收包软中断都压在一个CPU核上。6.3 修复与验证一次改动立竿见影确认多队列支持后执行ethtool -l eth0 ethtool -L eth0 combined 4同时配合RPS设置把收包中断分散到4个核然后再看mpstat -P ALL 1软中断从单核70%变成了4个核均匀分摊。接口P99延迟回落到60ms左右全程没有改一行业务代码。后面的复盘我们做了一件事把所有网关类服务器都加了网卡队列和RPS的持久化配置并在监控里加上了软中断占比的看板防止单核软中断再次成为隐形瓶颈。这个案例让我深刻体会到性能排查的路径比技巧重要top定位方向、vmstat判断状态、iostat排除硬件、最后落到协议栈和中断。7. 这些“优化操作”我劝你别在线上乱试最后写点反面的东西。有些命令和参数在社区里传得很广看着很“高级”实际是定时炸弹。我把这些年见过的、踩过的坑列出来每条都是真实教训。常见操作为什么有风险正确姿势echo 3 /proc/sys/vm/drop_caches清掉page cache会拖慢所有读请求生产上尤其危险除非做冷启动基准测试否则不要在生产环境主动清cache把vm.swappiness改成0内存紧张时完全失去Swap缓冲更容易触发OOM Killer调成10左右给极端情况留条命开启tcp_tw_recycleNAT环境下大量丢包服务间歇性不可用不要用优先从连接复用角度解决TIME_WAIT调低dirty_ratio却不测写吞吐频繁触发同步刷盘写性能反而下降用fio压测后再动改完观察wa是否平稳盲目加线程池线程数上下文切换暴涨吞吐不升反降原则线程数≈CPU核数×(1等待时间/计算时间)算清楚再改修改内核参数不持久化重启后配置丢失还可能导致系统不一致统一写到/etc/sysctl.conf执行sysctl -p生效并验证调优完不做压测对比改完不知道效果甚至不知道有没有副作用每项改动前后各跑一轮压测记录数据再决定去留单改ulimit -n不查file-max系统级文件描述符不足时改进程上限形同虚设两个都查、两个都调系统服务还要注意LimitNOFILE还有几个操作层面的提醒每次改动只改一个参数改完验证一轮再改下一个。同时改十个参数如果出问题你根本不知道是哪个引起的。所有参数改动前先备份当前配置sysctl -a /tmp/sysctl.bak这个命令花不了几秒钟关键时刻能救命。调优不是找最大值而是找最平滑的区间。比如线程池和队列长度理想状态是系统承受压力时延迟缓慢上升而不是像断崖一样突然不可用。我发现很多性能问题最终不是某个参数不对而是监控缺失带来的“事后被动”。如果你有条件优先把基础的CPU、内存、磁盘、网络四项指标接入监控再配合systat历史数据做到有据可查。有了这些底子任何调优改动都能量化验证你才能从一个“照抄命令的人”变成真正会做性能优化的人。最后分享一个我个人的小习惯每台服务器在调优完成后我会把改动清单、原因、压测结果写成一个简单的README放在/etc/sysctl.d/同级目录下。几个月后回头看这个文档比任何命令都值钱——它记录的不是参数而是当时为什么做了这个决定。