Nginx在运行时与其说是一个程序,不如说是一连串系统调用的组合。每接收一个连接、读取一个静态文件、写回一段响应,背后都是内核提供的系统调用在工作。当Nginx出现奇怪的表现,比如访问某个文件返回404但文件明明存在、请求卡住不动、或者怀疑某个配置没有生效,单看access log和error log有时并不能给出答案。这个时候strace就能派上用场,它把Nginx与内核之间的每一次对话原原本本地展示出来,让问题无处藏身。

strace的工作原理与常用参数
strace基于Linux的ptrace机制实现。被跟踪的进程在每次陷入内核执行系统调用时,内核会先通知strace,strace有机会读取和修改调用的参数与返回值,然后再让目标进程继续执行。所以strace看到的调用信息是完整的、实时的,代价是被跟踪进程的执行速度会明显变慢,这一点在排查生产问题时必须心里有数。
几个最常用的参数需要先掌握。-p用于附加到一个已经存在的进程,这是跟踪Nginx worker最常用的方式;-f表示跟踪目标进程fork出来的子进程;-e trace=xxx用来过滤只看某一类调用,例如-e trace=file只看文件相关调用,-e trace=network只看网络相关调用;-t和-tt在每行输出前加上时间戳,后者精确到微秒;-s控制字符串参数的显示长度,默认只显示32字节,抓包分析HTTP内容时通常要加大到-s 1024甚至更大;-o把输出写入文件而不是刷到终端。
另外-c参数很适合做性能分析,它统计一段时间内各种系统调用的次数、耗时和错误数,最后输出汇总报表。用它先看看Nginx进程在忙什么,再决定要不要展开详细的调用跟踪,是一个很实用的工作流程。
定位Nginx进程并附加跟踪
Nginx采用一个master进程加多个worker进程的架构,真正处理请求的是worker。跟踪的第一步就是找到worker的进程号:
ps -ef | grep nginx # 输出类似: # root 1234 1 0 10:00 ? 00:00:00 nginx: master process /usr/sbin/nginx # www-data 1235 1234 0 10:00 ? 00:00:00 nginx: worker process # www-data 1236 1234 0 10:00 ? 00:00:00 nginx: worker process
找到worker的PID之后,用strace -p 1235 -tt -s 1024就能附加上去。此时向Nginx发起一个请求,终端上会立刻滚动出一系列调用:先是accept4或accept接收新连接,接着epoll_wait等待可读事件,然后recvfrom或read读取请求数据,Nginx解析请求后可能调用openat、fstat、writev读取并返回静态文件,最后close或shutdown结束连接。看懂了这条链路,Nginx处理请求的全过程就在眼前铺开了。
如果不想附加已运行的进程,也可以直接用strace启动Nginx,例如strace -f -o /tmp/nginx.trace nginx,-f会跟上master派生出的所有worker。这种方式适合分析Nginx启动阶段的配置加载、模块初始化、绑定端口等行为,比如排查80端口被占用、配置文件读取顺序之类的问题。需要注意的是用strace启动的Nginx属于前台进程,调试完记得正确停止再正常启动服务。
还有一种情况是想跟踪某一个还没启动的worker。Nginx的worker数量由worker_processes决定,把配置改成1个worker,跟踪难度会大幅降低,这在测试环境里是最省事的办法。
分析典型场景的调用序列
排查404问题时,文件相关过滤非常好用。假设访问/static/app.js返回404,但磁盘上文件确实存在,可以执行:
strace -p 1235 -e trace=file -tt # 然后发起请求,观察输出: # 10:32:01.123456 openat(AT_FDCWD, "/var/www/html/static/app.js", # O_RDONLY|O_NONBLOCK) = -1 ENOENT (No such file or directory)
这一行输出直接暴露了Nginx实际尝试打开的真实路径。很多时候404的原因是root指令配错了location,或者alias的路径拼接规则理解有偏差,导致实际查找路径和预期不符。strace显示的路径是唯一可信的证据,比反复猜测配置怎么生效要高效得多。
排查请求卡住的问题则要看调用在哪儿停住了。附加strace后发起一个会卡住的请求,如果最后一行停在epoll_wait长时间不动,说明Nginx在等客户端数据,问题可能在客户端或网络;如果停在connect上,多半是Nginx作为反向代理去连上游服务,上游端口不通或者防火墙拦截;如果停在read某个socket上,配合ss -antp确认对端状态,基本就能锁定是哪一环在等待。-tt的微秒时间戳在这里非常关键,通过相邻两行的时间差可以直接算出每一步消耗的时间。
分析慢请求还可以借助-c。例如统计30秒内worker的调用开销:
strace -p 1235 -c -f -o /tmp/summary.txt # 运行30秒后Ctrl+C,查看 /tmp/summary.txt # % time seconds usecs/call calls errors syscall # ------ ------- ---------- ----- ------ ------ # 45.2 0.120000 1200 100 2 accept4 # 30.1 0.080000 80 1000 read
如果发现writev次数异常多,可能是响应被拆成了大量小块,值得检查缓冲区相关配置;如果openat出现大量ENOENT错误,说明存在大量无效文件请求,应该从应用层修复。这种统计视角能快速指明优化方向。
多进程跟踪技巧与注意事项
Nginx默认有多个worker,请求会被随机分配,附加单个worker可能半天抓不到目标请求。几个办法可以应对:一是用strace -ff -o /tmp/trace同时附加所有worker,-ff会为每个线程或进程生成独立的编号文件,事后在多个文件里grep关键字,比如grep请求的URL片段或目标文件名;二是临时把worker数量降到1,让所有请求集中到一个进程;三是利用SO_REUSEPORT的特性,某些场景下可以让特定worker承担流量。
使用strace有几个必须注意的点。首先是性能影响,ptrace会让每次系统调用都多出两次上下文切换,被跟踪的Nginx吞吐量可能下降数倍,绝不要在高负载生产机器上长时间附加,最好在测试环境复现问题,或者选择低峰期短时间抓取。其次是权限问题,strace附加进程需要root权限或者相同的用户身份,用普通用户附加master进程会直接报Operation not permitted。再次是strace本身不能跟踪已经处于跟踪状态的进程,重复附加会失败。
最后提醒一点,抓到的输出要保存下来慢慢分析,加上-o参数写文件,配合grep -E 'open|connect|errno'之类的过滤,效率远高于对着滚屏硬看。strace配合tcpdump、lsof、ss这些工具形成组合拳,从系统调用、网络包、文件描述符三个维度交叉验证,绝大多数Nginx疑难问题都能被定位到具体环节。掌握strace之后,你对Nginx的理解会从配置文件的层面下沉到内核交互的层面,这种底层视角对排查任何服务端问题都大有裨益。