服务器性能优化从哪里入手?CPU、内存、磁盘与网络全面分析

服务器卡了,第一反应往往是"加内存"或者"换个高配"。但很多时候加了配置还是一样卡,因为瓶颈根本不在你猜的地方:CPU 闲着却在等磁盘,内存看着占满其实大部分是文件缓存,网络带宽没跑满但连接已经排不上队了。

这篇文章解决的是"优化从哪里开始"这个问题。不讲玄学调参,讲的是一套可以复用的方法论:先测量再优化、用 USE 和 RED 两个框架给资源做体检、四类资源各自该看哪些指标用哪些工具、怎么判断收益最大的那个瓶颈、什么时候该加机器什么时候必须改代码、以及优化前后怎么证明真的有效。

读完你应该能形成一套固定动作:拿到一台"慢"的机器,十分钟内说清楚瓶颈在哪一层,而不是先去改配置文件碰运气。

一、先测量,再优化

这是整个性能优化领域唯一公认的铁律,也是最容易被跳过的一条。跳过测量的典型表现是:听说某个内核参数能提升并发,就去改 net.ipv4.tcp_tw_reuse;听说某个数据库参数能加速,就去调 innodb_buffer_pool_size。改完之后感觉好像快了,也可能更慢了,但没人能说清楚到底改对了没有。

原因很简单:没有量化数据,你就无法区分"优化生效了"和"当时正好不忙了"。

测量的第一步是建立基线。基线不是某个绝对值标准,而是"你的系统在正常状态下的样子"。需要在业务低峰和高峰分别采集一组数据,记录这些量:

项目 说明 采集方式
业务量 QPS、订单数、日活等,作为分母 应用侧埋点或 Nginx 访问日志
响应时间 平均值与 P95、P99 Nginx 日志、APM、压测工具
资源使用 CPU、内存、磁盘 IO、网络四项 vmstat、iostat、sar、node_exporter
错误率 5xx、超时、失败率 应用日志、Nginx 日志
饱和信号 运行队列长度、await、swap 进出 vmstat、iostat、sar -B

基线要有时间维度。只看当前一秒的数值毫无意义,因为性能问题往往是"某个时段"的问题。用 sar 这类能采集历史数据的工具,或者部署 Prometheus 长期保存,才能在故障发生后回看"出事前那十分钟机器是什么状态"。

测量的第二步是单次只改一个变量。这是对照实验的基本要求,但在运维现场经常被违反——一次性改了五个参数,结果变好了不知道是哪一个起的作用,变差了也不知道该回滚哪一个。正确的做法是:改一个、压测或观察一个周期、记录结果、再改下一个。每次改动都要留下"改动前 → 改动后"的数字对比。

测量的第三步是先知道目标。优化的终点不是"资源用得越少越好",而是"在满足业务目标的前提下资源够用"。典型的业务目标是:P95 响应时间低于某个值、错误率低于某个值、在目标 QPS 下系统不崩。如果当前已经满足,继续优化就是在浪费时间。反过来,如果目标是支撑 3 倍流量,那当前的资源余量、扩容路径、瓶颈点都要提前算清楚。

还有一个容易被忽略的点:测量本身有开销。strace、tcpdump、perf record 这类工具在高负载机器上会显著加重负担,采集前要评估,采集时要限时。生产环境优先选择读取 /proc、/sys 这类低开销的方式,必要时才上抓包和 perf。

二、USE 与 RED:两个够用的方法论

性能领域的方法论很多,对服务器运维来说两个就够:USE 管资源,RED 管服务。

USE 方法:从资源视角逐项排查

USE 是 Brendan Gregg 提出的方法,三个字母分别代表 Utilization(使用率)、Saturation(饱和度)、Errors(错误)。它的操作方式极其机械:对系统里的每一个硬件资源,依次问这三个问题。

  • 使用率:资源有多忙?通常用"资源处于忙碌状态的时间百分比"衡量。CPU 使用率 90%、磁盘 100% 忙,说的都是使用率。
  • 饱和度:资源排了多少队?有多少工作因为资源不够而在等待。CPU 的运行队列长度、磁盘的请求队列长度、内存的分页排队,都是饱和度。
  • 错误:有没有出错?网卡的丢包与错误计数、磁盘的 IO 错误、内存分配失败。

USE 的价值在于它的完备性:你按顺序过一遍清单,就不会漏掉某个资源。更关键的是它强制你看饱和度——大多数人排查时只看使用率,而真正压垮系统的往往不是"用了 100%"而是"开始排队了"。一台 CPU 使用率 60% 但运行队列长期排着 20 个进程的机器,比一台 CPU 100% 但没有排队的机器糟糕得多。

需要按 USE 检查的资源清单大致是:CPU(含每个核)、内存、磁盘(每块盘)、网络接口、以及存储控制器、CPU 互联、内存总线这些更深的资源。日常运维覆盖前四类基本够用。

RED 方法:从服务视角看用户体验

RED 来自云原生领域,三个字母是 Rate(请求速率)、Errors(错误率)、Duration(耗时)。它不看机器,看的是服务本身的处理情况:

  • Rate:每秒处理多少请求。
  • Errors:其中有多少失败。
  • Duration:成功请求花了多长时间。

RED 回答的是"用户感受到的服务好不好",USE 回答的是"机器哪个部件吃紧"。两者配合使用:业务侧发现 Duration 变长(RED 报警),再用 USE 下钻到具体是 CPU、磁盘还是网络的问题。

两者的分工

对比项 USE RED
观察对象 硬件/系统资源 服务/接口
核心三问 使用率、饱和度、错误 速率、错误率、耗时
回答什么 哪个部件是瓶颈 用户体验好不好
典型工具 vmstat、iostat、sar、node_exporter Nginx 日志、Prometheus、APM
适合场景 下钻定位资源瓶颈 监控大盘与告警分级

实际排查时的顺序是:RED 先告诉你"出问题了以及严重程度",USE 再告诉你"为什么"。上来就看 CPU 曲线的做法容易误判,因为 CPU 高可能只是结果而不是原因。

三、四类资源的指标与工具对照

下面这张表是排查时的速查清单,建议直接记下来。指标按 USE 分了类,工具列给出的是最小可用集合。

总表

资源 使用率(U) 饱和度(S) 错误(E) 主要工具
CPU top 的 us+sy,或 100-id vmstat 的 r 列、运行队列长度 一般无(特殊 CPU 例外) top、vmstat、mpstat、pidstat、perf
内存 已用内存/总内存 swap 进出 si/so、缺页、OOM OOM Killer 触发、dmesg 日志 free、vmstat、sar -r/-B、smem
磁盘 iostat 的 %util await、avgqu-sz、请求队列 /sys/block/*/stat 错误计数、dmesg 介质错误 iostat、iotop、pidstat -d、sar -d
网络 带宽占用/网卡速率 重传率、丢包、队列丢包 ifconfig/ip -s link 的 errors、dropped sar -n DEV/ETCP、ss、ethtool、ip -s link

CPU:使用率只是一个维度

top 头部那行 CPU 数据被分成几段,每一段指向的原因完全不同:

  • us(user):用户态。业务代码、数据库计算、正则、加解密、压缩都在这。高说明应用本身在算东西。
  • sy(system):内核态。系统调用频繁、上下文切换过多会导致它升高。短连接洪水也典型。
  • wa(iowait):CPU 空闲但在等 IO。这一项高几乎一定是磁盘或网络存储的问题,加 CPU 没用。
  • hi/si:硬中断/软中断。网卡收包过多、si 高通常指向网络包量过大。
  • st(steal):被宿主机偷走的时间。只出现在虚拟机上,st 高说明宿主机超卖了,你改 guest 没用,该换实例或换宿主机。

CPU 瓶颈最容易被漏判的其实是上下文切换:vmstat 的 cs 列每秒几万甚至十几万,说明大量时间花在切换而不是干活。这时候降低线程数、改用长连接或者批量处理,比加 CPU 有效得多。

内存:高占用往往是好事

Linux 的设计哲学是"空闲内存就是浪费的内存",它会把没用上的内存拿去做 Page Cache(文件缓存)和 Buffer,等应用需要时再回收。所以 free 看到 used 很高,并不代表内存不够。真正该看的是 available(可用内存),它已经把"可以回收的缓存"算进去了。

异常的信号是这几个:available 长期很小(比如低于总内存的 5%)、swap 持续增长、vmstat 的 si/so 长期不为 0(真正在换页)、dmesg 里出现 OOM Killer。

磁盘:%util 不等于性能问题

iostat -x 里最容易误读的就是 %util。它是"设备有 IO 请求在处理的时间占比",对机械盘来说接近 100% 基本就是瓶颈;但对支持并行的 SSD 和 RAID 来说,这个值的物理含义被削弱了——设备内部可以并行处理多个请求,%util 100% 时吞吐可能还有余量。

判断该看的是 await(请求从发出到完成的平均等待时间,含排队和服务时间)。经验参考(随机读写场景):机械盘 await 超过 20-50ms 要警惕,SATA SSD 正常在几毫秒内,NVMe 正常在 1ms 以内。await 明显高于设备正常值时,才算是真的排队了。另外 avgqu-sz(平均请求队列长度)持续大于 1 也说明请求在开始堆积。

网络:包量比带宽更隐蔽

很多人的网络监控只看带宽,但网卡先撞上的往往是 PPS(每秒包数) 而不是带宽。小包洪水(每秒几十万个 64 字节的包)带宽占用不到 100Mbps,但已经能把单核软中断打满。所以 sar -n DEV 要同时看 rxkB/s(带宽)和 rxpck/s(包量)。

另一个关键指标是重传率(sar -n ETCP 的 retrans/s)。哪怕只有千分之一的重传,也会让 TCP 的吞吐大幅下降,因为它触发拥塞控制降窗。丢包则要看 ip -s link 里的 dropped 和 errors,以及网卡的 Ring Buffer 溢出计数(ethtool -S)。

四、从症状到瓶颈的判定路径

登录一台"感觉很慢"的机器,先用下面这组命令做 60 秒体检,把四类资源各扫一遍,再决定往哪个方向深挖。这些命令都是只读的,不会改动系统状态,可以放心执行:

# 1. 整体负载与 CPU 时间分布(看 r/b 列、wa、st)
uptime
vmstat 1 5

# 2. 按进程看 CPU 与内存占用
top -b -n 1 | head -30
ps aux --sort=-%cpu | head -15
ps aux --sort=-rss  | head -15

# 3. 内存真实余量:重点看 available,而不是 free
free -m
cat /proc/meminfo | grep -E 'MemAvailable|SwapTotal|SwapFree|Slab'

# 4. 磁盘:看 await、%util、avgqu-sz
iostat -x 1 5
df -h && df -i          # 空间与 inode 都要看

# 5. 网络:带宽、重传、连接状态分布
sar -n DEV 1 5
ss -s
ss -ant | awk 'NR>1{print $1}' | sort | uniq -c | sort -rn

# 6. 谁在写盘、谁在占 CPU(需要 root)
iotop -o -P -n 3        # 仅显示正在做 IO 的进程
pidstat -u -d 1 5

部分命令需要安装工具包:RHEL/CentOS/Rocky 用 dnf install -y sysstat iotop procps-ng,Debian/Ubuntu 用 apt install -y sysstat iotop procps。iostat、pidstat、sar 都属于 sysstat。

有了指标,还需要一条把现象翻译成资源问题的路径。常见的对应关系如下:

现象 最先怀疑 验证命令
响应变慢,CPU busy 高 CPU 计算瓶颈 top、pidstat -u 1、perf top
CPU 不高但 Load 高 磁盘 IO 或 D 状态进程堆积 vmstat 1(看 b 列)、iostat -x 1
CPU 的 wa 很高 磁盘或网络存储慢 iostat -x 1、NFS 场景 nfsstat
内存 used 高但 si/so 为 0 正常,是文件缓存 free -m、cat /proc/meminfo
si/so 持续非 0 内存真不够,在换页 vmstat 1、sar -B
网络延迟忽高忽低 丢包与重传 sar -n DEV,ETCP 1、ethtool -S
连接建不上、超时多 连接队列满或端口耗尽 ss -s、ss -ant \| awk 统计状态
虚拟机里啥都慢,未见明显瓶颈 stealtime 被偷 top 的 st、vmstat

这套判定不是百分百准确,但能把排查范围从"整台机器"缩小到"一类资源",效率差别很大。否定法也有用:如果四类资源都不饱和,那瓶颈多半在应用层(锁竞争、串行等待、外部依赖慢)或者客户端,这时候要看应用自己的指标和链路追踪。

五、优化优先级:按收益和成本排序

找到瓶颈之后,不要急着动手,先按优先级排一遍。排序的原则是 "离用户最近的最优先改、收益最大的最优先改、成本最低的最优先改"。实践中大致是这个顺序:

  1. 消除明显的错误和浪费:例如日志级别开着 DEBUG 疯狂刷盘、循环里查数据库、没有索引的全表扫描、重复计算。这类问题几乎零成本,收益经常是数量级的。
  2. 减少不必要的工作:加缓存、合并请求、批量写、去掉无效轮询、开启压缩。这是架构层面的收益,通常远大于调参。
  3. 调整配置把资源用够:例如数据库连接池太小导致请求排队但 CPU 空闲、Nginx worker 数太少用不满多核。这类属于"白白浪费了已买的资源",性价比很高。
  4. 给瓶颈资源扩容:垂直扩容(更强的 CPU、更大的内存、SSD 替换机械盘)或水平扩容(加机器做负载分担)。
  5. 内核级与参数级微调:调度器、脏页回写比例、TCP 参数。这类改动收益通常有限(几个百分点到十几个百分点),且容易引入副作用,排在最后。

需要提醒的是不要过早做第 5 类优化。很多流传的"内核参数优化脚本"里的参数,在现代内核里已经是默认值或者根本不适用于你的场景。改之前先问一句:当前这个资源的瓶颈有数据支撑吗?没有就别改。

六、什么时候加机器,什么时候改代码

这是运维和开发最容易产生分歧的地方。判断标准其实比较清晰:

适合加机器(含扩容磁盘、换 SSD)的情况:

  • 瓶颈是纯粹的容量问题:内存装不下热点数据集、CPU 核数不够跑满倍的流量、磁盘 IOPS 达到硬件上限。
  • 流量确实增长了,单位请求的资源消耗已经合理。
  • 架构本身支持水平扩展:应用无状态,加机器能通过负载均衡直接分流量。
  • 时间窗口紧张,扩容能立刻止血,优化来不及。

应该改代码(或改架构、改 SQL)的情况:

  • 单个请求的资源消耗明显不合理:一个列表页查了 500 次数据库、一次请求读了 200MB 数据、正则 catastrophic backtracking 导致 CPU 打满。这种情况加 10 台机器也只是把浪费复制 10 份。
  • 存在串行等待、锁竞争、线程池配置不合理。加机器无法解决内部的串行。
  • 依赖的外部服务慢,本机资源很闲。瓶颈在别人家里。
  • 资源用不高吞吐却上不去,说明请求在内部排队,扩容等于把队排得更长。

一个粗略的判断法:看资源使用率和吞吐的关系。加流量时如果资源使用率线性上升且很快就到顶,是容量问题,可以扩容;如果资源使用率不高但吞吐已经上不去、延迟却在涨,那是效率或串行问题,要改代码。

七、形成闭环:基线、验证、防回退

优化不是一次性的动作,而是一个闭环,缺了后半段就会"优化了但不知道有没有效,过几个月又变回原来的样子"。

第一步,记录优化前的基线。 包括业务指标(QPS、P95、错误率)和资源指标四类数据,最好带上压测结果。压测要在同样的请求分布下做,否则数字没有可比性。

第二步,单次改动 + 对比验证。 改完之后用同样的负载再跑一次,对比同一组指标。有效的标准应该是业务指标的改善:比如同样 QPS 下平均响应时间从 300ms 降到 120ms,或者同样延迟下能支撑的 QPS 从 800 涨到 2000。只看资源指标(比如 CPU 从 90% 降到 70%)是不够的,资源降下来但业务没变快,可能是请求被排队或拒绝了。

第三步,写下来。 改了什么、为什么改、改之前多少、改之后多少。这份记录在系统迁移、人员交接、复盘时能省掉大量重复劳动。用版本管理维护配置文件(Nginx 配置、sysctl 配置、数据库配置)也是这个目的的一部分。

第四步,监控防回退。 把优化时设定的目标做成长期监控和告警。很多优化会在几次发布之后悄悄失效——某个新功能增加了全表扫描、某个新依赖引入了串行等待。没有持续监控,就只能等到下一次故障时才发现。

最后是心态问题:性能优化是收益递减的。第一轮优化通常收益巨大,之后每一轮的收益都在变小,而成本在变高。当优化的成本(时间、复杂度、稳定性风险)超过扩容的成本时,就该停下来买资源了。这是一道经济题,不是技术题。

常见问题

服务器内存占用 90%,要不要马上处理?

先看 available 而不是 used。用 free -m,如果 available 还有几 GB,说明大部分被占用的是可被回收的文件缓存,属于正常现象。真正要处理的是 available 接近 0、swap 开始被使用、vmstat 里 si/so 持续不为 0、或者 dmesg 出现了 OOM Killer 的情况。详见本系列的 Linux 内存分析篇。

有人说改 vm.swappiness 能提升性能,要不要改?

默认 60 对大多数场景是合理的。把 swappiness 调到 1 或 0 并不会让系统变快,只是让内核更不愿意换页,代价是内存真的耗尽时更容易直接触发 OOM Killer 杀进程。数据库类主机可以适当调低(如 1-10),但前提是这台机器的内存足够、并且有其他保护措施。这是第 5 类优化,优先级最低。

CPU 使用率和 Load Average 是一回事吗?

不是。CPU 使用率是"CPU 忙不忙",Load Average 是"有多少任务在运行或等待不可中断 IO"。CPU 100% 但 Load 不高,说明在干活没排队;Load 很高但 CPU 很闲,通常说明进程卡在 IO(尤其是 D 状态)或者等待网络存储。

优化应该先在测试环境做还是直接上生产?

参数类改动先在测试环境验证,尤其是内核参数和数据库参数,它们有引入副作用的风险。同时要有回滚方案:sysctl 改动记在 /etc/sysctl.d/ 下的独立文件里以便删除恢复,配置文件用版本管理。任何在生产上的改动都要在低峰期、可回滚、有人盯着指标的前提下做。

监控该从哪几个指标开始搭起?

如果现在什么监控都没有,先做最基础的四项再加业务指标:CPU 使用率与 Load、可用内存与 swap、磁盘使用率与 await、网络带宽与重传,然后加上 QPS、响应时间 P95、错误率这三项。这七个指标能覆盖绝大多数故障的发现环节。

总结

  • 优化的起点永远是测量而不是猜测。建立包含业务量与资源指标的时间序列基线,单次只改一个变量,每次改动都要有"改动前 vs 改动后"的数字对比,否则无法证明优化真的有效。
  • USE 方法管资源(使用率、饱和度、错误),保证排查不漏项;RED 方法管服务(速率、错误率、耗时),反映用户真实感受。排查顺序是 RED 先发现问题与严重程度,USE 再下钻定位到具体资源。
  • 四类资源各有容易被误读的指标:CPU 要看 sy 与上下文切换而不只是 us;内存要看 available 而不是 used;磁盘 %util 在 SSD 上参考意义下降,应改看 await 与平均队列长度;网络不能只看带宽,包量与重传率往往先出问题。
  • 优化按性价比排序:先消灭明显浪费(日志刷盘、循环查库、缺索引),再减少不必要工作(缓存、批量、异步),再调整配置把已有资源用够,最后才考虑扩容和内核参数微调。
  • 加机器还是改代码,看"资源使用率 vs 吞吐"的关系:使用率随流量线性上涨到硬件上限,是容量问题可以扩容;资源很闲但吞吐上不去、延迟在涨,是效率或串行问题,扩容等于复制浪费。
  • 性能优化收益递减。当继续优化的时间成本和稳定性风险超过扩容成本时,就停下来买资源。同时把优化目标固化成监控和告警,防止优化成果在后续发布中被悄悄抵消。