
上周线上有个服务接口偶发超时日志里只能看到“上游组件超时”CPU、内存全正常看监控也找不到异常。我挂上 strace 抓了不到 10 分钟就从系统调用时间戳里找到了真正的等待点。群里一个同事问了一句“strace 还能这么用”——我才意识到不少人对于 strace 的认知还停留在strace -p PID然后盯着一屏刷屏输出发呆的阶段根本不知道它还有过滤、统计、场景化组合这些高级玩法。这一篇是第 17 篇的续篇。上一次已经把 strace 的基础参数和输出格式讲了一遍今天不再重复基础知识直接聊高级技巧和生产环境实战。内容会覆盖怎么用过滤组合拳把噪音降到最低、怎么用统计模式快速排错以及我在真实线上问题里用 strace 定位过的四类典型场景。适合已经接触过 strace 基础命令、想把排查效率真正提上去的读者。1. 想用好 strace先清楚它到底在进程上做了什么1.1 ptrace 与系统调用插桩strace 不是黑魔法它基于 Linux 的 ptrace 系统调用实现。strace 启动后会通过 PTRACE_SYSCALL 请求让内核在目标进程每次系统调用进入和退出时把进程暂停下来strace 趁机读取寄存器里的系统调用号、参数记录返回值然后让进程继续跑。这意味着什么意味着目标进程每调用一次系统调用就要被“戳”两下。理解这一点非常重要因为它直接解释了为什么 strace 的侵入性很强、为什么不能长时间挂在高频进程上。做个生活化类比strace 就像给快递站门口装了一个登记员每个快递进出都要拦下来记录包裹信息、耗时、收件结果。对于快递量小的站点登记员影响不大但面对一天几万件包裹的仓库这个登记员就成了瓶颈本身。好消息是绝大多数线上性能问题最后都会落到某个具体系统调用上。read、write、open、connect、poll、futex这些调用是用户态请求内核干活的唯一入口。应用层再怎么封装慢的问题最终要体现在这些调用上。所以 strace 的价值就是帮你把“接口很慢”这个大问题翻译成“哪个系统调用、阻塞了多久、返回了什么错误”这样具体可查的小问题。1.2 三种工作模式对应三类不同问题我平时用 strace 基本只有三种起手式每种对应不同的排查场景。第一种跟踪一个执行中的命令。适合启动阶段的问题排查比如服务启动时找不到配置文件、初始化连接失败、启动后立刻崩溃。用法很简单strace -f -o /tmp/boot.trace ./app服务启动完成或崩溃后停掉 strace 翻日志就行。第二种附着到一个已经运行的进程。这是生产环境里最常用的方式适合线上已经跑着的服务出现异常strace -p PID需要注意 attach 时机。如果问题已经发生完了再挂上去很可能什么也抓不到——strace 只能看到挂上之后发生的系统调用。所以这种模式更适合问题还在持续发生、或者可以压测复现的场景。第三种跟踪全部线程和子进程。绝大多数服务都是多线程或多进程架构只跟踪主进程往往看不到真正的业务线程在干什么。必须加-f参数让 strace 跟随 fork、vfork、clone 出来的所有子线程和子进程strace -f -p PID如果线程实在太多可以用-ff参数让 strace 为每个进程/线程分别输出独立文件避免所有线程的输出混在一个文件里没法看。选择依据很简单怀疑启动阶段问题就从头跟命令怀疑运行期偶发问题就用-p附着服务有大量 worker 子进程时千万记得加-f。2. 真正提高效率的是过滤组合拳2.1 用 -e 参数把跟踪范围缩到最小很多人 strace 一挂上屏幕上瞬间刷出几百行然后就开始迷茫。问题的根源在于没有做范围收缩。strace 默认记录所有系统调用但你在排查网络问题时根本不需要看 brk、mmap、close 这些噪音。-e参数就是用来指定跟踪范围的。常用写法有几类# 只看文件相关操作 strace -e tracefile -p PID # 只看网络相关操作 strace -e tracenetwork -p PID # 只看描述符操作read/write/close/lseek 等 strace -e tracedesc -p PID # 只看内存分配相关 strace -e tracememory -p PID # 精确指定某几个系统调用 strace -e traceopenat,connect,read,write -p PID实际使用中精确指定系统调用最常见。因为tracefile可能还是太宽而traceopenat,connect,read,write这样点名道姓输出量通常能降一个数量级。还有一个小技巧-e trace%network,%desc这种写法可以一次性组合多个类别。在怀疑“网络相关调用 IO 相关调用”同时有问题时非常方便。2.2 用几个关键参数把输出变成“既短又有价值”过滤掉调用类型之后还有几个参数能进一步压缩输出、提升信息密度。-P path只看指定路径相关的调用。比如我怀疑程序在反复读写某个日志文件可以这样strace -e traceopenat,read,write -P /data/app.log -p PID配合-Z或-z只看成功或失败调用。这是一个容易被忽略但极其好用的参数-Z只看失败的系统调用-z只看成功的。排查“为什么连接失败”“为什么文件不存在”这类问题时用-Z能把所有成功的噪音全部过滤掉屏幕上剩下的全是错误点的errno返回码。# 只看失败的系统调用输出量瞬间大幅降低 strace -Z -e tracenetwork -p PID-y和-yy参数也很实用。它们会让 strace 打印文件描述符对应的具体路径或 socket 地址。比如看到read(12, ...)时你可能不知道 fd 12 是什么加了-y就会显示为read(12/data/app.log, ...)加了-yy还会显示 socket 对端 IP 和端口。字符串截断问题也必须处理。strace 默认只显示参数中的前 32 个字节遇到长路径、长参数、写 SQL 内容时经常截断到没法看。生产排查建议至少设置成 128strace -s 128 -p PID最后是输出重定向。不要直接在终端跑 strace输出刷屏后很难回溯。用-o参数写入文件需要时再 grepstrace -f -tt -T -s 128 -o /tmp/trace.log -e tracenetwork,desc -p PID其中-tt打印带微秒的绝对时间戳-T打印每次系统调用的耗时。这两个参数是定位“慢在哪一次调用”的关键后面实战案例里还会用到。2.3 统计模式不关心过程只想要“排行”有时候你不想看每个调用的细节只想快速知道这进程的系统调用画像。比如压测时 IO 高你想知道是 read 多还是 write 多还是 fsync 拖了后腿。这时候用统计模式timeout 30 strace -c -f -p PID跑 30 秒后strace 会输出一张汇总表统计每个系统调用被调用了多少次、总耗时多少秒、错误数多少、平均耗时多少。配合-S time可以按耗时排序而不是默认按调用次数排序timeout 30 strace -c -S time -f -p PID我习惯这样来做第一轮排查。先用统计模式看“量”和“耗时排行”锁定可疑调用再换细节模式配合-e trace具体调用抓单个调用现场。之前遇到过一个压测场景CPU 不高但磁盘 IO 跑满。我先跑了一轮strace -c -S time结果清楚地显示 fsync 调用次数和总耗时高得惊人而 read、write 反而占比不大。后续顺着 fsync 查下去才发现是业务代码里每写几百字节就同步刷盘一次。这个结论如果不用统计模式光靠肉眼翻输出根本不可能快速发现。3. 生产环境最常见的四类实战场景下面四个案例都是以我在真实线上环境里排查过的经历为原型进程名和组件名都做了脱敏处理但操作步骤和判断思路完全可以照搬。3.1 场景一接口偶发超时日志指向“上游超时”当时某服务 A 每几分钟出现一次接口延迟上游服务 B 的依赖方收到的响应时间飙到 5 秒但服务 B 自己的监控面板看过去一切正常。我先用统计模式跑了 1 分钟timeout 60 strace -c -f -S time -p pgrep app_a | head -1结果显示 poll 和 futex 两项耗时占比最高。poll 是网络等待的典型指标futex 则可能和锁竞争有关。继续细化只抓网络和线程同步相关调用timeout 120 strace -f -tt -T -s 128 -o /tmp/a.trace -e tracepoll,futex,connect,recvfrom,sendto -p PID从 trace 文件里看到绝大多数请求的 poll 都在 1 毫秒内返回但偶发的几个请求中poll 等待事件长达 3 秒多。再结合-tt打出的时间戳对比业务日志里的请求时间点确认是服务 A 的所有线程都被某个慢请求占满后后续请求在连接池里排队等待空闲连接。这个“排队等待”在系统调用层面表现为 poll 长时间阻塞。这个案例里 strace 的核心价值是把笼统的“上游超时”细化成了具体的“poll 等待事件”。如果只盯着业务日志你永远不知道超时时间花在了连接池等待上。3.2 场景二连接被重置服务端报 Connection reset监控群里刷屏服务 B 连外部网关时频繁报 Connection reset业务方第一反应就是“中间链路有问题、网关故意断连”。我直接用失败模式抓timeout 60 strace -f -Z -y -yy -e traceconnect,poll,recvfrom,read -o /tmp/b.z -p PID只看失败调用后输出量少了很多。关键行大概是这样[pid 1234] connect(7/target:10.0.0.5:443, ...) -1 EINPROGRESS (Operation now in progress) [pid 1234] poll([{fd7, eventsPOLLOUT}], 1, 5000) 1 ([{fd7, reventsPOLLERR|POLLHUP}]) [pid 1234] recvfrom(7/target:10.0.0.5:443, ...) -1 ECONNRESET (Connection reset by peer)这里有几个关键判断点。connect 返回 EINPROGRESS 不代表失败非阻塞 socket 的 connect 正常就是先返回 EINPROGRESS然后靠 poll 等待结果。但 poll 返回的 revents 是 POLLERR|POLLHUP说明对端状态异常。随后 recvfrom 返回 ECONNRESETerrno 是 104。结合-yy显示的远端地址再配合 tcpdump 抓包最终定位到服务 B 的连接池长期保持空闲连接而网关侧的 idle timeout 较短网关早就静默关闭了这条连接服务 B 并不知道继续拿着失效连接去发请求于是触发 RST。常见 errno 速查表errno数值常见含义EINPROGRESS115非阻塞操作正在进行中不算错误ECONNRESET104对端主动关闭连接或链路被重置ETIMEDOUT110连接或 IO 超时通常对端不可达或防火墙丢弃EAGAIN11资源暂时不可用非阻塞 IO 下常见EADDRINUSE98本地端口被占用服务重启时常见生产环境里遇到网络报错别只盯着 ECONNRESET 两个字配合-Z和-y把完整调用链拉出来才能分清是发起方的问题还是对端的问题。3.3 场景三频繁小写与 fsync 导致的写放大某服务正在压测CPU 使用率不高但磁盘 IO 占用率持续在 90% 以上。一开始怀疑磁盘有问题。我先做了一轮统计timeout 30 strace -c -f -p PID汇总结果里write 和 fsync 两个调用占据了绝大部分耗时。继续细化看每个请求的写模式timeout 60 strace -f -tt -T -e tracewrite,fsync,fdatasync,openat -o /tmp/fs.trace -p PID从 trace 日志里能看到非常典型的写放大模式业务代码每处理一条请求就执行一次几百字节的 write紧接着调用一次 fsync单次 fsync 平均耗时 10-20 毫秒。在并发压测下大量线程同时做这件事磁盘 IO 自然瞬间被打爆。证明问题的逻辑很简单write 本身很快但 fsync 要求数据真实落盘每次都要等磁盘完成持久化代价远高于 write。如果业务场景允许轻微丢数据比如日志、非关键状态就应该去掉 fsync改成批量写入或定期刷盘。strace 在这里的作用是给出铁证先用量级统计证明 fsync 次数异常再用细节模式证明每次写入量很小而 sync 开销很大。有了这些数据跟开发团队沟通调优方向就有了充分的依据。3.4 场景四启动时读了“错误”的配置某服务升级后行为异常配置明明已经改了程序却不生效。怀疑编译打包过程有问题但一直没找到线索。这种情况下直接用 strace 从头跟踪启动过程最干净strace -f -e traceopenat,access,stat,read -o /tmp/boot.trace ./app启动完成后从日志里过滤 openat 相关的调用grep openat /tmp/boot.trace很快看到了关键路径链程序先尝试打开/etc/app/main.yaml返回 ENOENT再尝试./conf/app.yaml也返回 ENOENT最后成功读取了/opt/app/default.yaml。也就是说程序走的是一套“找不到 A 就找 B找不到 B 就回退到默认配置”的逻辑。配置没生效的原因是环境变量里指定的配置路径不对程序一直在用兜底配置运行。这种配置回退问题单看代码不一定能立刻发现问题因为代码逻辑会告诉你“应该有默认值”。但 strace 会诚实地把每次尝试打开的真实路径和结果列出来一眼就能看出程序实际读的是哪个文件。配合-Z只看失败也可以快速列出哪些路径缺失grep ENOENT /tmp/boot.trace有时候排查问题的路径比读代码更快。4. 我踩过的坑和一句话避坑清单4.1 几个真实“事故”这些年我见过不少人在 strace 上栽跟头自己也踩过坑列出来给大家避雷。第一个坑挂太久。strace 是插桩式跟踪不是采样式监控它会对每个系统调用进行拦截和记录。挂在频繁系统调用的进程上服务吞吐可能直接掉一半以上。有人把 strace 挂在线上进程上一跑就是一整天业务方投诉性能下降回头一看才发现是这个工具没停。现在我都习惯用timeout 30 strace ...这种方式强制限制执行时长。第二个坑日志文件爆炸。多线程服务加-f后输出量会指数级增长-s 256再叠加-tt每秒可能产生几十 MB 日志。曾经有个同事在 /tmp 下直接 nohup 跑 strace回来发现磁盘被写满。记住三点必须用-o指定文件、先df -h看磁盘剩余空间、能加-Z只看失败就优先加。第三个坑attach 权限问题。生产环境经常跑在容器里并且 host 上开着 Yama LSM 保护非父子进程之间 attach 会被拒绝报错类似ptrace: Operation not permitted。这不是 strace 的问题是权限限制。解决方式要么用 root 运行要么调整 ptrace 相关内核参数。第四个坑忘记加-f。排查多进程服务时只 attach 到主进程主进程 fork 出来的 worker 子进程一个都看不到关键问题全漏掉了。我的习惯是不确定服务是不是多线程/多进程时直接加-f输出太乱再用-ff拆文件。第五个坑只看调用名不看返回值和 errno。strace 输出的精髓是对每一行调用后面的返回值和错误码做判断。比如 connect 返回 EINPROGRESS 不代表失败read 返回 -1 时到底 errno 是 EAGAIN 还是 ECONNRESET含义完全不同。养成查 errno 的习惯排查速度能快一倍。4.2 一套可复制的生产排查路径我现在在线上做排查基本遵循一条固定路径分享出来供参考先做外围判断。用日志、监控、perf top、iostat、sar等确认问题大致在哪个层面磁盘 IO、网络、锁、还是进程异常。尽量复现问题。偶发问题可以在压测环境先复现复现不了的准备好命令等待下一个问题窗口。先跑一轮统计模式。timeout 30 strace -c -S time -f -p PID拿系统调用排行锁定可疑目标。换定向细节模式。用-e trace具体调用类型加-Z只看失败或正常模式单独抓可疑点带上-tt -T看时间戳和耗时。把 strace 时间戳和业务日志时间点对齐。这一步能确认问题是否集中在特定请求上。强制定时停止。不管有没有收获到时间就停恢复系统状态。模板命令# 统计模式先看排行 timeout 30 strace -c -f -p PID # 细节模式只看失败 网络类调用带时间和耗时 timeout 60 strace -f -tt -T -s 128 -Z -o /tmp/trace.err -e tracenetwork,desc -p PID这套流程的核心逻辑是先粗后细、先少后多、主动设限。不要一上来就动细节跟踪那样容易被海量输出淹没。5. strace 不够用的时候你还可以试试这些5.1 ltrace 和 gdb负责更细的层次strace 只能看到系统调用但有些问题发生在用户态的库函数层面比如 malloc、free、pthread_mutex_lock 这些调用是库函数不是系统调用。这种时候 ltrace 更合适ltrace -p PIDltrace 跟踪的是动态库函数调用。排查锁问题、内存分配问题时它的信息量比 strace 直接很多。gdb 则可以进一步深入。catch syscall可以让你在某次指定系统调用发生时断下来然后查看完整的用户态调用栈搞清楚是代码里哪一行触发了这次调用。不过 gdb 会真正停住进程不适合直接挂在线上高并发服务上更适合线下复现问题后精细调试。5.2 perf trace 和 bpftrace低开销的现代方案straces 最大的问题就是开销高。如果你很在意性能影响或者问题发生在高频系统调用场景可以换成基于采样的perf traceperf trace -p PID它的输出格式和 strace 类似但基于 perf_event 采样机制不会逐个拦截系统调用开销小很多适合长时间观察。更进一步bpftrace 可以针对内核 tracepoint 做定制统计。比如统计进程打开了哪些文件bpftrace -e tracepoint:syscalls:sys_enter_openat { printf(%s\n, str(args-filename)); }bpftrace 写起来比 strace 复杂但它能做聚合统计、分布直方图而且不会像 strace 那样显著影响业务是生产环境长时间观测的进阶选择。5.3 把它当成“排查链路”的一环strace 不是万能的它只是链路中的一环。我的实际使用感受是问题排查需要把多个工具串起来而不是寄希望于一个工具解决所有问题。比如 CPU 占用高应该先看 perf top 找热点函数而不是 strace磁盘 IO 高先看 iostat 确认是读还是写再用strace -c确认是哪个进程哪种调用模式网络连接异常先看 tcpdump 确认报文有没有到本机再用 strace 看进程视角的连接状态。strace 的强项是告诉你“进程在哪个系统调用上花了时间、返回了什么错误”但需要进一步定位代码位置时要配合 gdb 看用户态栈需要确认报文是否到达时要配合 tcpdump。工具之间互相印证结论才站得住脚。我现在工作里的使用习惯是strace 一般不是第一个排查工具而是做“确认”的工具。先把监控、日志、perf、iostat 过一遍确定怀疑到某个进程的某个 IO再短时间小范围 strace 一把。抓的时候尽量用-o输出到文件带上-tt -T并且记录业务日志时间点用于对齐。最后分享一个小技巧抓 trace 的过程中在业务侧主动触发一次可复现的请求然后以请求时间点为中心看 strace 前后 2 秒的调用序列比漫无目的翻全部输出要高效得多。这个方法帮我快速定位过好几次偶发超时问题。还有一个小建议如果你怀疑 DNS 或者配置文件读取有关的问题优先用-e traceopenat,connect,sendto,recvfrom -Z先把失败路径列出来再深挖。用完记得停线上稳定比排查本身更重要。