sendfile 与 aio threads 是协作关系:sendfile 负责热数据零拷贝传输,aio threads 仅在 sendfile 失效时异步读冷数据;二者互斥,需按场景精确配置,错误共用反而降效。

sendfile 和 aio threads 不是并列选项,而是分工明确的协作关系:sendfile 负责“热数据零拷贝传输”,aio threads 负责“冷数据异步读盘”。用错场景或强行共存,反而会降效甚至失效。
sendfile 是大文件传输的默认主力
只要文件在内核页缓存(Page Cache)中,sendfile 就能直接让内核把数据从磁盘缓冲区复制到 socket 发送队列,全程不进用户态、不调 read()、不占 worker CPU。这是最轻量、最高吞吐的方式。
- 必须开启:sendfile on;
- 配合优化:tcp_nopush on;(攒满 TCP 包再发,减少协议栈开销)
- 防止单次卡死:sendfile_max_chunk 1m;(每次最多传 1MB,之后主动让出事件循环)
- 禁用干扰项:gzip off;、sub_filter off;(二者会强制走用户态读写,使 sendfile 失效)
aio threads 只在 sendfile 失效时补位
sendfile 在以下情况退化为普通 read():文件未被缓存(冷文件)、文件系统不支持 sendfile(极少见)、或你显式关掉了 sendfile。这时,aio threads 才真正起作用——它把阻塞的 read() 提交到独立线程池执行,worker 主线程继续处理其他请求。
- 三要素缺一不可:thread_pool io_pool threads=8 max_queue=16384;(定义池)+location /download/ { aio threads=io_pool; directio 4m; sendfile off; }(启用路径)
- directio 4m 是关键开关:仅当文件 ≥4MB 且以 O_DIRECT 方式打开时,才触发异步读;小文件仍走 page cache + sendfile,更高效
- 不要全局开 aio on;它只对 sendfile off 的路径生效,且与 sendfile 互斥——同时开只会让大文件走 sendfile,小文件走同步 read,aio threads 形同虚设
动静分离 + 精确作用域,避免误伤
别把 aio threads 配在根 location 或 upstream 代理块里。它只适合明确的大静态资源路径,比如 /static/assets/、/downloads/、/archive/。API 接口、动态内容、日志 tail 等场景完全不需要,甚至有害。
- 推荐写法:location ~ ^/downloads/.*\.(zip|tar|iso|img)$ {
alias /data/files/;
sendfile on;
sendfile_max_chunk 512k;
tcp_nopush on;
open_file_cache max=1000 inactive=60s;
# 不加 aio,靠 page cache + sendfile 覆盖 95% 场景
} - 真有大量冷文件(如新上传未缓存的 ISO):单独拆出一个 location,显式关 sendfile,开 aio threads + directio
- 线程池容量要留余量:若设 worker_processes 4 × worker_connections 8192 = 32768 并发连接,max_queue 至少设为 65536,否则请求在线程池排队,反而增加延迟
验证是否生效的简单方法
不用看日志或 perf,两个命令就能判断当前路径走的是哪条链路:
- 查系统调用:strace -p $(pgrep nginx) -e trace=openat,read,sendfile,io_submit 2>&1 | grep -E "(openat|read|sendfile|io_submit)" —— 如果看到大量 sendfile,说明走零拷贝;如果看到 io_submit,说明 aio threads 已介入
- 看响应头:curl -I http://example.com/large.zip —— 若返回 Content-Length 但无 Transfer-Encoding: chunked,基本确认是 sendfile 直传;若出现 chunked,大概率已 fallback 到用户态流式读取











