场景均为虚构但来自真实线上教训。前言接到告警后先跑这三条登录机器后不要急着翻日志先建立全局视野# 1. 系统负载和运行时间uptime# 2. 内核最近的异常事件dmesg-T|tail-20# 3. 整体资源概览free-hecho---df-h为什么要先做这三步uptime告诉你问题是刚发生的还是持续半小时了load average 三个值dmesg可能直接暴露 OOM killer 杀进程或磁盘 IO error省掉后面的猜测freedf检查总水位避免在资源耗尽的机器上跑重型工具导致二次故障。现在进入具体场景。场景一CPU 飙高但不知道谁干的现象某台 Java 业务机告警CPU 使用率持续 95%。uptime看到 load average 超过 CPU 核数假设 16 核load average: 24.3, 18.7, 12.1第 1 列 16说明任务在排队。第 3 列 第 1 列说明问题在最近 5 分钟内开始加重。第一步定位消耗 CPU 的进程top-b-n1|head-20输出片段PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND 12345 appuser 20 0 4567892 524288 32768 S 350.0 12.3 12:34.56 java关键信号%CPU 350.0— 这个 java 进程吃了 3.5 个核。结合 load average 24说明还有别的线程在抢 CPU但大概率是这个进程引起的。TIME 12:34— 自启动以来累积用了 12 分钟 CPU结合进程启动时间可以判断是不是突然飙起来的。第二步深入进程内部看哪些线程在吃 CPUtop-H-p12345-b-n1|head-20PID USER PR NI VIRT RES SHR S %CPU COMMAND 12346 appuser 20 0 4567892 524288 32768 R 85.0 java 12347 appuser 20 0 4567892 524288 32768 R 82.3 java 12348 appuser 20 0 4567892 524288 32768 R 78.1 java 12349 appuser 20 0 4567892 524288 32768 R 76.9 java4 个线程各吃 ~80%占满 3.5 核。记下最热的几个线程 PID12346~12349。第三步把线程号转成十六进制去 jstack 里查printf%x\n12346123471234812349# 输出303a 303b 303c 303dJava 特有的步骤。C/C / Go 程序直接perf top更简单后面会讲。jstack12345|grep-A200x303a输出http-nio-8080-exec-123 #456 daemon prio5 os_prio0 tid0x... java.lang.Thread.State: RUNNABLE at java.util.regex.Pattern$Curly.match(Pattern.java:4226) at java.util.regex.Pattern$GroupHead.match(Pattern.java:1658) at java.util.regex.Pattern$GroupTail.match(Pattern.java:1717) ...看到都在正则匹配里。八成是某些请求的正则写得差回溯爆炸或者接到了一个精心构造的恶意输入ReDoS。给读者的建议先重启止损然后从日志里捞对应时间段的请求 URL看是正常功能的正则问题还是被攻击。不用 Java 的场景用火焰图上面 jstack 那套只适用于 JVM。通用方案# 安装 perfCentOSyuminstall-yperf# 以 99Hz 采样 30 秒perf record-a-g--sleep30# 生成报告perf report-ggraph --no-childrenperf report交互界面里按箭头展开函数调用链最顶层的函数就是 CPU 热点。如果想出图用 FlameGraph 脚本# 把采样数据转成火焰图格式perf scriptperf.scriptgitclone https://github.com/brendangregg/FlameGraph ./FlameGraph/stackcollapse-perf.pl perf.scriptperf.folded ./FlameGraph/flamegraph.pl perf.foldedflame.svg浏览器打开 flame.svg鼠标悬停在方块上就能看到哪个函数最宽≈最吃 CPU。火焰图的好处是能一眼看出问题主要在这条调用路径上比读perf report快得多。快速检查上下文切换CPU 高不一定是因为代码跑得多也可能是上下文切换太频繁vmstat15关注cscontext switch列procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 16 0 0 123456 1024 2048576 0 0 0 0 12000 85000 45 50 5 0 0cs高达 85000/ssy系统 CPU占 50%。这通常意味着锁竞争严重mutex / spinlock或者线程数太多调度开销压过了实际计算。验证pidstat -w -p 12345 1 3看进程级别的自愿上下文切换配合strace -c -p 12345看系统调用频率。场景二内存泄漏机器 swap 报警现象一台 Go 微服务实例告警内存使用率 90%swap 开始使用。应用没有重启过运行了 15 天。第一步看内存整体视图free-h输出total used free shared buff/cache available Mem: 15Gi 14Gi 300Mi 100Mi 700Mi 800Mi Swap: 2Gi 1.2Gi 800Mi两个关键信号available 只有 800M— 新进程可能被 OOM killer 杀掉swap used 1.2G— 已经触发了 swap性能会急剧下降磁盘写入纳秒级变毫秒级。第二步逐进程排序内存占用psaux--sort-%mem|head-10或者看更多细节RSS即实际物理内存ps-eopid,ppid,cmd,%mem,%cpu,rss--sort-rss|head-10PID PPID CMD %MEM %CPU RSS 54321 1 /usr/bin/my-go-app 45.2 2.3 678912 12345 1 java -jar service.jar 22.1 15.4 339456第三步确认趋势——是不是真的在上涨一次ps的快照说明不了问题。要确认泄漏需要看趋势# 每 60 秒记录一次 rss共 10 次foriin$(seq110);dops-orss-p54321/tmp/rss-trace.logsleep60done观察/tmp/rss-trace.log里的数字是否线性增长。如果是基本确认泄漏。也可以用更直观的/proc接口cat/proc/54321/smaps_rollup|grep-ERss|Pss第四步针对不同语言确定泄漏方向Go 语言Go 的常见内存泄漏场景goroutine 泄漏 — 协程阻塞在 channel / 锁上永远不退导致其引用的内存也无法回收time.Ticker/time.After没有Stopmap 只增不减的缓存实现。先看 goroutine 数量# 通过 pprof 接口curlhttp://localhost:6060/debug/pprof/goroutine?debug2|grep-cgoroutine如果 goroutine 数量在缓缓上涨基本就是泄漏。接着# 看堆内存curl-oheap.pprof http://localhost:6060/debug/pprof/heap# 用 go tool 分析go tool pprof-topheap.pprof也可以在本地用交互模式看 graphgo tool pprof-http:8080 heap.pprof浏览器打开http://localhost:8080能看到内存分配的火焰图调用链上的每个函数分配了多少内存。Java 语言jmap-histo:livepid|head-30连续运行两次观察某个类的实例数量是否在持续增长。通用方法/proc看内存分布cat/proc/54321/status|grep-EVmRSS|VmSwap|Threads# pmap 看进程地址空间分布pmap-x54321|tail-20pmap可以看到该进程的各个内存段的映射情况。如果发现某个动态库或匿名映射占用了大量内存可以通过/proc/54321/smaps进一步看细节# 找到最大的几个映射grep-A6^[0-9a-f]/proc/54321/smaps|grep-E^[0-9a-f]|Rss:|paste- -|sort-t:-k2-rn|head-10紧急处理套路确认泄漏后不要花时间找具体代码行线上操作优先级重启服务— 立刻止血恢复可用保留现场— 重启前把/proc/pid/smaps和堆 dump 保存下来重启后慢慢分析限流 / 限制连接数— 防止进程重启后再次快速涨到高水位给容器加 Memory Limit— 配置 OOM 后的自动重启策略如 Docker 的--restarton-failure或 K8s 的restartPolicy。追加检查是不是 page cache 吃掉了内存有时候free看到 used 高但实际是 page cache文件缓存。验证# 对比 /proc/meminfo 中 Cached 和 Active(file) 的值grep-E^(Cached|Active\(file\)|Dirty)/proc/meminfo如果 Cached 占用很大而应用的 RSS 总和不大那内存不是泄漏只是 FS 缓存。Linux 会在内存紧张时自行回缩 page cache不需要人工干预。如果实在想立即释放# 危险谨慎使用echo3/proc/sys/vm/drop_caches这条命令应该只在极端情况用。它会丢掉所有 page cache导致磁盘读性能骤降直到缓存重新预热。小结问题主线排查工具一句话原则CPU 高perf/top -H/ 火焰图先看 top 再跟 perf别在线上试错内存泄漏/proc/pprof/jmap先确认趋势再找具体对象最后揪代码上下文切换vmstat/pidstat -wcs 高过 CPU 频率就查锁和线程数如果你在实际排查中有遇到过什么典型案例欢迎分享交流。