本质是同步阻塞与连接长期占用导致事件循环卡死;需启用aio threads和directio 4m绕过page cache异步读,配合limit_rate限速及tcp_nopush优化sendfile传输。

大文件下载独占 Nginx Worker 进程,本质是同步阻塞 + 连接长期占用,导致事件循环卡死、后续请求无法及时 accept 或处理,进而引发上游(如负载均衡、K8s Service、客户端)超时重传甚至丢包。这不是网络层“真丢包”,而是服务端响应能力坍塌引发的链式劣化。
只开 sendfile on 不够,必须配合异步读与连接管控
sendfile 本身不解决磁盘未缓存时的阻塞问题。当文件不在 page cache 中,Nginx 主线程会同步等待磁盘读取完成,Worker 就被锁死。关键要绕过内核页缓存,用线程池异步加载:
- 在对应 location 块中启用 aio threads; 和 directio 4m;(仅对 ≥4MB 文件生效,避免小文件额外开销)
- 确保文件系统支持(XFS/ext4),且内核开启
aio-nr足够(sysctl -w fs.aio-max-nr=524288) - 关闭 gzip、sub_filter、add_header 等与 sendfile 冲突的功能,否则 Nginx 自动降级为 read+write 模式
限制单连接吞吐,防止单个下载吃尽资源
不限速的大文件下载会持续占用一个连接数小时,同时压满带宽和磁盘 IO,挤占其他请求的调度窗口。这不是并发高,而是“长连接低频但高吞吐”带来的隐性饥饿:
- 使用 limit_rate 2m;(单位:bytes/second,2m = 2MB/s)控制单连接速率,让出 CPU 和磁盘调度权
- 搭配 limit_rate_after 100m; 实现“前100MB不限速,之后限速”,兼顾首屏体验与公平性
- 若用的是 OpenResty,可用 lua-resty-limit-traffic 做更细粒度的令牌桶限速
精准启用 sendfile 并提升 TCP 效率
sendfile 只在特定场景下真正生效。盲目全局开启反而掩盖配置冲突,还可能因 tcp_nopush 缺失导致大量小包:
- 仅在静态大文件路径启用:location ~ \.(mp4|zip|iso|tar\.gz)$ { sendfile on; tcp_nopush on; }
- tcp_nopush 必须与 sendfile 同时开启才起作用,它把多个 sendfile 数据攒成一个 TCP 包发出,减少协议栈开销
- 避免在该 location 中使用 proxy_pass、rewrite 或任何需要修改响应体的操作,否则 sendfile 自动失效
验证是否真正缓解,别信配置,要看运行态
改完配置不等于问题消失。重点观察三个指标:
- 用 strace -e trace=sendfile64 -p $(pgrep -f "nginx: worker") 确认实际调用的是 sendfile64 系统调用,而非 read/write
- 压测时盯住 nginx_stub_status 的 Writing 值:稳定在个位数(如 2~5),说明 Worker 没被卡住;若持续 >20 且缓慢上涨,说明仍存在阻塞
- 用 iostat -x 1 观察 await 和 %util:await 从 50ms+ 降到 5ms 以内、%util 稳定在 70% 以下,说明磁盘压力已释放










