strace 跟不动的线上问题:抓 syscall 逐层定位方法
一、从进程卡死到 syscall 现场
线上告警群里最常见的描述是"进程还在,但就是不干活了"。这时候第一反应往往是 strace -p,可真挂上去,屏幕上刷出来的可能是几十万行 futex、epoll_wait、clock_nanosleep 混在一起,Ctrl+C 都按不赢。Kernel 5.15 上实测,抓一个长连接网关进程 3 秒,输出 8 万多行,光靠肉眼翻根本找不出卡点。更麻烦的是 strace 本身会放大问题:每个 syscall 两次上下文切换,高并发进程被 ptrace 之后,耗时从 50ms 涨到 400ms,原本只是慢的接口直接超时,现场被观测者改写了。所以第一步不是抓,而是先缩小范围。用 cat /proc/12345/status 看 State,D 状态说明卡在 IO,S 加高 utime 说明在空转,再配合 /proc/12345/stack 和 wchan 拿到内核侧阻塞点,通常能直接落到某个文件系统或锁等待上。确认有 syscall 层面的悬念再上工具:strace -p 12345 -T -tt -e trace=network,desc -o /tmp/s.log,只留文件描述符和网络两类,退出时用 -c 拿汇总,看哪类调用占比和总耗时最不正常。比如一次线上排查里,write 只占调用数 2%,但累计耗时 11.6 秒,问题就直接指向某个 fd 的写缓冲满了。
二、高频调用与阻塞点的辨识
把 -c 汇总拿到手,第一件事是看分布而不是看绝对值。同样 3 秒采样,一个正常网关进程的 syscall 结构大致是 futex 和 epoll_wait 占大头,各自几十个百分点;一旦某个 fd 相关调用突然顶到前列,就该顺着数下去。常见误判是把高频当热点:futex 一秒几万次往往只是锁竞争的正常表现,真正拖住业务的是单次耗时长的调用。判断办法是给 strace 加 -T,每行末尾会打印该次 syscall 的内核耗时,正常 read/write 在百微秒级,超过 10ms 的就要单独记。更省事的是 -c -w,按 wall clock 统计而不是 CPU 时间,阻塞调用会自己浮上来。实测一个卡住的消费进程,recvfrom 调用数只占 3.1%,但 -w 下累计 wall 耗时 27.4 秒,平均每次 1.8 秒,对照 /proc/12345/fd 发现它读的是一个没人写的 socket pair,阻塞点就此锁定。再往下要区分是等锁、等 IO 还是等网络:epoll_wait 返回后紧跟的 read 若报 EAGAIN 说明是空转轮询,若长时间停在 read 上则是对端不吐数据,用 ss -tnp 看该 fd 的 Recv-Q 是否为零即可确认。必要时上 perf trace -p 12345,它对高并发进程的放大比 strace 小一个量级,5.15 内核上采样同样 3 秒只引入约 15% 额外开销。
三、落到源码级的最小复现
定位到具体 syscall 之后别急着改线上配置,先在本地把那一行复现出来。回到上面那个消费进程的例子,拿到阻塞 fd 的 inode,用 ls -l /proc/12345/fd | grep socket 对出 socket:[4026531841],记下这个号。然后在测试机上写一段最小代码,只保留出事那次调用的形状:一个 socketpair、一端只写不读、另一端按线上同样的缓冲大小 212992 去读。跑起来用 strace -T -e trace=recvfrom,sendto,write ./repro 盯住,正常情况下 recvfrom 每次都在 80 微秒内返回,一旦对端停写,第一行超过 10ms 的 recvfrom 就会跳出来,跟线上 -T 日志里的 1800 毫秒量级对得上,说明形状对了。再把缓冲调到实际值验证边界:内核 net.core.wmem_default 默认 212992 字节,写完这个量对端不读,sendto 立刻返回 EAGAIN,报错原文是 Resource temporarily unavailable,这就复刻了“没人写的 socket pair”。复现出来后缩到二十行以内,去掉业务逻辑,只留 socketpair 加两次循环,然后逐个变量往回拧:缓冲大小、读写方是否阻塞、是否有 O_NONBLOCK,一次只动一个,看哪一个改回去阻塞就消失。等最小例子里 recvfrom 的耗时从 1800 毫秒掉回微秒级,根因就钉死在这一行的语义上了,再回线上改才有依据。
- 点赞
- 收藏
- 关注作者
评论(0)