服务响应变慢,往往不是单一原因造成的,而是请求链路上某个或多个环节出现了瓶颈。排查需要按照“客户端 → 网络 → 服务入口 → 应用代码 → 系统资源 → 外部依赖”的顺序,由外向内分层定位。下面给出一个实战中经过验证的排查框架和关键工具。
1. 确认问题边界:是用户侧问题还是服务端问题
在登录服务器之前,先做快速的外部分析,避免盲目登录所有机器。
- 从多个网络位置和终端复现:使用浏览器开发者工具(Network 标签)或命令行
curl -w查看各个阶段耗时。
curl -o /dev/null -s -w "time_namelookup: %{time_namelookup}\ntime_connect: %{time_connect}\ntime_starttransfer: %{time_starttransfer}\ntime_total: %{time_total}\n" http://example.com/api
对输出中各阶段耗时的分析可以大致判断:
- DNS 解析慢(
time_namelookup)→ 检查 DNS 服务器或本地缓存; - 连接建立慢(
time_connect)→ 网络延迟高或端口不通; - 等待第一个字节慢(
time_starttransfer)→ 后端处理慢; - 总时长高但上述均低 → 响应体过慢,可能是带宽或应用发送数据慢。
- 使用
mtr(My TraceRoute)代替简单 ping:连续探测从你的机器到目标服务的整条链路,可以看到哪一跳出现丢包或延迟飙升。这对区分本地网络、IDC 网络、服务端网络问题非常有效。
2. 服务器入口层:连接和请求是否进来了
登录服务器后,首先确认流量是否正常到达,以及服务是否可以接受新连接。
- 查看进程监听状态:
ss -tlnp | grep <端口号>
确保服务进程处于 LISTEN 状态,且监听在正确的地址(0.0.0.0 或特定内网 IP)。
- 检查连接队列溢出:半连接队列(SYN)或全连接队列(accept queue)溢出会导致新连接无法建立,表现为有些客户端连接超时。
netstat -s | grep -i "listen" # 查看 TcpExt 字段中 ListenOverflows 或 ListenDrops
ss -lnt # 查看 Recv-Q 和 Send-Q,若 Recv-Q 持续大于 0 可能积压
若发现溢出,需调整内核参数 net.core.somaxconn 和应用的 backlog 设置。
- 观察当前连接数和状态:
ss -s # 汇总统计
ss -tan state time-wait | wc -l # 大量 TIME_WAIT 可能消耗端口,可开启 tcp_tw_reuse
3. 系统资源瓶颈:CPU、内存、磁盘 I/O
服务响应的根因经常落在硬件资源的争用上。
- CPU 瓶颈:
top -bn1 | head -20 # 查看整体 CPU 使用率,关注 us(用户态)、sy(内核态)、wa(等待 I/O)
mpstat -P ALL 1 # 观察每核负载是否均衡
若 wa 高,说明 CPU 在等待磁盘或网络 I/O,转向 I/O 排查;若 us 或 sy 高,需用 pidstat 或 perf top 定位具体进程和函数调用。
pidstat 1 # 实时显示每个进程的 CPU 占用
perf top -p <PID> # 分析进程内部函数热点
- 内存瓶颈:
free -h # 查看内存和 swap 使用,若 swap 使用持续增长,说明物理内存不足
vmstat 1 # 观察 si(换入)和 so(换出)列,持续非零表示严重的内存颠簸
smem -s uss -r # 若安装了 smem,可快速找到占用物理内存最大的进程
若内存不足,检查是否有内存泄漏(通过监控进程的 RSS 长期趋势)或缓存过多(缓存会被自动回收,通常不是问题根源,但若应用要求大量内存,则需要增加物理内存或限制缓存)。
- 磁盘 I/O 瓶颈:
iostat -x 1 # 观察 %util(饱和度)、await(平均等待时间)和 rkB/s、wkB/s
iotop -o # 找出产生 I/O 最多的进程
如果磁盘利用率接近 100% 且 await 很高,结合 df -h 和 lsblk 确定是磁盘性能不足还是某个进程的异常写入(例如日志未轮转导致磁盘满)。对数据库服务器,通常要关注磁盘的随机读写能力(IOPS)。
4. 应用与代码级定位
当硬件资源看起来不算紧张时,需要进到进程内部。
- 慢请求追踪:如果是 Web 服务,启用访问日志并记录请求处理时间。例如 Nginx 可配置
$request_time,Apache 可使用%D。分析日志找出超时的接口和请求特征。 - 系统调用层面观察:
strace -c -p <PID> # 统计进程调用了哪些系统调用,占用时间最多的往往指示瓶颈
strace -tt -T -p <PID> # 实时打印每个系统调用及其耗时,适合实时跟踪
如果发现对文件、网络套接字的 read/write 消耗大量时间,或者在 futex(锁)上挂起,可以进一步深入代码逻辑。
- 利用应用自己的工具:Java 应用可用
jstack打印线程堆栈,结合top -H -p <PID>找到消耗 CPU 的线程 ID;Python 可采用py-spy top -p <PID>直接采样;Go 应用内置 pprof,可分析 CPU profile、goroutine 阻塞。
5. 依赖链排查:数据库、缓存、外部 API
很多慢响应实际卡在查询数据库或调用其他服务上。
- 数据库慢查询:MySQL 启用 slow_query_log 并设置
long_query_time;使用show processlist观察正在运行的查询;分析EXPLAIN执行计划。 - 连接池耗尽:如果应用使用连接池,检查数据库连接数是否达到最大限制,日志中可能出现“cannot get connection”等错误。可通过数据库管理工具或命令(如 MySQL 的
show variables like 'max_connections')对比当前连接数。 - 缓存穿透/击穿:Redis/Memcached 响应变慢或未命中率突然升高,可监控缓存实例的延迟和命中率,使用
redis-cli --latency检查 Redis 响应时间。 - DNS 解析超时:应用代码中对外部域名的解析可能在高峰期延迟,可以使用
strace查看是否有大量的getaddrinfo调用,或检查/etc/resolv.conf中的 DNS 服务器是否可靠。
6. 网络深层排查
如果前几层均未发现异常,且流量明显没到应用层,就需深入数据包层面。
- 抓包分析:
tcpdump -i eth0 port 80 -w slow.pcap # 抓取一段时间的数据包
用 Wireshark 或 tcpdump -r 分析 TCP 握手重传、零窗口、Keep-Alive 超时等。特别关注三次握手是否完成、是否有大量重传(可能是网络质量差或中间设备丢包)。
- 检查网卡统计:
ethtool -S eth0或ip -s link查看是否有错误(errors)、丢弃(dropped)、超限(overruns)。如果存在,可能是网卡、交换机或网线问题。
7. 信息汇总与隔离
全链路排查不是机械地执行每一个命令,而是有逻辑地形成“假设-验证”循环。建议时刻保持一个排查日记,记录每一步观察到的数据,避免在多个仪表盘之间反复横跳。
可以将以上检查点封装成一个简单的脚本,快速生成“现场快照”,在问题期间执行一份留作分析和对比。
实践中,大约 70% 的服务响应慢问题最终定位在应用代码的锁争用、数据库慢查询、内存不足或错误的网络配置上。按照“由外向内、逐层收窄”的方法,通常能在几分钟内找到主要瓶颈。