sendfile 与 tcp_nopush 必须成对启用且满足运行时条件(如本地静态文件、content-length 存在、gzip off)才能协同实现内核零拷贝与 tcp 包合并,单独开启任一指令均无效。

启用 sendfile 并配合 tcp_nopush,确实能让 Nginx 在传输大文件(如视频、安装包、静态资源)时绕过用户态内存拷贝,实现内核态零拷贝(zero-copy),显著降低 CPU 和内存带宽开销。但“开启即生效”是常见误解——实际效果高度依赖配置协同、内核版本、TCP 栈行为及文件访问模式。
sendfile 是什么,为什么它能零拷贝?
sendfile() 是 Linux 内核提供的系统调用,允许数据直接在两个文件描述符间传输(如磁盘文件 fd → socket fd),全程不经过用户空间内存。Nginx 启用 sendfile on; 后,对于满足条件的静态文件响应(GET、无 body、无 filter、非 range 请求等),会跳过 read() + write() 的两段拷贝,由内核直接 DMA 从磁盘读入 socket 发送缓冲区。
注意:真正零拷贝需同时满足:
• 文件未被 mmap 或其他进程锁住
• 文件大小不超过 sendfile_max_chunk(默认 0,即不限;若设为非零值,超长文件会退化为普通 read/write)
• 使用的是支持 splice 的内核(2.6.33+ 更稳定,推荐 3.10+)
tcp_nopush 必须和 sendfile 配合使用
tcp_nopush on; 对应 TCP_CORK 行为(不是 TCP_NODELAY)。它的作用不是“禁用 Nagle 算法”,而是**延迟发送,直到一个完整的 TCP 报文段填满或连接关闭**。这与 sendfile 天然互补:
- 单独用
sendfile可能产生大量小报文(尤其当文件块被分片读取时),触发 Nagle 算法合并,但仍有额外延迟 - 启用
tcp_nopush后,Nginx 会在调用sendfile()前设置 TCP_CORK,确保内核把多个sendfile片段攒成一个满载 MSS 的报文再发,避免 IP 分片和 ACK 洪水 - 连接关闭前自动取消 cork,保证末尾数据不滞留
⚠️ 不要混淆:tcp_nodelay on;(禁用 Nagle)适用于小包低延迟场景(如 WebSocket、API),与大文件零拷贝目标相悖,二者互斥,不可共存。
关键配置项与安全边界
以下是最小可行且生产推荐的组合配置(放在 http 或 server 块中):
sendfile on; tcp_nopush on; tcp_nodelay off; # 显式关闭,避免隐式继承 sendfile_max_chunk 0; # 允许任意大小文件走 sendfile(默认即 0) aio threads; # 可选:配合 sendfile 提升大文件并发吞吐(需 kernel ≥ 4.18 + nginx ≥ 1.15.9) directio 4m; # 可选:对 ≥4MB 文件启用 O_DIRECT,绕过页缓存(减少内存压力,但随机读性能下降)
补充说明:
-
aio threads不是必须,但在高并发大文件场景下可缓解 sendfile 对磁盘 I/O 的阻塞等待 -
directio适合顺序读为主的冷文件服务;若文件常被重复访问,保留 page cache 反而更快 - 务必确认后端存储支持 direct I/O(如 XFS/ext4 OK,某些 NFSv3 不支持)
验证是否真正生效
光看配置不等于零拷贝成功。可通过以下方式实测确认:
- tcpdump + wireshark:抓包观察 TCP payload size 是否接近 MSS(如 1448 字节),且 ACK 间隔变长、小包消失
-
perf record:
perf record -e syscalls:sys_enter_sendfile -p $(pgrep nginx),确认系统调用频次与预期一致 -
/proc/net/snmp:监控
TcpOutSegs与TcpRetransSegs比率,零拷贝优化后重传率不应上升 -
ab / wrk 压测对比:固定 QPS 下,开启前后对比 CPU system time(
top中 %sy)下降 15%+ 通常表明有效
常见失效原因:启用了 gzip、sub_filter、add_header(触发 buffer copy)、请求含 Range、使用了 proxy_pass、文件被其他进程 mmap 锁定。











