nginx worker进程“阻塞”实为事件循环被拖住,本质是单线程模型下cpu密集型脚本、同步i/o、ssl握手开销、锁竞争或上游等待导致响应迟缓,需通过stub_status、strace/gdb及openresty协程调度分析区分真伪阻塞并定位具体阶段。

排查 Nginx 工作线程在特定阶段的阻塞问题,关键在于区分“真阻塞”与“伪阻塞”,并结合进程模型定位瓶颈所在。Nginx 本身是多 worker 进程、单线程事件驱动模型,不存在传统意义的“线程阻塞”,所谓“阻塞”通常表现为某个 worker 进程响应迟缓、连接堆积或请求长时间挂起,本质是事件循环被拖住——可能源于 CPU 密集型 Lua 脚本、同步 I/O、SSL 握手开销、第三方模块锁竞争,或上游等待超时。
确认是否真为 worker 阻塞,而非资源或配置问题
先排除常见误判场景:
- 检查 Active connections 和 Writing/Waiting/Reading 比例(通过
http_stub_status)。若 Writing 持续高位,说明响应体发送卡住,常见于客户端网络慢、带宽打满或后端返回巨量数据未分块;若 Reading 异常高,可能是客户端发包慢或恶意慢速攻击 - 运行
nginx -t验证配置语法,尤其注意worker_processes是否超过 CPU 核数、worker_connections是否被突破、worker_rlimit_nofile是否足够 - 用
netstat -antp | grep :80 | wc -l对比worker_connections × worker_processes,确认是否连接数已达硬上限导致新请求被丢弃(此时 error.log 会出现accept() failed (24: Too many open files))
定位具体阻塞阶段:从请求生命周期切入
Nginx 请求处理分多个阶段(post-read → server-recheck → find-config → rewrite → post-rewrite → preaccess → access → post-access → try-files → content → log)。阻塞常发生在以下环节:
-
SSL 握手阶段:TLS 版本协商、证书验证、密钥交换耗时高。可通过
openssl s_client -connect your.site:443 -tls1_2测试握手延迟;启用ssl_buffer_size或 KTLS(内核 TLS 卸载)缓解 -
rewrite 或 access 阶段:正则匹配复杂(如未锚定的
location ~ \.php$)、Lua 脚本执行过长(ngx.sleep(0)不让出控制权)、或调用ngx.location.capture同步阻塞 upstream -
content 阶段:使用
proxy_pass但proxy_read_timeout过短,或上游服务响应慢;OpenResty 中滥用io.open()同步读文件、未用thread.pool处理磁盘 I/O -
log 阶段:日志写入磁盘慢(尤其开启
buffered仍不足),或使用log_format中含大量变量计算
用 strace + gdb 抓取实时阻塞点
当某 worker 进程明显卡顿(如 ps aux | grep nginx 显示 CPU 为 0%,但连接不释放),直接诊断:
- 用
strace -p $(pgrep nginx | tail -n1) -e trace=epoll_wait,read,write,futex -f -s 256观察其是否长期停在epoll_wait(正常空闲)、反复read却无返回(上游未发完)、或卡在futex(死锁信号) - 若发现
futex(..., FUTEX_WAIT_PRIVATE, ...)长时间不动,记下锁地址(如0x7f234053902c),再用gdb -p $(pgrep nginx | tail -n1)进入,执行:thread apply all bt—— 查看哪个线程停在pthread_mutex_lock;p *(pthread_mutex_t*)0x7f234053902c—— 看__owner字段对应线程 ID;thread <id></id>+bt—— 追踪持锁线程是否也在等另一把锁,形成闭环
针对 OpenResty 场景:协程调度与 fake timeout
OpenResty 的 Lua 协程虽可让出控制权,但一旦执行 CPU 密集操作(如 base64 编解码、JSON 解析大 payload、正则暴力匹配),就会阻塞整个 worker 事件循环:
- 启用
lua_check_client_abort off避免因客户端断连触发额外检查开销 - 将重计算逻辑拆分为小块,中间插入
ngx.sleep(0)让出调度权 - 对文件读写、DNS 查询等 I/O,务必使用
cosocket或resty.http,禁用io.*同步 API - 用
lua_code_cache off(仅开发环境)配合curl -I触发单次请求,观察error.log中是否出现lua tcp socket read timeout或timeout while connecting to upstream,这往往是协程饥饿的间接证据











