资讯动态

Linux性能监控与调优实战:从CPU、内存到磁盘IO的排查流程

发布时间:2026/10/21 5:29:58 来源:尧图企业网站定制
1. 性能监控与排查的整体思路服务器半夜报警CPU 跑满 100%登录上去敲 top进程列表一片红但你不知道从哪下手。这个场景我见了太多次也是从“会敲命令”到“会做系统管理”的分水岭。这篇是 Linux 系统管理高级教程的第六篇主题定在性能监控与调优实战。我不讲安装之类的基础操作直接面对生产环境里最常见的 CPU、内存、磁盘 IO 和日志问题给你一套可以复用的排查方法而不是背一堆命令参数。先说明一个观点性能监控的核心不是命令是流程。你现在就可以把 top、vmstat、free 倒背如流但如果没有清晰的分析路径遇到复杂故障照样一头雾水。这篇博客的编排就是一个流程先看整体水位再逐步深入到具体进程最后靠日志锁定事件。后面每一章会沿这条主线展开你在实际生产里遇到问题也建议按这个顺序走能省掉大量无效操作。1.1 先定“基线”再谈“调优”我非常反对一上来就翻内核参数。很多管理员看到负载一高第一反应是去调 sysctl或者干脆重启服务这种做法等于不看心电图直接给病人开药。真正合理的顺序是先采集一段时间的“正常数据”把系统日常运行的负载、内存占用、磁盘 IO 压在哪个量级搞清楚然后以这个基线去对比异常时期的数据差异处往往就是问题的入口。比如我管理过的某台业务服务器平时 CPU 使用率稳定在 30% 左右某天突然冲到 95%。那不用多想要么是业务流量发生变化要么是某个进程出现死循环。反过来如果这台服务器的 CPU 本来就长期跑在 90%那偶尔跳到 95% 反而只是业务高峰的正常波动不值得大惊小怪。没有基线所有的“异常”都是在猜有了基线排查才有方向。基线的获取不用复杂工具。最简单的方式是写一个轻量采集脚本把关键指标定时落盘。我这里提供一个可用的模板你可以根据自己的场景调整采样间隔#!/usr/bin/env bash # 每天按日期生成一个日志文件每小时由 cron 调用一次 LOG_DIR/var/log/sysmon/monthly mkdir -p $LOG_DIR LOG_FILE$LOG_DIR/$(date %F).log { echo snap: $(date %F %H:%M:%S) uptime free -m iostat -x 1 2 2/dev/null df -h } $LOG_FILE 21配合 cron 里的0 * * * * /usr/local/bin/sysmon.sh一个月后你就拥有了一份按小时采样的系统历史水位。别嫌这个办法土很多高级工具崩溃或没部署的时候恰恰是这种最笨的数据救了你。我后来能快速定位几次线上问题全靠几个月前随手写的这个脚本。1.2 工具选型从整体水位到单进程定位Linux 自带的工具链其实非常完整绝大多数问题根本不需要装额外的商业监控软件。我的习惯是先做外部观测再做内部观测。外部观测用 uptime、free、iostat、sar 这类工具看整体水位内部观测用 pidstat、strace、perf 这类工具定位到具体进程和内核调用。这里有个很关键的选型原则工具要按“问题类型”选而不是按“别人推荐”选。查 CPU 使用率排行用 top 就够了查 CPU 上下文切换飙升要配合 vmstat 和 pidstat查内存不够用先拿 free 确认总量再用 ps 按内存排序看单个进程磁盘 IO 整体水位用 iostat定位谁在读写则用 iotop。混用工具不只是浪费操作成本更会把判断搞复杂因为每个工具的视角和粒度都不同。我自己的生产环境里首选保留的是 sysstat 包里的 sar。它能把 CPU、内存、IO、网络的历史数据自动落盘回看过去 7 天的数据非常方便。很多新同学不知道sar本身只是一个读取工具真正干活的是后台的sadc数据采集进程。平时它安安静静地积累数据等故障发生后再去翻比临时抱着 top 干瞪眼有效得多。1.3 监控阈值速查怎么用才不误报很多人问“负载多少算高”这个问题没有绝对答案但在云主机和物理机混用的场景里我的经验值是这样CPU 使用率持续 15 分钟高于 85%或者 load average 长期超过 CPU 核数的 4 倍就已经值得关注。内存方面可用内存持续低于总内存的 20%而且 swap 一直在动说明内存已经吃紧。磁盘方面iostat 里的 %util 超过 80%同时 await 超过 30ms基本可以判断磁盘是瓶颈。这些阈值不是金科玉律不同硬件和业务模式会有差异但作为第一道粗筛非常有效。我建议你把上面的数字写成一个监控小脚本每 5 分钟检查一次触发条件就发告警。注意设置告警时一定要加“持续时间”条件比如连续 3 次采样都超标才触发否则业务流量瞬时会制造大量无意义告警把真正的风险淹没在噪音里。2. CPU 与进程监控调优实战2.1 load average 和 CPU 使用率不是一回事遇到 CPU 飙高很多人第一眼就看 top 里的 %CPU。这个动作没问题但太局限。top 的 %CPU 只告诉你谁在消耗 CPU没有告诉你系统为什么感觉卡。要回答“为什么”得把 load average 和进程状态一起看。load average 的三个数字1 分钟、5 分钟、15 分钟反映的是过去一段时间处于可运行状态和不可中断睡眠状态的进程平均数。这里最容易踩的坑就是把 load 高直接等同于 CPU 忙。不可中断睡眠状态也就是 D 状态的进程通常是在等磁盘 IO 或者等内核锁这时候 load 会高但 CPU 可能很闲。我遇到过一台机器 load 飙到 20用 mpstat 看 CPU 占用却不到 30%最后发现是磁盘阵列有异常大量写请求都卡在等待队列里。如果你只盯着 CPU%这个方向就完全错了。所以我在排查 load 异常时第一件事永远是区分“CPU 密集”还是“IO 等待”。判断方法不复杂看进程状态就行。用 top 按“D”状态过滤如果发现一堆进程处在 D 状态而 CPU 空闲率却不低基本就是 IO 出问题如果 D 状态很少r 列排队很多那就是纯粹的 CPU 争抢。这一步做对了后面的排查才有意义。2.2 vmstat 读数r、b、cs 的判别价值vmstat 是性能排查里最被低估的工具输出简洁信息密度却很高。我的固定用法是vmstat 1 5每秒采样一次连续采 5 次重点看 r、b、cs、si、so 这几列。r 列表示正在排队等待 CPU 的进程数b 列表示处于不可中断睡眠状态的进程数。如果 r 很大b 基本为 0说明是 CPU 资源争抢如果 b 经常非零CPU 却有空闲说明大概率是 IO 等待。这两列能帮你快速定位主方向。另外us 和 sy 的比例也有参考价值sy 占比长期超过 30%说明系统调用或内核态处理负担过重可能是锁竞争、频繁中断或系统调用太多。上下文切换的异常更隐蔽。很多高性能应用性能突然下降不是 CPU 不够而是上下文切换太频繁。vmstat 里的 cs 列每秒几千次是正常范围但如果压测时 cs 从 5000 跳到 5 万同时 CPU 的 sy 占比升高就要怀疑是不是线程数开太多、锁竞争严重或者定时器太密集。此时用pidstat -w 1看哪个进程每秒的 cswch主动切换和 nvcswch被迫切换高顺着那条线就能找到元凶。2.3 进程优先级调整的正确姿势调整进程优先级有两种常规手段nice 命令在启动进程时设定renice 在进程运行中修改。优先级范围从 -20 到 19数字越小优先级越高。生产环境改优先级要格外小心把核心进程调成 -5 以下可能造成其他业务延迟反而放大问题。我一般在两类场景才会动 nice一是批处理脚本和关键守护进程共存时把批处理任务 renice 到 10 以上让它别抢核心业务的 CPU二是某个前端服务需要优先响应时把它的进程优先级稍微调高给关键线程留出 CPU。操作命令不复杂renice -n 10 -p 12345就可以直接修改。需要注意的是普通用户只能调高 nice 值降低优先级要调低必须用 root 权限这也是系统层面的安全设计。2.4 顺着 CPU 使用率追到具体调用链单进程 CPU 高很多人习惯直接 kill 重启。这是最省事但也是最危险的解法因为根因还在进程拉起来没多久又会复发。更稳妥的做法是先确认进程身份再判断是用户态忙还是内核态忙最后定位到函数或线程级别。如果进程用户态 CPU 高可以先用pidstat -p PID 1确认趋势再上perf top -p PID看热点函数。如果热点集中在一个明显的函数上比如某些正则表达式处理、加解密算法或者序列化逻辑就可以回查代码或配置。如果内核态 CPU 高用perf record -g -p PID抓调用栈重点看是不是频繁触发软中断、内存分配、文件系统锁。这些动作不复杂但能把问题从“换台机器”升级成“修复根因”这才是高级系统管理该有的水平。3. 内存与交换空间诊断优化3.1 看懂 free别被 used 和 available 欺骗内存排查是最容易被数据误导的领域。free -h一看 free 列只有几十兆很多人就以为内存不足实际上 Linux 的内存管理哲学是“内存不用白不用”空闲内存会尽量用作 page cache。真正需要关注的指标是 available它表示在不触发 swap 的情况下还能给新进程分配多少内存。以我管理过的一台 Web 服务器为例free 命令显示 used 占了 96%但 available 还有 30%。业务跑得很稳swap 也很少动。这说明内存并没有真正紧张只是大部分文件缓存没法被统计成“空闲”。如果照着 used 去扩容或者去清缓存就是白白浪费成本。判断内存够不够我只认两条available 是否长期低于总内存的 20%以及 swap 的 si/so 是否持续非零。除了 free还可以用ps aux --sort-rss | head快速排出当前物理内存占用最高的进程。想更准确一点就同时看/proc/meminfo里的 CommitLimit 和 Committed_AS它们反映的是系统承诺给进程的虚拟内存总量。如果 Committed_AS 长时间超过 CommitLimit说明过度分配情况比较严重未来可能出现内存分配失败的隐患。3.2 swappiness 要按场景调不能照抄关于 swappiness这个参数控制内核回收匿名内存与文件缓存的倾向。文档默认值通常是 60很多优化文章喜欢直接改成 0 或 10理由是减少 swap 使用。但从我的经验看无脑改低在某些场景反而有害。如果一台机器大量运行 Java 这类堆内存应用把 swappiness 调得过低内核会更不愿意回收匿名页面。一旦出现突发内存需求系统可能直接在分配时卡顿甚至触发 OOM Killer。反过来对于高并发读的缓存服务器调低 swappiness 可以减少不必要的换页确实有效。正确做法是结合 available 和 swap 变化趋势来综合判断而不是照抄网上的参数。修改命令是sysctl -w vm.swappiness10只对当前运行生效要让重启后也生效需要写进 /etc/sysctl.conf。任何内核参数修改前一定先备份原值并记录修改时间和原因。很多线上事故不是改错了而是改完忘了为什么改回滚时无从下手。3.3 内存泄漏的定位思路连续观察 RSS内存泄漏定位也有不少误区。ps aux 里的 VIRT 列代表进程申请的虚拟地址空间很多服务启动时就预留了很大的 VIRT比如 JVM 的堆配置这不代表真的占用了那么多物理内存。RSS 更接近物理内存占用但它包含共享库的内存多个进程会重复计算。要精确判断泄漏更可靠的手段是连续采集 RSS。如果某个进程的 RSS 在业务量不变的情况下稳定增长比如每小时涨 500MB连续一天不回落基本可以判断是泄漏。采集方法可以直接用 cron 把/proc/PID/status里的 VmRSS 记下来也可以用ps -o pid,rss,vsz,cmd -p PID定时输出。定位到进程之后要区分是堆内存泄漏还是线程栈、本地内存缓存失控。Java 应用可以看堆内外的使用曲线C/C 应用则可以用 valgrind 或 jemalloc 的 profiling 功能辅助。这个过程确实比较耗时但比反复重启进程假装问题不存在要专业得多。3.4 缓存回收时机与 cgroup 内存兜底有人喜欢用echo 3 /proc/sys/vm/drop_caches清缓存来“释放”内存我劝你不要把这个命令当成常规武器。它确实会清掉 page cache但随后所有文件读取都要重新落盘磁盘 IO 会短暂飙高业务可能反而变慢。这个命令只在执行完大文件操作、想把内存腾给下一个大任务时偶尔使用。正常情况下Linux 自己的 writeback 机制会根据脏页比例和回收压力去处理缓存根本不需要人工干预。你真正该做的是为关键服务设置内存上限兜底。用 systemd 管理服务时可以给单元文件添加MemoryMax或通过systemctl set-property 服务名 MemoryMax2G来限制。这样即使业务模块出现内存失控也只是这个单元被限制而不会把整台服务器拖进 OOM 危机。4. 磁盘 I/O 与文件系统性能实践4.1 iostat 关键指标与 %util 的局限磁盘性能优化的第一步同样是读数。iostat -x 1输出里的 %util 被很多人当成磁盘忙不忙的唯一指标但它的含义是设备在采样周期内有请求未完成的时间占比。对单块机械盘%util 到 100% 基本就是满负荷对 SSD 或做了阵列的存储%util 很容易虚高因为硬件支持并行处理多个请求100% 的 %util 并不一定意味着达到性能极限。更值得关注的是 await它表示平均每次 IO 请求从提交到完成的时间。机械盘正常在 5-15msSSD 在 1ms 以内。如果 await 明显走高svctm 却变化不大说明请求在队列里排队问题多半出在并发压力或调度上。另一个指标 r_await 和 w_await 最好分开看因为很多场景下读延迟和写延迟差异巨大混在一起会掩盖真实问题。4.2 I/O 调度器和挂载参数真的需要改吗I/O 调度器的选型在传统机械盘和 NVMe 固态盘上结论不同。机械盘上deadline 调度器能有效减少大文件的长时间饥饿适合数据库负载NVMe 固态盘本身随机 IO 能力强大用 none 反而能减少调度层开销。不过现代内核已经默认使用多队列调度除非明确知道自己的负载特征否则没必要频繁切换。挂载参数里性价比最高的优化是 noatime。默认的 atime 每次读取文件都要更新访问时间等于每次读操作伴随一次写操作。对高并发读的服务器改用 noatime 或 relatime 能立刻降掉一批写放大。现代文件系统多数发行版默认是 relatime已经平衡了访问时间和性能如果你的业务没有严格审计需求可以进一步改为 noatime。可能有人问文件系统选 ext4 还是 xfs。这个问题没有标准答案但我的习惯是大文件、海量小文件混合的场景选 xfs它处理大文件和高并发写入更稳老牌兼容性更强的场景选 ext4遇到问题资料多运维也不慌。不要盲目追求“新版更好”稳定性和运维熟悉度更重要。4.3 iotop、lsof 定位热点文件的组合拳定位具体热点进程iotop 是最直观的。它能按实时 IO 读写量排序一眼看到谁在大量读写。比如数据库高负载时你经常会看到 mysqld 排在前面问题往往不是它本身而是某个慢查询或者备份任务在背后触发海量扫描。想进一步知道进程在操作哪个文件要结合打开的句柄目录来看。ls -l /proc/PID/fd会列出该进程打开的所有文件描述符配合lsof -p PID能更清晰地看到文件路径。之前我排查一次日志磁盘写满发现是某个服务的 debug 日志把整个盘打爆原因就是日志框架级别配置错误同一个日志文件被循环写出巨大体量。这种问题调度器救不了只能从配置层面纠正。4.4 备份对 IO 的冲击一个案例复盘处理过一个典型场景数据库备份任务一跑整台机器就卡到 SSH 都连不上。用 iostat 看%util 在 95% 以上await 从正常的 5ms 涨到 200ms同时 iotop 显示有一个备份子进程在大量读数据。问题不在 CPU也不在内存就是备份脚本的并发度太高把磁盘 IO 队列打满了。解决办法并不复杂用 ionice 把备份命令的 IO 优先级调低同时把备份拆成按表分批执行。核心命令是ionice -c 3 /usr/local/bin/backup.sh其中-c 3表示 idle 级别只有当系统空闲时才会执行 IO。这一个改动做下去备份照跑业务恢复平稳。这提醒我影响系统性能的往往不是某个工具本身而是使用工具的时机和参数。5. 日志审计与故障定位实战5.1 journalctl 的组合过滤参数系统日志是排查事故的最后一道防线。很多故障不会当场复现但一定会在日志里留痕迹。journalctl 是 systemd 系发行版最常用的日志查询工具它的价值不止是查看实时输出更在于支持组合过滤。我查问题的固定套路是先用手表定位时间窗口。比如事故发生在 22:15 到 22:20就用journalctl --since 22:15 --until 22:20 -p err把这条时间线里的错误日志全部拉出来。服务维度用-u指定单元名比如journalctl -u 应用服务名 --since today -p warning。想看内核消息就用-k。熟练组合这些参数后查日志的效率能提升一个量级。5.2 从系统日志反推异常事件日志里最容易忽略的线索是 OOM 记录。当内核内存不足时会触发 OOM Killer 杀掉进程对应的记录会出现在系统日志的内核消息里提示哪类进程被杀了、内存占用多少。很多人看到服务进程突然消失第一反应是查服务自身的错误日志结果发现根本没有异常输出白白浪费半天。这时候先查系统日志里的 oom 关键字往往一眼就能定位。另外日志时间线的拼图也很重要。一个看似无关的定时任务可能在某个时刻同时触发 CPU 密集、内存暴增和磁盘读写放大。用 journalctl 拉出同一时间窗内所有消息按系统日志、内核日志、应用日志三个维度去对齐经常能发现一根隐藏的因果链。这也是为什么我一直强调事故处理不要只盯服务日志要全局地看。5.3 logrotate 的高频坑与配置要点logrotate 配置里有两个高频坑。第一个是轮转周期和业务日志产生速度不匹配。有些服务一小时能写几百 MB 日志按 daily 轮转根本顶不住需要改成按 size 切。配置项很简单比如size 100M日志文件到 100MB 就轮转。第二个坑是配置了 create 但服务还是收不到新日志。原因通常是服务进程一直持有旧文件句柄轮转后没有重新打开日志文件。解决办法是配 postrotate 指令通知进程重新加载配置或重开文件句柄。比如很多应用服务支持发送信号后重新打开日志就可以在 postrotate 里写kill -HUP $(pidof 应用名)。还有一个容易忽略的点是延迟压缩建议把 compress 改成 delaycompress避免压缩大文件时把 IO 打满。5.4 多机日志汇聚的最小方案如果管理的机器数量比较多本地 journalctl 显然不够用。我的经验是先别急着上重量级采集平台自建一个最小方案就能解决大部分问题。用 rsyslog 把关键服务日志转发到一台集中日志机按主机名和服务名分目录存储配合简单的归档脚本就够日常排查了。rsyslog 的配置不难客户端在 /etc/rsyslog.d/ 下新建转发规则把设施和日志级别转发到日志服务器的指定端口服务器端开启接收模块并按%HOSTNAME%/%programname%做目录模板。这套方案一天就能搭完能解决 90% 的“日志散落在几十台机器上”的问题。等团队规模真正变大、检索需求变复杂再评估更重的方案也不迟。6. 常见误区与排查速查表6.1 五个最常见的错误判断这套监控方法用了这么多年我见过太多同行在排查路上栽进同一个坑。第一个典型误区是看见响应变慢就重启进程却不先查是整体负载高还是单进程异常重启之后问题复现才发现什么都没解决。第二个是看见 swap 有占用就急着加内存却不先确认是不是空的 page cache 可以被回收。第三个看到 sar 报告里 CPU 队列长就调内核参数却不先确认是不是应用层线程数配得太多。第四个误区是只看进程本身的“健康状态”不看系统级资源水位。应用日志一切正常不代表底层没有资源争抢。第五个误区是把所有异常都归结为“流量突增”不做数据对比。没有基线的支撑流量突增只是句正确的废话。其实很多问题的根源只是配置不合理或者单应用算法问题内核默认参数远比你想象的更可靠。6.2 可复用的性能排查命令清单最后把我常用的一套排查命令整理成速查表平时记不得就翻一下目标命令关注点整体负载uptimeload 1/5/15 分钟趋势资源队列vmstat 1 5r、b、cs、si/so单核分布mpstat -P ALL 1是否单个核被打满进程切换pidstat -w 1cswch、nvcswch内存水位free -havailable不是 free历史水位sar -r -d过去几天的内存和磁盘磁盘 IOiostat -x 1%util、await、w_await实时 IO 进程iotop按读写量排序进程打开文件lsof -p PID定位热点文件时间窗日志journalctl --since ... --until ... -p err按时间过滤错误内核 OOMjournalctl -kgrep -i oom最后留一个个人习惯每次处理完一起性能问题我都把现象、用过的命令、最终根因写进一个本地笔记一周后再回看。很多看似复杂的问题其实有相似的纹理记录多了下次再报警你会发现自己下意识已经开始分类。系统管理这门手艺就是从无数个这样的复盘里长出来的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑