ngx_http_copy_filter_module负责准备响应体buf链表并交由下游处理,不修改内容、不执行send/write;对内存buf直接引用,文件buf按条件启用sendfile零拷贝,special buf透传,小buf可合并。

nginx 的 ngx_http_copy_filter_module 是 HTTP 处理链中负责“数据拷贝”的关键一环,它不修改内容,只把上游模块(如 upstream、静态文件读取)产生的响应体数据,按需复制到下游(客户端 socket 缓冲区或下一个 filter)。理解它的逻辑,对排查响应延迟、内存占用异常、零拷贝失效等问题很实用。
核心职责:把 buf 链表“准备好”,交给 downstream
这个模块本身不执行真正的 send() 或 write(),也不做编码转换。它的作用是:
- 遍历上游传来的
ngx_chain_t *链表,检查每个ngx_buf_t的类型(内存 buf、文件 buf、special buf) - 对内存 buf:直接保留引用,不做 memcpy
- 对文件 buf(
buf->in_file == 1):若开启sendfile且满足条件(如未启用 gzip、未开启 sub_filter、文件未被截断等),就标记为sendfile模式,后续由ngx_http_write_filter调用sendfile()系统调用 - 对 special buf(如
flush、last_buf、sync):原样透传,确保控制语义不丢失 - 合并小的内存 buf(当配置
output_buffers且有空闲 buffer 时),减少系统调用次数
关键判断逻辑:什么情况下走 sendfile?
是否启用内核零拷贝,取决于多个条件同时满足:
-
ngx_conf_t.conf.sendfile == 1(主配置开启) -
r->connection->tcp_nopush == 0(未强制禁用 nopush,避免干扰 sendfile) -
buf->in_file == 1 && buf->file_last - buf->file_pos > 0(确实是有效文件段) - 当前请求未启用任何会破坏原始字节流的 filter:比如
gzip、sub_filter、charset、slice等,这些 filter 会在copy_filter前注册,一旦存在,copy_filter就会把文件 buf 强制读入内存(调用ngx_read_file),转成内存 buf - 连接处于可写状态,且没有正在等待的 pending 数据
只要其中任一条件不满足,文件内容就会被预读进内存,再由 writev() 发送 —— 这就是“零拷贝失效”的常见原因。
内存管理要点:buffer 复用与链表清理
copy_filter 不分配新 buffer,但参与 buffer 生命周期管理:
- 它会尝试复用
r->out中已有的空闲 buffer(来自output_buffers配置),用于合并小 buf - 处理完一个 chain 后,会调用
ngx_free_chain(c->pool, in)释放输入链表节点(但不释放 buf 内存,因为可能是上游长期持有的) - 真正释放 buf 内存,是在
ngx_http_write_filter完成发送后,通过ngx_pfree或 pool cleanup 回收
因此,如果看到大量 ngx_chain_t 对象堆积,往往不是 copy_filter 的问题,而是下游写不出去(如客户端接收窗口满、网络卡顿),导致 chain 一直挂在 r->out 上无法释放。
调试建议:如何验证 copy_filter 行为
在开发或排障时,可通过以下方式确认实际路径:
- 开启
error_log debug,搜索"http copy filter"、"sendfile: "、"not sendfile"等日志关键词 - 用
strace -e trace=sendfile,write,writev -p $(pidof nginx)观察系统调用类型 - 检查请求响应头:
Content-Length是否与文件大小一致、Content-Range是否出现(影响 sendfile)、是否有Vary: Accept-Encoding(暗示可能触发 gzip) - 临时关闭疑似干扰模块(如注释掉
gzip on),对比 sendfile 是否恢复
它不复杂,但容易忽略前置条件是否闭合。











