sendfile与tcp_nopush如何协同实现超大文件分发的零拷贝技术?
- 内容介绍
- 相关推荐
本文共计1010个文字,预计阅读时间需要5分钟。
使用`sendfile`和`tcp_nopush`功能时,必须同时启用,并且仅在满足以下三个硬件条件时才能生效:
为什么单独开 sendfile 或 tcp_nopush 都没用
tcp_nopush 不是“延迟发送开关”,它只是告诉内核:等响应头写完、且后续数据(比如文件开头)能凑满一个 TCP 段(MSS ≈ 1448 字节)时,再一起发出去。这个“后续数据”必须来自 sendfile 路径——即内核直接从磁盘页缓存搬进 socket 发送队列,不经过用户态缓冲区。
若 sendfile off,Nginx 先 read() 到用户空间 buffer,再 write() 到 socket,此时 tcp_nopush 完全被忽略,Nginx 日志里会出现 tcp_nopush is ignored 警告。
常见误判点:
- 看到配置里写了
tcp_nopush on就以为生效了 - 在 proxy_pass 场景下全局开了
sendfile on,但后端返回的是 chunked 编码或没带Content-Length - 启用了
gzip on,哪怕只压缩 JS/CSS,也会导致整个请求链路禁用 sendfile
必须满足的三个运行时前提
配置写了不算数,Nginx 在每次响应时都会动态判断是否走零拷贝路径。以下三点缺一不可:
-
sendfile on已在当前 location 块中显式启用(继承自 server/http 块可能被覆盖) - 请求命中的是真实磁盘文件:例如
root /data/assets+location ~ \.zip$,且文件存在、可读、未被 SELinux 拦截 - 响应头中必须含
Content-Length:Nginx 需提前知道文件大小,才能调用sendfile()系统调用;若后端动态生成或流式响应无长度头,自动 fallback 到read()/write()
注意:etag、expires、add_header 这类纯 header 操作不影响 sendfile;但 sub_filter、access_by_lua_block、proxy_buffering on 会强制退出零拷贝路径。
tcp_nodelay off 是 tcp_nopush 生效的关键配合项
Linux 下 tcp_nopush on 实际启用的是 TCP_CORK,它的行为是“攒包”,不是“延迟”。而 tcp_nodelay on 会强制立即发包(禁用 Nagle),二者逻辑冲突。
所以必须确保:
-
tcp_nodelay off(默认值就是 off,但建议显式写出,避免被上级块继承on) - 不要在同一个 location 中混用
tcp_nodelay on和tcp_nopush on,后者会被前者直接废掉 - 对视频首帧加载特别敏感的场景(如 HLS 的第一个 ts),可考虑把 m3u8 单独剥离到另一个不启用
tcp_nopush的 location,避免因凑包带来毫秒级首包延迟
如何验证是否真正在用零拷贝
别信配置,看系统调用:
- 用
strace -p $(pgrep nginx) -e trace=sendfile,read,write抓 worker 进程,下载一个 mp4 文件,观察是否出现sendfile(调用;若全是read(和write(</code),说明被 gzip、chunked 或权限问题拦住了</li> <li>用 <code>ss -i 查看连接的
cwnd和retrans,零拷贝+tcp_nopush 后小包数量应明显下降,重传率趋近于 0 - 对比开启前后 top 中 nginx worker 的 %CPU:大文件高并发下,CPU 使用率通常下降 30%–50%
最容易被忽略的一点:即使所有配置都对,若文件系统挂载时用了 noatime 以外的选项(比如 relatime 或未禁用 barrier),也可能让 sendfile 的实际性能打折扣——这不是 Nginx 层能控制的,得查 /proc/mounts 和内核日志。
本文共计1010个文字,预计阅读时间需要5分钟。
使用`sendfile`和`tcp_nopush`功能时,必须同时启用,并且仅在满足以下三个硬件条件时才能生效:
为什么单独开 sendfile 或 tcp_nopush 都没用
tcp_nopush 不是“延迟发送开关”,它只是告诉内核:等响应头写完、且后续数据(比如文件开头)能凑满一个 TCP 段(MSS ≈ 1448 字节)时,再一起发出去。这个“后续数据”必须来自 sendfile 路径——即内核直接从磁盘页缓存搬进 socket 发送队列,不经过用户态缓冲区。
若 sendfile off,Nginx 先 read() 到用户空间 buffer,再 write() 到 socket,此时 tcp_nopush 完全被忽略,Nginx 日志里会出现 tcp_nopush is ignored 警告。
常见误判点:
- 看到配置里写了
tcp_nopush on就以为生效了 - 在 proxy_pass 场景下全局开了
sendfile on,但后端返回的是 chunked 编码或没带Content-Length - 启用了
gzip on,哪怕只压缩 JS/CSS,也会导致整个请求链路禁用 sendfile
必须满足的三个运行时前提
配置写了不算数,Nginx 在每次响应时都会动态判断是否走零拷贝路径。以下三点缺一不可:
-
sendfile on已在当前 location 块中显式启用(继承自 server/http 块可能被覆盖) - 请求命中的是真实磁盘文件:例如
root /data/assets+location ~ \.zip$,且文件存在、可读、未被 SELinux 拦截 - 响应头中必须含
Content-Length:Nginx 需提前知道文件大小,才能调用sendfile()系统调用;若后端动态生成或流式响应无长度头,自动 fallback 到read()/write()
注意:etag、expires、add_header 这类纯 header 操作不影响 sendfile;但 sub_filter、access_by_lua_block、proxy_buffering on 会强制退出零拷贝路径。
tcp_nodelay off 是 tcp_nopush 生效的关键配合项
Linux 下 tcp_nopush on 实际启用的是 TCP_CORK,它的行为是“攒包”,不是“延迟”。而 tcp_nodelay on 会强制立即发包(禁用 Nagle),二者逻辑冲突。
所以必须确保:
-
tcp_nodelay off(默认值就是 off,但建议显式写出,避免被上级块继承on) - 不要在同一个 location 中混用
tcp_nodelay on和tcp_nopush on,后者会被前者直接废掉 - 对视频首帧加载特别敏感的场景(如 HLS 的第一个 ts),可考虑把 m3u8 单独剥离到另一个不启用
tcp_nopush的 location,避免因凑包带来毫秒级首包延迟
如何验证是否真正在用零拷贝
别信配置,看系统调用:
- 用
strace -p $(pgrep nginx) -e trace=sendfile,read,write抓 worker 进程,下载一个 mp4 文件,观察是否出现sendfile(调用;若全是read(和write(</code),说明被 gzip、chunked 或权限问题拦住了</li> <li>用 <code>ss -i 查看连接的
cwnd和retrans,零拷贝+tcp_nopush 后小包数量应明显下降,重传率趋近于 0 - 对比开启前后 top 中 nginx worker 的 %CPU:大文件高并发下,CPU 使用率通常下降 30%–50%
最容易被忽略的一点:即使所有配置都对,若文件系统挂载时用了 noatime 以外的选项(比如 relatime 或未禁用 barrier),也可能让 sendfile 的实际性能打折扣——这不是 Nginx 层能控制的,得查 /proc/mounts 和内核日志。

