系统突然变慢这个问题,我在面试里问过别人,也在实际生产环境里被问过无数次。大多数人的第一反应是“上去 top 看一眼”,这个没错,但如果只会 top,那你就永远停在“发现 CPU 高”这一步,没法真正回答“为什么慢”“卡在哪”。字节这场一面把问题设在“某一天突然变慢”,重点其实不是让你背命令,而是考察你在一个未知的、紧急的、影响面可能很大的故障面前,有没有一套稳定、可复现、能落地的排查思路。这篇文章,我就按自己实际处理故障的路径,把这个排查过程完整拆开讲讲。
1. 先建立排查框架,别急着找代码
很多人一上来就盯着某个进程、某个日志,这是典型的“先开枪后瞄准”。系统突然变慢,可能的原因太多了:流量突增、代码 bug、数据库抖动、网络延迟、磁盘写满、依赖服务超时、甚至是隔壁租户把宿主机资源吃光了。没有框架地乱撞,只会浪费时间,而故障处理最贵的就是时间。
1.1 为什么这个问题几乎是面试必考题
“系统变慢”是一个结果,不是一个原因。它背后是一个长长的因果链,从用户请求到应用处理,再到数据库返回,任何一个环节断裂都会被感知成“系统卡了”。面试官问这个问题,本质是想看你有没有建立一套完整的因果链认知。
我理解,一个合格的排查者脑子里应该有一张地图:请求链路经过哪些节点、每个节点有哪些风险点、哪些指标能反映这个节点的状态、哪些工具能拿到这些指标。没有地图的人靠猜,有地图的人靠定位。两者花费的时间可能是 10 分钟和一个下午的差距。
1.2 从现象到根因:一条主线,六个方向
我自己的排查逻辑可以归纳成一句话:从全局到局部,从系统到应用,从硬件到代码,逐层缩小包围圈。
展开来说,就是六个方向:
- 系统层:CPU、内存、磁盘、网络。这是最底层的资源,任何上层问题最终都会反映到资源的消耗或阻塞上。
- 应用层:进程状态、线程状态、GC、锁、日志。这是代码运行的地方,很多“慢”最终都表现为线程卡住、资源争抢。
- 中间件层:数据库、缓存、消息队列。分布式环境下,应用慢很多时候不是应用自己的问题,而是它依赖的东西慢了。
- 链路层:调用关系、依赖超时、重试机制。一个慢接口可能拖垮调用方,形成雪崩。
- 变更面:发布、配置、流量切换、数据迁移。大量故障都发生在变更之后,先查变更记录能省一半时间。
- 容量面:是否到达了系统瓶颈。比如线程池满了、连接池耗尽、带宽打满,这类问题不是“哪里坏了”,而是“不够用了”。
这六个方向不是每次都要全部排查,而是一个排查清单,按优先级和可能性逐个验证,快速排除。
1.3 排查前的三件事:确认“变慢”的定义、影响范围、变更记录
实战里,我第一次遇到线上告警时也是慌的,后来才总结出,动手之前必须先确认三件事,否则很容易被带偏。
第一,确认“变慢”的具体定义。是接口响应时间变长?还是页面加载变慢?还是定时任务跑不完?还是数据库查询卡顿?“慢”的表现不一样,排查路径完全不同。比如接口慢,可能是依赖超时;但定时任务慢,大概率是资源竞争或者数据量暴增。
第二,确认影响范围。是所有请求都慢?还是部分接口慢?是单台机器慢?还是整个集群慢?这个信息能快速区分是局部问题还是全局问题。如果只有一台机器慢,可以先怀疑这台机器的资源或者网络;如果整个集群都慢,那问题很可能出在下游公共依赖。
第三,必须要看变更记录。我相信每一个运维人都踩过这种坑:排查了半天,最后发现是前一天晚上发了一个新版本,或者改了一条配置。变更是故障的第一大诱因,排查前花两分钟确认有没有变更,能少走很多弯路。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 第一轮体检:系统层四件套
框架搭好了,接下来就是动手。我的习惯是先用一套“系统层四件套”快速摸一遍全貌,也就是负载、CPU、内存、磁盘、网络这几个基础指标。这套操作熟练的话三五分钟就能完成,但是信息量非常大。
2.1 用 uptime 看负载,先判断是忙还是堵
登录服务器的第一件事,我一般是敲 uptime。它返回的是系统最近 1 分钟、5 分钟、15 分钟的平均负载(load average)。
code复制$ uptime
14:32:10 up 320 days, 2:41, 1 user, load average: 22.31, 18.07, 12.55
这个命令看起来简单,但读起来有门道。load average 是处于运行状态和不可中断状态的进程平均数量,它不是一个百分比,而是一个数值。看它要和 CPU 核数对比才有意义。比如一台 32 核的机器,负载 22 说明还有余量;但如果是 4 核的机器,负载 22 已经是灾难了。
三个时间点数值的变化趋势也很关键。如果 1 分钟负载远高于 15 分钟负载,说明系统是突然繁忙起来的,符合“突然变慢”的特征;如果三个数值都很高且接近,说明系统已经持续高负载很长时间了,可能是一个渐进式问题的爆发。
另外要注意,负载高不一定是 CPU 忙,也可能是 IO 等待。uptime 只能告诉你系统“忙不忙”,具体忙在哪,需要配合后面的命令看。
2.2 top:CPU 和内存的第一现场
第二站,top。这是绝大多数人熟悉的命令,但很多人只是看一眼 %CPU 最高的进程就完事了,这远远不够。
code复制$ top -c
top - 14:35:22 up 320 days, 2:44, 1 user, load average: 22.31, 18.07, 12.55
Tasks: 521 total, 1 running, 520 sleeping, 0 stopped, 0 zombie
%Cpu(s): 65.3 us, 8.2 sy, 0.0 ni, 22.5 id, 2.1 wa, 0.0 hi, 1.9 si, 0.0 st
MiB Mem : 128746.7 total, 102345.2 free, 8123.4 used, 18278.1 buff/cache
MiB Swap: 32768.0 total, 32768.0 free, 0.0 used
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
5678 admin 20 0 23.567g 4.345g 1.234g S 213.0 3.4 345:23.56 java
我最关注 top 里的几个信息:
第一是 %Cpu(s) 这一行的细分项。us 是用户态 CPU,sy 是内核态 CPU,wa 是 IO 等待,si 和 hi 是软硬中断。如果 wa 特别高,说明磁盘 IO 跟不上了;如果 sy 特别高,可能是系统调用太频繁或者上下文切换太多;如果 st 很高,说明你可能跑在虚拟机上,宿主机资源被抢了。这些细节决定了下一步往哪走。
第二是进程的 CPU 占用和 TIME+。TIME+ 是进程累计消耗的 CPU 时间,如果一个进程 %CPU 并不高但 TIME+ 特别大,说明它一直在消耗 CPU,只是这一刻不是峰值;如果 %CPU 超过 100%,说明它使用了多核。
第三是内存。RES 是实际物理内存占用,VIRT 是虚拟内存,经常有人被 VIRT 吓到,其实 java 和其他语言常见的大 VIRT 是正常的,主要看 RES 和内存总量是否匹配,以及有没有发生 swap。
top 进入交互模式后,按 P 按 CPU 排序,按 M 按内存排序,这两个快捷键我几乎每个排查场景都会用。
2.3 vmstat 看内存与 IO 的联动
top 是瞬间快照,而系统慢通常是持续状态,所以我一般接下来会敲 vmstat 1 5,每 1 秒打一次,连续打 5 次。这个命令能看清楚各项指标的动态变化趋势。
code复制$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
4 2 0 102345 18278 32456 0 0 34 56 1250 23000 65 8 22 2 0
6 1 0 102340 18278 32458 0 0 45 70 1340 25100 70 8 17 3 0
5 2 0 102338 18278 32460 0 0 40 65 1280 24000 67 9 20 2 0
这里我重点看几个指标:
r(running)和b(blocked):r 是正在运行的进程数,如果长期大于 CPU 核数,说明 CPU 已经成为瓶颈;b 是阻塞的进程数,如果长期大于 0,说明有 IO 等待或者锁等待。si和so(swap in/out):如果这两个值不为 0,说明内存在频繁换页,内存严重不足。这是非常危险的状态。bi和bo(block in/out):磁盘读写的块数量,如果 bi 很高说明大量读磁盘,可能是内存不足导致缓存失效,也可能是查询量太大。cs(context switch):上下文切换次数,如果特别高,比如每秒几万次,说明线程太多或者锁竞争严重,这个数值高常常是“系统很忙但 CPU 不高”的原因。wa:IO 等待,和 top 里的 wa 对应。
vmstat 的价值在于联动。比如 r 很高、us 很高,说明 CPU 真在计算;r 很高、wa 很高,说明进程都在等 IO;si/so 很高,说明内存才是根源。看到组合,就能排除掉大量不可能。
2.4 iostat 确认磁盘是不是瓶颈
如果 vmstat 里 wa 高、b 高,下一步我会用 iostat -x 1 看每块磁盘的详细情况。
code复制$ iostat -x 1
avg-cpu: %user %nice %system %iowait %steal %idle
45.0 0.0 10.0 35.0 0.0 10.0
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz %util
vda 120.0 45.0 10240.0 2048.0 0.0 0.0 0.0 0.0 8.0 12.0 2.4 85.3 45.5 80.0
这里我最关心几个值:
%util:磁盘忙闲比例。注意它接近 100% 不代表磁盘“坏了”,而是说明磁盘已经满负荷运转。但它是个平均值,对 SSD 和机械盘的解读不一样。r_await和w_await:单次 IO 的响应时间,这个是判断磁盘是否“慢”的核心指标。机械盘一般延迟在 10ms 级别,SSD 在 0.1ms 级别。如果 r_await 很高,说明磁盘响应慢,可能是硬件故障也可能是 IO 队列太长。aqu-sz:IO 队列长度,如果这个值持续很大,说明请求在排队,磁盘处理不过来。
iostat 还有一个作用,就是能看出来是读多还是写多、请求大小是否正常。如果写等待特别高,可能是刷日志太频繁;如果读等待高,可能是数据库频繁扫盘。
2.5 网络层别忽略:带宽和连接数
网络问题经常被当成“玄学”,其实也有一串可以量化的指标。我常用的工具是 sar -n DEV 1 看网卡流量,ss -s 看连接状态汇总,ss -tn 看具体连接。
code复制$ sar -n DEV 1
Linux 3.10.0-1127.el7.x86_64 (hostname) 05/27/2024
14:40:01 IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil
14:40:02 eth0 45000.00 32000.00 55000.00 48000.00 0.00 0.00 0.00 40.50
%ifutil 是网卡利用率。如果接近 100%,说明带宽被占满,导致请求排队、响应变慢。很多“突然变慢”的故障,最后查出是某个任务在大量上传下载,把带宽打满了。
连接状态也要看。如果 ss -s 里出现大量 TIME_WAIT 或者 CLOSE_WAIT,说明连接没有正常回收。尤其是 CLOSE_WAIT 堆积,通常是服务端代码没有正确关闭连接,会导致连接被耗尽,新请求进不来,表现就是“系统很卡”。
3. 第二轮深挖:应用层定位
系统层体检完,通常能筛出一到两个重点怀疑对象。如果系统层的 CPU、内存、IO、网络都正常,但服务还是慢,那问题大概率出在应用本身。这一轮我们要从“机器”下钻到“代码”。
3.1 从进程到线程:找到那个“罪魁祸首”
假设 top 里发现某个 java 进程 CPU 很高,比如占了 800%CPU,那说明它用了 8 个核。这时候要知道到底是哪些线程在消耗 CPU,Java 里线程和系统线程有对应关系,需要用 top -Hp 查看进程内线程的 CPU 占用。
code复制$ top -Hp 5678
PID USER PR NI VIRT RES SHR S %CPU TIME+ COMMAND
5701 admin 20 0 23.567g 4.345g 1.234g R 120.0 345:23.56 java
5723 admin 20 0 23.567g 4.345g 1.234g R 80.0 334:12.33 java
5732 admin 20 0 23.567g 4.345g 1.234g S 0.0 12:23.11 java
找到高 CPU 的线程 ID(比如 5701),转成十六进制,就是 printf '%x\n' 5701,得到 0x1645。然后用 jstack 抓线程栈,在 dump 文件里搜 nid=0x1645,就能定位到具体代码位置。
这一步是很多人的知识盲区。很多人知道 jstack,但不知道要先通过 top -Hp 找出线程 ID 再精准定位,导致抓了一整份 dump,几十MB 文本,根本看不出问题是哪个线程引起的。
3.2 jstack 抓线程栈的正确姿势
jstack <pid> > thread_dump.txt 是最常规的操作,但怎么抓、什么时候抓、抓几次,都有讲究。
第一,要连续抓。我一般间隔 5 秒抓一次,连续抓 5 次。单次 dump 只能看到某一瞬间的状态,而线程问题是有节奏的,连续多次能看到哪些线程一直处于同一个状态。比如一个线程每次 dump 都卡在同一个锁上,那这个锁基本就是瓶颈。
第二,重点关注几种线程状态:
RUNNABLE:正在执行。但要小心,Thread Dump 里的 RUNNABLE 可能是在用户态计算,也可能是在等网络 IO(TCP 连接读不到数据时 Java 层面也显示 RUNNABLE)。BLOCKED:等待锁。如果大量线程都 BLOCKED 在同一个锁对象上,说明锁竞争太严重。WAITING/TIMED_WAITING:等待通知或者等待时间到期。大量线程处于这个状态不一定有问题,但要是线程池线程全部 WAITING,说明没任务来;如果任务堆积但线程都在 WAITING,那就不正常了。
第三,定位锁竞争。jstack dump 里看到 waiting to lock <0x00000000xxxx> (a com.example.XxxLock),再查谁的线程 stack 里 locked <0x00000000xxxx>,就能找到持锁线程。锁竞争最恶心的场景是持锁线程自己又去调用了外部慢接口,直接导致所有线程一起卡住。
除了 jstack,Arthas 也是一个非常趁手的工具。thread -n 3 可以直接找到 CPU 占用最高的 3 个线程,thread -b 可以一键检测死锁。我一般是在生产环境允许的情况下优先用 Arthas,因为它交互式操作比 jstack 打 dump 再分析要快很多。
3.3 内存与 GC:别被表象骗了
Java 应用慢,GC 问题是非常高发的原因。系统指标看起来 CPU 不高、内存也够用,但接口就是慢,这时候要怀疑 GC。GC 期间会触发 Stop The World,整个应用暂停,表现就是“卡一下”。
常用的命令是 jstat -gcutil <pid> 1000 10,每秒打一次,连续 10 次:
code复制$ jstat -gcutil 5678 1000 10
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 99.60 78.45 85.20 95.30 96.50 10234 234.456 5 10.234 244.690
0.00 99.60 79.10 85.30 95.30 96.50 10234 234.456 5 10.234 244.690
我先看 O(老年代使用率),如果持续在 85% 以上,而且 FGCT(Full GC 时间)在持续增加,说明频繁 Full GC,老年代快满了。再看 YGC(Young GC 次数)和 YGCT(Young GC 耗时),如果次数非常多,说明对象创建速度太快,或者 Survivor 区设置不合理。
GC 频繁的根因,通常可以排查三个方向:一是内存泄漏,对象回收不掉,老年代持续上涨;二是对象分配过大,比如一次查了太多数据全部加载到内存;三是代码里有大对象或者缓存没设上限。
我处理过一个案例,接口每次请求都把 10MB 的配置从数据库查出来放内存缓存,然后缓存永不过期,结果 O 区持续增长,每过半小时就 Full GC 一次。那个排查过程就是通过 jstat 发现 FGC 频繁,然后 dump 堆内存,用 MAT 分析出缓存对象占了 90% 的堆空间。
3.4 日志分析:从错误到流量异常
如果 GC 正常,线程也正常,那就要看日志。日志是应用留给我们的第一手现场证据。
我的习惯是先看日志有没有大量 ERROR/WARN,尤其是超时、连接拒绝、重试这类的关键字。很多时候“系统慢”其实是下游服务慢,应用在等下游返回,体现在日志里是大量 read timeout 或者 connection refused。
第二是看请求量和响应时间的变化趋势。如果日志里同一个接口的耗时从平均 50ms 突然涨到平均 2000ms,那就对比这个时间点前后发生了什么变化。是某个调用方开始疯狂请求?是 SQL 突然变慢?还是下游响应变慢?日志的时间线是还原故障现场的最好材料。
这里强烈建议,平时就先把结构化日志做好。request_id、耗时、调用方 IP、错误码、异常堆栈一个都不能少,否则排查时只能靠猜。尤其是分布式环境下,没有 request_id 几乎没法串联一条完整调用链。
4. 第三轮下沉:数据库与中间件
应用层排查完,如果还没定位到根因,那就要考虑“慢”不在应用本身,而是应用依赖的数据库、缓存、消息队列慢了。这一层的问题往往影响面更大,因为一个慢数据库会拖垮所有依赖它的服务。
4.1 慢 SQL:先抓凶手级查询
数据库是最常见的“背锅侠”,也是真正的高发环节。一条本来执行 10ms 的 SQL,因为数据量涨了、索引失效、或者被锁阻塞,可能变成 10 秒,整个接口就卡死了。
排查数据库第一个动作是看慢查询日志。MySQL 里执行:
sql复制SHOW VARIABLES LIKE 'slow_query_log%';
SHOW GLOBAL STATUS LIKE 'Slow_queries';
如果慢查询开关没开,那接下去的排查就很被动。所以生产环境建议长期开启慢查询日志,并设置合理的阈值,比如 long_query_time = 1。
拿到慢 SQL 之后,用 EXPLAIN 分析执行计划,重点看这几个列:
type:如果是ALL(全表扫描)或者index(全索引扫描),大概率有问题。rows:预估扫描行数,如果特别大,说明没走对索引。Extra:如果出现Using filesort或者Using temporary,说明排序和临时表操作很重。
除了慢 SQL,还要看数据库的当前状态。SHOW PROCESSLIST 能直接看到正在执行的 SQL 和它们的状态。如果有大量会话卡在 Waiting for table metadata lock,说明有 DDL 阻塞了查询;如果有大量会话处于 Sending data,可能是真的在查大结果集。
另外连接数也是一个关键指标。如果 Threads_connected 满了,应用拿不到连接,表现就是接口超时。我之前遇到过一例,是连接池最大连接数设了 100,但有个死循环在不停申请连接,一下子把连接池打满,所有正常请求都在排队等连接,业务“变慢”了半个小时才发现。
4.2 缓存排查:命中率下降是强烈信号
如果系统用了 Redis,还要确认缓存本身的状况。缓存的 key 大量过期、缓存被清空、或者缓存服务本身出现慢查询,都会导致请求直接打到数据库,数据库压力上来了,整个系统自然就慢了。
常用的排查命令:
bash复制redis-cli info stats
redis-cli info commandstats
redis-cli monitor
info stats 里看 instantaneous_ops_per_sec 和 keyspace_hits / keyspace_misses,可以算出命中率。如果命中率从 95% 降到 50%,那说明大量请求穿透到数据库了,数据库压力必然升高。
还有一个常见问题叫缓存雪崩/击穿,就是大量 key 同时过期,或者一个热点 key 失效,导致突发流量全部打到数据库。排查方式就是看过期时间设置,以及有没有做永久 key + 后台更新的策略。
值得多说一句,很多团队出问题后第一反应是“给 Redis 扩容”,其实缓存命中率低才是根因。扩容只是缓解了数据库压力,并没有解决“缓存为什么没生效”的问题。
4.3 消息队列与依赖服务:链路阻塞的排查
现在的业务系统基本都是微服务架构,一个请求要经过多个服务。如果 A 服务调 B 服务,B 又调 C,C 慢了,A 和 B 都会慢。我在实际故障处理中,经常发现最下游的服务只慢了几十毫秒,放大到上游就成了几秒钟。
排查链路问题,如果公司有全链路追踪系统(比如 SkyWalking、Zipkin、Jaeger),直接看 trace 里哪个节点耗时最长,这是最有效的方式。如果没有这类系统,就只能逐层排查:先确认 A 调 B 的耗时,再确认 B 调 C 的耗时,逐层往下,直到找到瓶颈。
消息队列层面的排查也不容忽视。如果应用是异步消费消息的,比如 Kafka 消费端卡住,会导致消息积压,业务数据处理延迟,表现也是“系统变慢”。我一般会用 kafka-consumer-groups.sh --describe 看消费组的 lag,或者看队列积压数。如果消费速率远小于生产速率,那问题出在消费者,可能是消费逻辑太慢,也可能是消费者挂了。
5. 面试复盘与常见误区
最后回到这道面试题本身。它考察的不只是技术,还有表达和应变。面试官想要听到的,是一个思路清晰、有层次、有实例的排查过程,而不是零散命令的堆砌。
5.1 一个高分的回答框架
我个人建议,如果面试中被问到这类问题,可以按照下面的结构来组织语言:
先表明态度。比如“我会把排查过程分为系统层、应用层和依赖层三步,每一步用对应的工具和指标来快速验证,而不是盲目猜测”。这句话会给面试官一个印象:你有方法论。
然后讲第一步,系统层。用 uptime 看负载、top 看 CPU 和内存、vmstat 看 IO 和上下文切换、iostat 看磁盘、sar 看网络,快速定位资源层面的瓶颈。
再讲第二步,应用层。用 top -Hp 找线程,jstack / Arthas 看线程栈,jstat 看 GC,日志看异常和耗时。
最后讲第三步,依赖层。看数据库慢查询、连接数、Redis 命中率、消息队列积压,以及全链路追踪系统。
每一步都要强调“什么指标对应什么结论”,而不是念命令。面试官追问“如果 CPU 不高但系统很慢怎么办”的时候,顺着链路往下走就行:不高就看 IO,不高就看内存换页,不高就看锁和 GC,还不行就看下游依赖,一定有一个环节印证出问题。
5.2 最常见的五种跑偏思路
这些年我见过很多排查者,也读过很多事故复盘报告,发现有几类典型错误反复出现:
只盯 CPU,忽略负载和等待。 系统慢不一定是 CPU 高,IO 等待、锁等待、网络等待都会让系统变慢,但 CPU 百分比却可能是低的。这类误判最典型的情况是数据库慢查询导致大量线程在等数据,CPU 反而是空闲的。
抓了 dump 不会看。 有人确实执行了 jstack,但把 50MB 的 dump 从头翻到尾,看不出所以然。不会用 top -Hp 定位线程,不知道 nid 要转十六进制,也不知道连续抓几次做对比。这等于白抓。
忽略变更。 很多故障明明就是一次配置变更导致的,但排查者一上来就怀疑代码、怀疑数据库,兜了一大圈才想起来昨天有过变更。这个习惯一定要养成:先查变更,再查故障。
查库不看连接池。 数据库 CPU 不高、慢 SQL 也没有,但应用就是拿不到连接。这类问题光看数据库本身永远查不出来,必须结合应用的连接池状态一起来看。
分不清主次,同时操作多个工具。 新手最容易犯的错是又开 top 又开 jstack 又开日志,一小时过去了,什么都没定位清楚。正确的做法是一条线走到底,系统层有线索就沿着系统层往下挖,应用层有线索就沿着应用层往下挖,每条线走到底再换线。
5.3 排查工具箱速查表
这里把常用的工具和命令整理成一张表,方便收藏和复习:
| 排查对象 | 命令/工具 | 核心关注点 |
|---|---|---|
| 系统负载 | uptime |
1/5/15分钟负载趋势 |
| 进程与CPU | top -c |
%CPU、%MEM、TIME+ |
| 线程定位 | top -Hp <pid> |
高CPU线程ID |
| 内存与IO联动 | vmstat 1 |
r/b、si/so、wa、cs |
| 磁盘IO | iostat -x 1 |
%util、r_await、w_await |
| 网络流量 | sar -n DEV 1 |
%ifutil、rxkB/s、txkB/s |
| 连接状态 | ss -s、ss -tn |
TIME_WAIT、CLOSE_WAIT |
| 线程栈 | jstack <pid>、Arthas thread |
BLOCKED、WAITING、锁持有 |
| 堆内存与GC | jstat -gcutil <pid> 1000 |
FGC次数、FGCT、老年代占比 |
| 堆转储分析 | jmap -dump + MAT |
大对象、内存泄漏 |
| 数据库慢查 | 慢查询日志、EXPLAIN |
type、rows、Extra |
| 数据库实时状态 | SHOW PROCESSLIST |
锁等待、长事务、连接数 |
| Redis状态 | redis-cli info stats |
命中率、ops、延迟 |
| 消息积压 | Kafka describe -group |
lag、消费速率 |
| 链路追踪 | SkyWalking / Zipkin / Jaeger | 各节点耗时分布 |
这个工具箱不是一个囤积清单,而是一套可以随时调用的武器库。每个工具对应的指标,你都能解读它的业务含义的时候,排查的速度自然就上来了。
排查系统变慢这件事,说到底是一个“用证据还原现场”的过程。我在实操中最深的体会是:不要急着下结论,也不要被第一个看到的异常带偏。 曾经有一次线上故障,top 里明显是 CPU 高,但顺着线程栈查下去,发现是一个下游系统超时导致的应用层重试风暴,CPU 高只是结果而不是原因。如果当时看到 CPU 高就开始优化代码,这个故障永远找不到根因。
每次排查完,我都会顺手把结论记录成一篇复盘文档,哪怕只有几行字。积累得多了,发现自己面对新故障时,第一反应从“慌”慢慢变成了“按照流程来”,这种掌控感,比背十个命令都值钱。希望这篇文章也能帮你在下一次遇到“系统突然变慢”的时候,少走几步弯路,早点找到那个真正的凶手。
