服务起不来,日志没报错。进程在跑,但就是不干活。
这种问题最恶心,看日志看不出问题,看监控也没异常。
这时候就需要strace和lsof这两个神器了。
strace:跟踪系统调用
strace能看到进程在做什么系统调用,相当于给进程装了个监控摄像头。
基本用法
bash
# 跟踪一个命令
strace ls
# 跟踪正在运行的进程
strace -p <pid>
# 跟踪子进程
strace -f -p <pid>
案例一:服务启动卡住
现象:Java服务启动后卡住,不打印任何日志。
bash
# 找到进程号
ps aux | grep java
# 假设是12345
# strace跟踪
strace -p 12345
输出:
arduino
futex(0x7f8a8c000000, FUTEX_WAIT_PRIVATE, 0, NULL
卡在futex,说明在等锁。
进一步看是什么锁:
bash
strace -p 12345 -e trace=futex -T
结合jstack看线程栈:
bash
jstack 12345 > thread.dump
grep -A 20 "BLOCKED" thread.dump
发现是启动时连接数据库,数据库连不上,超时时间设太长了。
案例二:文件读写问题
现象:服务很慢,但CPU和内存都不高。
bash
# 只看文件相关的调用
strace -p 12345 -e trace=file
# 或者看所有IO
strace -p 12345 -e trace=read,write,open,close
输出:
scss
open("/data/logs/app.log", O_WRONLY|O_APPEND) = 3
write(3, "2024-12-23 10:00:00 INFO...", 1024) = 1024
close(3) = 0
open("/data/logs/app.log", O_WRONLY|O_APPEND) = 3
write(3, "2024-12-23 10:00:00 INFO...", 1024) = 1024
close(3) = 0
...
每次写日志都open-write-close,频繁的文件操作导致性能差。
改成保持文件句柄打开,或者用异步日志。
案例三:网络问题
现象:服务偶尔超时。
bash
# 只看网络相关
strace -p 12345 -e trace=network -T
输出:
scss
connect(5, {sa_family=AF_INET, sin_port=htons(3306), sin_addr=inet_addr("10.0.0.1")}, 16) = -1 ETIMEDOUT (Connection timed out) <30.001234>
连接数据库超时30秒,问题找到了。
常用参数
bash
# -f:跟踪子进程
strace -f -p 12345
# -T:显示每个调用耗时
strace -T -p 12345
# -t:显示时间戳
strace -t -p 12345
# -c:统计系统调用次数和耗时
strace -c -p 12345
# -o:输出到文件
strace -o trace.log -p 12345
# 组合使用
strace -f -T -t -o trace.log -p 12345
统计分析
bash
strace -c -p 12345
输出:
lua
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
45.23 2.345678 234 10000 write
30.12 1.234567 1234 1000 read
20.11 0.987654 98 10000 futex
4.54 0.234567 23 10000 clock_gettime
------ ----------- ----------- --------- --------- ----------------
100.00 4.802466 31000 total
一眼就能看出时间花在哪了。
lsof:列出打开的文件
Linux里一切皆文件,lsof能看到进程打开了什么文件、网络连接、设备等。
基本用法
bash
# 查看进程打开的所有文件
lsof -p <pid>
# 查看某个文件被谁打开
lsof /var/log/app.log
# 查看某个端口
lsof -i :8080
# 查看某个用户的所有打开文件
lsof -u root
案例一:端口被占用
bash
# 谁占用了8080端口
lsof -i :8080
输出:
sql
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 12345 root 123u IPv6 123456 0t0 TCP *:8080 (LISTEN)
进程12345占用了8080端口。
案例二:文件句柄泄漏
现象:服务运行一段时间后报"Too many open files"。
bash
# 查看进程打开的文件数
lsof -p 12345 | wc -l
# 按文件类型分组统计
lsof -p 12345 | awk '{print $5}' | sort | uniq -c | sort -rn
输出:
yaml
5000 IPv4
3000 REG
1000 DIR
5000个网络连接?明显有连接泄漏。
bash
# 看看都连了谁
lsof -p 12345 -i | head -20
rust
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 12345 root 123u IPv4 123456 0t0 TCP 10.0.0.1:54321->10.0.0.2:3306 (ESTABLISHED)
java 12345 root 124u IPv4 123457 0t0 TCP 10.0.0.1:54322->10.0.0.2:3306 (ESTABLISHED)
java 12345 root 125u IPv4 123458 0t0 TCP 10.0.0.1:54323->10.0.0.2:3306 (ESTABLISHED)
...
全是连数据库的,连接池用完没归还。
案例三:删除的文件还在占用空间
bash
# 查看已删除但仍被引用的文件
lsof +L1
输出:
c
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
java 12345 root 10w REG 253,1 10737418240 0 12345 /var/log/app.log (deleted)
日志文件被删了,但进程还引用着,10G空间释放不掉。
解决:重启服务,或者truncate文件:
bash
# 找到文件描述符路径
ls -l /proc/12345/fd/10
# 清空内容但不关闭句柄
: > /proc/12345/fd/10
案例四:网络连接分析
bash
# 查看所有网络连接
lsof -i
# 只看TCP
lsof -i tcp
# 只看某个状态
lsof -i | grep ESTABLISHED
# 统计连接数
lsof -i | grep ESTABLISHED | wc -l
# 按目标地址分组
lsof -i | grep ESTABLISHED | awk '{print $9}' | cut -d'>' -f2 | cut -d':' -f1 | sort | uniq -c | sort -rn
组合使用
排查思路
- 先用top/htop看整体
- 用ps看进程状态
- 用lsof看打开了什么
- 用strace看在做什么
实战:服务假死排查
现象:服务进程在,但不响应请求。
bash
# 1. 看进程状态
ps aux | grep java
# 状态是Sl,正常
# 2. 看打开的文件和连接
lsof -p 12345 | wc -l
# 8000+,有点多
# 3. 看网络连接
lsof -p 12345 -i | grep -c ESTABLISHED
# 5000+,太多了
# 4. 看连接状态分布
ss -tnp | grep 12345 | awk '{print $4}' | sort | uniq -c
# 大量CLOSE_WAIT
# 5. strace看在做什么
strace -p 12345 -e trace=network
# 卡在accept上,但新连接进不来
根因:连接池满了,CLOSE_WAIT状态的连接没有正确关闭。
实战:CPU 100%排查
bash
# 1. top找到占用CPU的进程
top -c
# PID 12345 CPU 99%
# 2. 看线程CPU使用
top -H -p 12345
# TID 12346 CPU 99%
# 3. 把线程ID转成16进制
printf "%x\n" 12346
# 303a
# 4. jstack看线程栈(Java)
jstack 12345 | grep -A 30 "0x303a"
# 5. 或者用strace看系统调用
strace -p 12346 -c
远程排查
有时候问题机器在远程,需要登录排查。
我们有几台服务器在不同机房,之前用跳板机一层层跳很麻烦。现在用星空组网把所有机器组到一起,直接SSH过去就能用strace、lsof排查,效率高多了。
常用命令速查
bash
# strace速查
strace -p <pid> # 跟踪进程
strace -f -p <pid> # 跟踪包括子进程
strace -e trace=network -p <pid> # 只看网络
strace -e trace=file -p <pid> # 只看文件
strace -c -p <pid> # 统计
strace -T -p <pid> # 显示耗时
# lsof速查
lsof -p <pid> # 进程打开的文件
lsof -i :<port> # 谁占用端口
lsof -i tcp # 所有TCP连接
lsof +L1 # 已删除但仍占用的文件
lsof -u <user> # 用户打开的文件
总结
| 工具 | 用途 | 典型场景 |
|---|---|---|
| strace | 跟踪系统调用 | 卡死、慢、报错看不出原因 |
| lsof | 看打开的文件/连接 | 端口占用、文件泄漏、连接泄漏 |
排查原则:
- 从宏观到微观
- 从现象到根因
- 不确定就多看几遍
这两个工具用熟了,大部分疑难杂症都能查出来。
有排查经验欢迎评论区分享~