sendfile 是 Nginx 静态资源服务高效运行的核心开关,启用后通过零拷贝实现磁盘到网卡的内核直传,仅需 2 次拷贝和上下文切换;若启用 gzip、Range 请求或远程文件系统等会导致退化为 4 次拷贝。

sendfile 指令不是“锦上添花”的可选项,而是决定 Nginx 静态资源服务是否真正高效的核心开关。它直接决定了数据从磁盘到网络的路径是走“零拷贝内核直通”,还是陷入用户态反复搬运——前者 CPU 占用低、延迟小、吞吐高;后者在高并发下极易成为瓶颈。
sendfile 怎么让传输变快?两次拷贝 vs 四次拷贝
不启用 sendfile 时,Nginx 必须用 read() + write() 组合完成文件发送:
- 磁盘 → 内核缓冲区(DMA)
- 内核缓冲区 → 用户空间缓冲区(CPU 拷贝)
- 用户空间缓冲区 → Socket 缓冲区(CPU 拷贝)
- Socket 缓冲区 → 网卡(DMA)
共 4 次数据拷贝 + 4 次上下文切换,CPU 被大量消耗在搬运上,而非处理请求。
启用 sendfile 后,调用内核原生 sendfile() 系统调用:
- 磁盘 → 内核缓冲区(DMA)
- 内核缓冲区 → Socket 缓冲区(内核态直传,无 CPU 拷贝)
- Socket 缓冲区 → 网卡(DMA)
仅需 2 次拷贝 + 2 次上下文切换。Linux 2.4+ 还支持 SG-DMA,可进一步跳过内核缓冲区到 Socket 的拷贝,实现更彻底的“零拷贝”。
为什么 sendfile 会失效?常见退化场景
即使配置了 sendfile on,Nginx 也可能自动回退到 read/write 模式,导致性能断崖式下降。典型原因包括:
- 启用了 gzip on(运行时压缩必须进用户态,破坏零拷贝路径)
- 使用了 etag 或 expires 指令(需动态计算响应头,触发用户态介入)
- 客户端发起 Range 请求(如视频拖拽),Nginx 需切片处理
- 静态文件存于 NFS/CIFS 等不支持 sendfile 的远程文件系统
- gzip_static 开启但对应 .gz 文件缺失(Nginx 会 fallback 到运行时压缩或原始文件读取)
验证是否真实生效:用 strace -p $(pgrep nginx) -e trace=sendfile 观察 worker 进程是否持续调用 sendfile 系统调用。
如何确保 sendfile 始终在线?关键协同配置
单开 sendfile on 不够,必须配合其他指令堵住退化漏洞:
- 用 gzip_static on 替代 gzip on:提前生成 .gz 文件(如 app.js.gz),Nginx 直接返回压缩体,仍走 sendfile 路径
- 显式关闭运行时压缩:gzip off,避免模块干扰
- 搭配 tcp_nopush on:等 sendfile 数据攒满 TCP 包再发,减少小包数量
- 启用 open_file_cache:缓存文件句柄和元数据,避免高并发下频繁 open/stat 触发用户态操作
- 静态资源 location 使用前缀匹配(location /assets/),禁用正则(location ~* \.css$),省去 PCRE 计算开销
配置位置与默认行为要清楚
sendfile 可在 http、server、location 块中配置,作用范围逐级覆盖。现代 Nginx(0.8.12+)默认值为 on,但部分精简版或容器镜像可能关闭。务必显式声明:
http {
sendfile on;
tcp_nopush on;
# 其他配套项...
}
修改后执行 nginx -t && nginx -s reload 生效。注意:反向代理场景下 sendfile 无效(两端都是 socket,无文件句柄可传),仅对真实静态文件服务起作用。










