sendfile高效的根本原因是内核直通、零用户态拷贝,路径为磁盘→内核缓冲区→socket缓冲区→网卡,仅2次拷贝和2次上下文切换;需sendfile on、tcp_nopush on、tcp_nodelay off三者协同生效。

sendfile 不是靠“把文件读进内存”来提速,而是让内核直接在磁盘和网络之间搬运数据,跳过用户态,减少拷贝和上下文切换——这才是它高效的根本原因。
核心机制:内核直通,零用户态拷贝
传统 read()+write() 流程要经历:磁盘 → 内核缓冲区 → 用户内存 → socket 缓冲区 → 网卡,共 4 次拷贝 + 4 次上下文切换。而 sendfile 路径是:磁盘 → 内核缓冲区 → socket 缓冲区 → 网卡,全程在内核完成,仅需 2 次拷贝 + 2 次切换。Linux 2.4+ 还支持 SG-DMA,甚至能跳过内核缓冲区到 socket 的拷贝,实现更彻底的零拷贝。
- 依赖内核系统调用 sendfile64(),不是 Nginx 自实现,也不需要 mmap
- 数据不经过用户空间,CPU 不参与搬运,只负责调度,sy(系统态)CPU 占比明显下降
- 对大文件(≥4KB)、本地 ext4/xfs 文件效果最显著,小文件也受益于减少系统调用次数
必须配套的三项配置
单独开 sendfile on 不够,必须三者协同才能真正走零拷贝路径:
- sendfile on; —— 启用基础能力,允许内核直传
- tcp_nopush on; —— 让内核攒满 TCP 包再发,避免 sendfile 数据被拆成大量小包
- tcp_nodelay off; —— 关闭 Nagle 算法,否则会与 tcp_nopush 冲突,破坏批量传输优势
这三项建议统一写在 http 块顶层,确保所有静态 location 继承生效。
常见失效场景及规避方式
即使配置正确,Nginx 也会自动退化为 read/write 模式。以下情况会强制绕过 sendfile:
- 启用了
gzip on或gunzip on—— 压缩必须在用户态做,建议改用gzip_static on;配合预压缩文件(如style.css.gz) - 使用了
sub_filter、add_before_body等内容改写指令 —— 触发缓冲与重写逻辑 - 文件不在真实磁盘路径:alias 指向 NFS、FUSE、Docker overlayFS 或符号链接到容器卷时,部分实现不支持 sendfile 系统调用
- 客户端发起 Range 请求(如视频拖拽)或服务端动态计算 ETag/Expires —— 需切片或头干预,无法直传整文件
辅助优化提升整体吞吐
这些设置不改变 sendfile 路径,但能减少干扰、释放更多资源:
- sendfile_max_chunk 512k; —— 限制单次 sendfile 传输量,防止单一大文件阻塞事件循环,适合图片、短视频首段加载
- open_file_cache max=10000 inactive=60s; —— 缓存文件句柄与元数据,降低高并发下频繁 open()/stat() 开销
- access_log off; —— 在纯静态 location 中关闭日志,避免 I/O 成瓶颈;如需记录,应异步写入或单独归档
-
expires 7d; 或
add_header Cache-Control "public, max-age=604800";—— 减少重复请求,间接减轻 sendfile 压力











