sendfile 提升大文件效率的核心是打通“磁盘→页缓存→socket发送队列”内核直通链路,必须同时满足:location 显式启用 sendfile on、响应真实本地磁盘文件、响应头含准确 content-length,缺一不可。

sendfile 能提升大文件处理效率,核心不在“开开关”,而在于让数据全程留在内核态完成搬运——跳过用户空间、减少拷贝次数、压低上下文切换开销。真正起效必须满足路径可控、条件齐备、干扰清零三个关键逻辑。
零拷贝路径必须完整闭环
sendfile 不是加速“读”或“写”,而是打通“磁盘→页缓存→socket发送队列”的内核直通链路。这个链路只有在以下三者同时成立时才被激活:
- 当前 location 显式启用 sendfile on(不能只靠 http 块继承)
- 响应的是真实磁盘上的常规文件(ext4/xfs 等本地文件系统,非 NFS、FUSE、容器卷等)
- 响应头中含准确的 Content-Length(Nginx 需提前知道大小才能调用 sendfile();若为 chunked 或流式响应,则自动退化)
tcp_nopush 必须依附于 sendfile 路径
tcp_nopush 的作用不是“提速”,而是“合包”:把 HTTP 响应头和文件开头数据合并进同一个 TCP 报文段发出。但这仅在 sendfile 路径下可行——因为此时内核能将响应头与文件数据视为逻辑连续块;若走 read/write 模式,数据已分段进入 socket 队列,tcp_nopush 就失去意义,Nginx 日志会提示 “tcp_nopush is ignored”。
- 必须搭配使用:sendfile on + tcp_nopush on
- 必须关闭 Nagle 干扰:tcp_nodelay off(二者不可同时为 on)
- 配置位置要精准:建议在匹配大文件的 location 块中显式声明,避免被更高层级配置覆盖或继承干扰
所有用户态干预都会强制中断零拷贝
只要响应体在发出前被 Nginx 或第三方模块触碰过,内核就无法接管整段传输。这类操作包括但不限于:
- 启用 gzip on 或 gunzip on(压缩/解压必须进用户态)
- 使用 sub_filter、add_before_body、access_by_lua_block 等内容改写或注入指令
- 开启 proxy_buffering on 或后端返回无 Content-Length 的动态响应
- 客户端发起 Range 请求且未适配(虽部分内核支持 sendfile + range,但需确认版本与配置)
验证是否真走零拷贝,不能只看配置
配置正确 ≠ 运行生效。必须通过运行时行为确认:
- 用 strace -p $(pgrep nginx) -e trace=sendfile64 观察 worker 进程是否持续调用 sendfile64 系统调用
- 对比开启前后 CPU 的 sy(系统态)占比:真实生效时应明显下降,QPS 提升通常达 20%–50%
- 检查错误日志是否有 sendfile() failed 或权限类报错(如 SELinux 限制、文件不可读)











