引言
问题:Nginx 配置 client_body_temp_path 的文件是否会删除,什么时候删除?
先说结论:如果 client_body_in_file_only 没有配置,那么会在Request生命周期结束后删除,如果配置了,那么就不要关心这个配置了,看 client_body_in_file_only 策略。如果进程崩溃,那么可能会有残留。同样作为proxy时的临时文件存储 proxy_temp_path 也是一样的逻辑。
这个问题在网上找了一大堆但是没有有依据的回答,所以索性自己趴一下源码和验证一下。
1. 一些前置知识
1.1. client_body_temp_path
指定存储客户端请求体临时文件的目录路径。当请求体数据过大(超过 client_body_buffer_size)时,Nginx 会将数据写入该目录下的临时文件。
1.2. client_body_in_file_only
强制将客户端请求体 完全存储在临时文件中(即使请求体很小),而不是优先使用内存缓冲区。
csharp
client_body_in_file_only on | off | clean;
#off 默认值,不会写入
#on 所有请求都会写入文件,并且不会清理
#clean 所有请求都会写入文件,但会在请求生命周期结束后unlink+close
1.3. unlink
unlink 是 Linux 系统中的一个 系统调用(system call),用于从文件系统中删除一个文件的名称(即目录项)。它的核心作用是 解除文件路径与底层数据的关联,但实际删除文件数据的时机取决于文件的引用计数。以下是详细解析。
rm 命令和 Nginx delete 的实现都是 unlink。unlink 的重要特性之一是执行 unlink 后只要还持有句柄,那么依旧可以继续写入文件以及使用sendfile。只要仍有进程打开该文件(持有文件描述符),那么数据会保留到最后一个文件描述符关闭。
所以经常可以看到即使调用 unlink 删除路径,但是磁盘占用还没有下去,主要还是因为句柄还在,可以通过 lsof 查看句柄。
1.3.1. unlink 的逻辑
-
删除文件路径:移除文件在文件系统中的路径(例如 /path/to/file),使该路径不再指向实际数据。
-
减少引用计数:每个文件在文件系统中都有一个 inode 结构,记录文件的元数据和引用计数(硬链接数)。
a. 执行 unlink 后,文件的引用计数减 1。
b. 当引用计数降为 0:文件系统会释放数据占用的磁盘空间。
c. 引用计数仍大于 0:文件数据继续保留(例如有其他硬链接或进程正在使用该文件)。
1.3.2. 安全的创建临时文件
由于 unlink 的特性,因此以下方式可以安全的创建一个临时文件,及时进程在读写过程中崩溃这个临时文件也会被删除。
所以 Nginx 中即使你设置了定时任务删除 nginx 的缓存文件,其实也没关系,因为nginx会持有句柄,直到释放。
scss
int fd = open("tempfile", O_CREAT | O_RDWR | O_EXCL, 0600);
unlink("tempfile"); // 路径消失,但文件描述符有效
// 读写操作...
close(fd); // 数据释放
1.3.3. Nginx 删除逻辑
打开临时文件
//tf->persistent = r->request_body_in_persistent_file;
ngx_fd_t
ngx_open_tempfile(u_char *name, ngx_uint_t persistent, ngx_uint_t access)
{
ngx_fd_t fd;
fd = open((const char *) name, O_CREAT|O_EXCL|O_RDWR,
access ? access : 0600);
if (fd != -1 && !persistent) {
(void) unlink((const char *) name);
}
return fd;
}
关闭文件
void
ngx_pool_cleanup_file(void *data)
{
ngx_pool_cleanup_file_t *c = data;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
c->fd);
if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
ngx_close_file_n " "%s" failed", c->name);
}
}
删除文件
void
ngx_pool_delete_file(void *data)
{
ngx_pool_cleanup_file_t *c = data;
ngx_err_t err;
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
c->fd, c->name);
if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
err = ngx_errno;
if (err != NGX_ENOENT) {
ngx_log_error(NGX_LOG_CRIT, c->log, err,
ngx_delete_file_n " "%s" failed", c->name);
}
}
if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
ngx_close_file_n " "%s" failed", c->name);
}
}
#define ngx_delete_file(name) unlink((const char *) name)
2. 临时文件写入的源码实现
ini
static ngx_int_t
ngx_http_write_request_body(ngx_http_request_t *r)
{
ssize_t n;
ngx_chain_t *cl, *ln;
ngx_temp_file_t *tf;
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
rb = r->request_body;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http write client request body, bufs %p", rb->bufs);
if (rb->temp_file == NULL) {
tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
if (tf == NULL) {
return NGX_ERROR;
}
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
tf->file.fd = NGX_INVALID_FILE;
tf->file.log = r->connection->log;
tf->path = clcf->client_body_temp_path;
tf->pool = r->pool;
tf->warn = "a client request body is buffered to a temporary file";
tf->log_level = r->request_body_file_log_level;
tf->persistent = r->request_body_in_persistent_file;
/**
如果设置了 client_body_in_file_only,那么一定会记录报文到文件,如果是clean才会清理
所以 buffer 方式一定不是通过直接delete删除的
if (clcf->client_body_in_file_only) {
r->request_body_in_file_only = 1;
r->request_body_in_persistent_file = 1;
r->request_body_in_clean_file =
clcf->client_body_in_file_only == NGX_HTTP_REQUEST_BODY_FILE_CLEAN;
r->request_body_file_log_level = NGX_LOG_NOTICE;
} else {
r->request_body_file_log_level = NGX_LOG_WARN;
}
*/
tf->clean = r->request_body_in_clean_file;
if (r->request_body_file_group_access) {
tf->access = 0660;
}
rb->temp_file = tf;
if (rb->bufs == NULL) {
/* empty body with r->request_body_in_file_only */
//当请求体为空是会走这里,实际上也肯定是命中request_body_in_file_only了
if (ngx_create_temp_file(&tf->file, tf->path, tf->pool,
tf->persistent, tf->clean, tf->access)
!= NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}
}
if (rb->bufs == NULL) {
return NGX_OK;
}
//创建文件
n = ngx_write_chain_to_temp_file(rb->temp_file, rb->bufs);
/* TODO: n == 0 or not complete and level event */
if (n == NGX_ERROR) {
return NGX_ERROR;
}
rb->temp_file->offset += n;
/* mark all buffers as written */
for (cl = rb->bufs; cl; /* void */) {
cl->buf->pos = cl->buf->last;
ln = cl;
cl = cl->next;
ngx_free_chain(r->pool, ln);
}
rb->bufs = NULL;
return NGX_OK;
}
ssize_t
ngx_write_chain_to_temp_file(ngx_temp_file_t *tf, ngx_chain_t *chain)
{
ngx_int_t rc;
if (tf->file.fd == NGX_INVALID_FILE) {
//代码会走这里
rc = ngx_create_temp_file(&tf->file, tf->path, tf->pool,
tf->persistent, tf->clean, tf->access);
if (rc != NGX_OK) {
return rc;
}
if (tf->log_level) {
ngx_log_error(tf->log_level, tf->file.log, 0, "%s %V",
tf->warn, &tf->file.name);
}
}
#if (NGX_THREADS && NGX_HAVE_PWRITEV)
if (tf->thread_write) {
return ngx_thread_write_chain_to_file(&tf->file, chain, tf->offset,
tf->pool);
}
#endif
return ngx_write_chain_to_file(&tf->file, chain, tf->offset, tf->pool);
}
//创建临时文件
ngx_int_t
ngx_create_temp_file(ngx_file_t *file, ngx_path_t *path, ngx_pool_t *pool,
ngx_uint_t persistent, ngx_uint_t clean, ngx_uint_t access)
{
size_t levels;
u_char *p;
uint32_t n;
ngx_err_t err;
ngx_str_t name;
ngx_uint_t prefix;
ngx_pool_cleanup_t *cln;
ngx_pool_cleanup_file_t *clnf;
if (file->name.len) {
name = file->name;
levels = 0;
prefix = 1;
} else {
name = path->name;
levels = path->len;
prefix = 0;
}
file->name.len = name.len + 1 + levels + 10;
file->name.data = ngx_pnalloc(pool, file->name.len + 1);
if (file->name.data == NULL) {
return NGX_ERROR;
}
#if 0
for (i = 0; i < file->name.len; i++) {
file->name.data[i] = 'X';
}
#endif
p = ngx_cpymem(file->name.data, name.data, name.len);
if (prefix) {
*p = '.';
}
p += 1 + levels;
n = (uint32_t) ngx_next_temp_number(0);
cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
if (cln == NULL) {
return NGX_ERROR;
}
for ( ;; ) {
(void) ngx_sprintf(p, "%010uD%Z", n);
if (!prefix) {
ngx_create_hashed_filename(path, file->name.data, file->name.len);
}
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
"hashed path: %s", file->name.data);
//打开临时文件
file->fd = ngx_open_tempfile(file->name.data, persistent, access);
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
"temp fd:%d", file->fd);
if (file->fd != NGX_INVALID_FILE) {
//清理 hook,在请求 pool 释放时回调所有的ngx_pool_cleanup
cln->handler = clean ? ngx_pool_delete_file : ngx_pool_cleanup_file;
clnf = cln->data;
clnf->fd = file->fd;
clnf->name = file->name.data;
clnf->log = pool->log;
return NGX_OK;
}
err = ngx_errno;
if (err == NGX_EEXIST_FILE) {
n = (uint32_t) ngx_next_temp_number(1);
continue;
}
if ((path->level[0] == 0) || (err != NGX_ENOPATH)) {
ngx_log_error(NGX_LOG_CRIT, file->log, err,
ngx_open_tempfile_n " "%s" failed",
file->name.data);
return NGX_ERROR;
}
if (ngx_create_path(file, path) == NGX_ERROR) {
return NGX_ERROR;
}
}
}
//打开临时文件
ngx_fd_t
ngx_open_tempfile(u_char *name, ngx_uint_t persistent, ngx_uint_t access)
{
ngx_fd_t fd;
fd = open((const char *) name, O_CREAT|O_EXCL|O_RDWR,
access ? access : 0600);
//创建成功并且不需要持久化,那么就设置 unlink
//未设置client_body_in_file_only时,persistent = 0
if (fd != -1 && !persistent) {
(void) unlink((const char *) name);
}
return fd;
}
void
ngx_pool_delete_file(void *data)
{
ngx_pool_cleanup_file_t *c = data;
ngx_err_t err;
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
c->fd, c->name);
if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
err = ngx_errno;
if (err != NGX_ENOENT) {
ngx_log_error(NGX_LOG_CRIT, c->log, err,
ngx_delete_file_n " "%s" failed", c->name);
}
}
if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
ngx_close_file_n " "%s" failed", c->name);
}
}
void
ngx_pool_cleanup_file(void *data)
{
ngx_pool_cleanup_file_t *c = data;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
c->fd);
if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
ngx_close_file_n " "%s" failed", c->name);
}
}
3. client_body_temp_path 逻辑分析
3.1. nginx.conf 配置
ini
server {
listen 80;
server_name localhost;
sendfile on;
#charset koi8-r;
#access_log logs/host.access.log main;
#location / {
# root html;
# index index.html index.htm;
#}
#设置临时文件目录
client_body_temp_path data/client_body;
#设置buffer大小,超过则写入临时文件
client_body_buffer_size 1;
#不配置 client_body_in_file_only
#client_body_in_file_only on;
location /baidu {
proxy_pass http://baidu.com:80;
}
}
3.2. strace 跟踪
css
strace -o output.txt -T -tt -e trace=all -p 21663
css
23:20:16.261254 epoll_wait(8, [{EPOLLIN, {u32=3821928464, u64=140569511591952}}], 512, -1) = 1 <6.544655>
23:20:22.806143 accept4(6, {sa_family=AF_INET, sin_port=htons(56442), sin_addr=inet_addr("54.86.50.139")}, [112->16], SOCK_NONBLOCK) = 3 <0.000031>
23:20:22.806292 epoll_ctl(8, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=3821928912, u64=140569511592400}}) = 0 <0.000025>
23:20:22.806374 epoll_wait(8, [{EPOLLIN, {u32=3821928912, u64=140569511592400}}], 512, 60000) = 1 <0.000024>
23:20:22.806450 recvfrom(3, "POST /baidu HTTP/1.1\r\nConnection"..., 1024, 0, NULL, NULL) = 1024 <0.000026>
23:20:22.806549 ioctl(3, FIONREAD, [12127]) = 0 <0.000024>
23:20:22.806672 write(5, "2025/06/05 23:20:22 [error] 1731"..., 168) = 168 <0.000032>
23:20:22.806749 write(5, "2025/06/05 23:20:22 [error] 1731"..., 195) = 195 <0.000029>
23:20:22.806822 write(5, "2025/06/05 23:20:22 [error] 1731"..., 195) = 195 <0.000026>
23:20:22.806890 write(5, "2025/06/05 23:20:22 [error] 1731"..., 175) = 175 <0.000027>
23:20:22.806959 write(5, "2025/06/05 23:20:22 [error] 1731"..., 167) = 167 <0.000032>
23:20:22.807033 write(5, "2025/06/05 23:20:22 [error] 1731"..., 165) = 165 <0.000026>
23:20:22.807100 write(5, "2025/06/05 23:20:22 [error] 1731"..., 177) = 177 <0.000025>
23:20:22.807166 write(5, "2025/06/05 23:20:22 [error] 1731"..., 172) = 172 <0.000025>
23:20:22.807251 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000024>
//创建临时文件,句柄为 10
23:20:22.807328 open("/root/nginx/data/client_body/0000000001", O_RDWR|O_CREAT|O_EXCL, 0600) = 10 <0.000054>
//设置 unlink
23:20:22.807457 unlink("/root/nginx/data/client_body/0000000001") = 0 <0.000030>
//开始写入数据
23:20:22.807532 pwritev(10, [{iov_base="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., iov_len=732}, {iov_base="a", iov_len=1}], 2, 0) = 733 <0.000035>
23:20:22.807614 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000025>
23:20:22.807687 pwrite64(10, "a", 1, 733) = 1 <0.000026>
23:20:22.807757 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000024>
23:20:22.807825 pwrite64(10, "a", 1, 734) = 1 <0.000026>
23:20:22.807891 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000033>
23:20:22.807968 pwrite64(10, "a", 1, 735) = 1 <0.000031>
23:20:22.808040 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000024>
23:20:22.808108 pwrite64(10, "a", 1, 736) = 1 <0.000026>
23:20:22.808174 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000024>
23:20:22.808241 pwrite64(10, "a", 1, 737) = 1 <0.000026>
23:20:22.808308 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000024>
23:20:22.808375 pwrite64(10, "a", 1, 738) = 1 <0.000026>
23:20:22.808441 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000024>
23:20:22.808509 pwrite64(10, "a", 1, 739) = 1 <0.000026>
23:20:22.808575 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000024>
23:20:22.808643 pwrite64(10, "a", 1, 740) = 1 <0.000026>
23:20:22.808708 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000023>
23:20:22.808775 pwrite64(10, "a", 1, 741) = 1 <0.000026>
23:20:22.808841 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000024>
23:20:22.808908 pwrite64(10, "a", 1, 742) = 1 <0.000024>
23:20:22.808972 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000027>
23:20:22.809042 pwrite64(10, "a", 1, 743) = 1 <0.000023>
23:20:22.809104 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000021>
23:20:22.809166 pwrite64(10, "a", 1, 744) = 1 <0.000023>
23:20:22.809228 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000021>
。。。。。。。。
23:20:28.278535 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000020>
23:20:28.278595 pwrite64(10, "a", 1, 41967) = 1 <0.000023>
23:20:28.278655 recvfrom(3, "a", 1, 0, NULL, NULL) = 1 <0.000024>
23:20:28.278719 pwrite64(10, "a", 1, 41968) = 1 <0.000023>
23:20:28.278780 epoll_ctl(8, EPOLL_CTL_MOD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=3821928912, u64=140569511592400}}) = 0 <0.000022>
23:20:28.278892 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 11 <0.000040>
23:20:28.278978 ioctl(11, FIONBIO, [1]) = 0 <0.000024>
23:20:28.279052 epoll_ctl(8, EPOLL_CTL_ADD, 11, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=3821929136, u64=140569511592624}}) = 0 <0.000025>
//connect upstream 服务器
23:20:28.279123 connect(11, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("39.156.66.10")}, 16) = -1 EINPROGRESS (操作现在正在进行) <0.000050>
23:20:28.279366 epoll_wait(8, [{EPOLLOUT, {u32=3821928912, u64=140569511592400}}], 512, 60000) = 1 <0.000022>
23:20:28.279440 epoll_wait(8, [{EPOLLOUT, {u32=3821929136, u64=140569511592624}}], 512, 56141) = 1 <0.028348>
23:20:28.307855 getsockopt(11, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 <0.000025>
23:20:28.307964 writev(11, [{iov_base="POST /baidu HTTP/1.0\r\nHost: baid"..., iov_len=285}], 1) = 285 <0.000041>
//通过 sendfile 发送请求
23:20:28.308058 sendfile(11, 10, [0] => [27056], 41969) = 27056 <0.000062>
23:20:28.308169 sendfile(11, 10, [27056], 14913) = -1 EAGAIN (资源暂时不可用) <0.000026>
23:20:28.308252 epoll_wait(8, [{EPOLLOUT, {u32=3821929136, u64=140569511592624}}], 512, 60000) = 1 <0.033216>
23:20:28.341529 sendfile(11, 10, [27056] => [41969], 14913) = 14913 <0.000047>
23:20:28.341625 epoll_wait(8, [{EPOLLIN|EPOLLOUT|EPOLLRDHUP, {u32=3821929136, u64=140569511592624}}], 512, 60000) = 1 <0.061374>
23:20:28.403116 recvfrom(11, "HTTP/1.1 302 Found\r\nDate: Thu, 0"..., 4096, 0, NULL, NULL) = 478 <0.000028>
//关闭临时文件,句柄为 10
23:20:28.403277 close(10) = 0 <0.000026>
23:20:28.403344 readv(11, [{iov_base="", iov_len=3618}], 1) = 0 <0.000011>
23:20:28.403405 close(11) = 0 <0.000024>
23:20:28.403458 writev(3, [{iov_base="HTTP/1.1 302 Found\r\nServer: ngin"..., iov_len=274}, {iov_base="<!DOCTYPE HTML PUBLIC "-//IETF//"..., iov_len=210}], 2) = 484 <0.000017>
23:20:28.403525 shutdown(3, SHUT_WR) = 0 <0.000014>
23:20:28.403575 epoll_wait(8, [{EPOLLOUT, {u32=3821928912, u64=140569511592400}}], 512, 5000) = 1 <0.000008>
23:20:28.403614 epoll_wait(8, [{EPOLLIN|EPOLLOUT|EPOLLHUP|EPOLLRDHUP, {u32=3821928912, u64=140569511592400}}], 512, 5000) = 1 <0.229661>
23:20:28.633375 recvfrom(3, "", 4096, 0, NULL, NULL) = 0 <0.000026>
23:20:28.633498 write(4, "54.86.50.139 - - [05/Jun/2025:23"..., 105) = 105 <0.000033>
23:20:28.633642 close(3) = 0 <0.000046>
23:20:28.633741 epoll_wait(8, <detached ...>
从上面日志可以总结出:
- 现象 :文件路径
/root/nginx/data/client_body/0000000001
被删除,但文件描述符10
保持打开。在open后立马执行了 unlink 函数 - 数据写入 :后续通过
pwritev(10, ...)
写入数据到文件描述符10
。 - 最终释放 :当
close(10)
执行时,文件数据被释放(引用计数归零)。
4. client_body_in_file_only on 逻辑分析
4.1. nginx.conf 配置
ini
server {
listen 80;
server_name localhost;
sendfile on;
#charset koi8-r;
#access_log logs/host.access.log main;
#location / {
# root html;
# index index.html index.htm;
#}
client_body_temp_path data/client_body;
#client_body_buffer_size 1;
#开启并且不清理
client_body_in_file_only on;
location /baidu {
proxy_pass http://baidu.com:80;
}
}
4.2. 请求报文文件
发送请求后可以看到文件还存留。事实上从名字就可以看出这两个配置不是一回事,一个是临时文件,一个是用于保存报文的,多用于排查问题。
csharp
[root@VM-16-2-centos nginx]# ls /root/nginx/data/client_body/
0000000001
4.3. strace 跟踪
css
23:38:30.877543 epoll_wait(8, [{EPOLLIN, {u32=833757200, u64=140033947475984}}], 512, -1) = 1 <8.515686>
23:38:39.393468 accept4(6, {sa_family=AF_INET, sin_port=htons(53393), sin_addr=inet_addr("xxx")}, [112->16], SOCK_NONBLOCK) = 3 <0.000031>
23:38:39.393615 epoll_ctl(8, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=833757648, u64=140033947476432}}) = 0 <0.000026>
23:38:39.393687 epoll_wait(8, [{EPOLLIN, {u32=833757648, u64=140033947476432}}], 512, 60000) = 1 <0.000025>
23:38:39.393763 recvfrom(3, "POST /baidu HTTP/1.1\r\nConnection"..., 1024, 0, NULL, NULL) = 1024 <0.000026>
23:38:39.393862 ioctl(3, FIONREAD, [9304]) = 0 <0.000024>
23:38:39.393988 write(5, "2025/06/05 23:38:39 [error] 2330"..., 168) = 168 <0.000030>
23:38:39.394067 write(5, "2025/06/05 23:38:39 [error] 2330"..., 195) = 195 <0.000024>
23:38:39.394130 write(5, "2025/06/05 23:38:39 [error] 2330"..., 195) = 195 <0.000023>
23:38:39.394193 write(5, "2025/06/05 23:38:39 [error] 2330"..., 175) = 175 <0.000023>
23:38:39.394277 write(5, "2025/06/05 23:38:39 [error] 2330"..., 167) = 167 <0.000026>
23:38:39.394381 write(5, "2025/06/05 23:38:39 [error] 2330"..., 165) = 165 <0.000029>
23:38:39.394454 write(5, "2025/06/05 23:38:39 [error] 2330"..., 177) = 177 <0.000026>
23:38:39.394521 write(5, "2025/06/05 23:38:39 [error] 2330"..., 172) = 172 <0.000026>
23:38:39.394614 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 0, NULL, NULL) = 8192 <0.000031>
//打开文件
23:38:39.394699 open("/root/nginx/data/client_body/0000000001", O_RDWR|O_CREAT|O_EXCL, 0600) = 10 <0.000047>
//写入数据
23:38:39.394819 pwritev(10, [{iov_base="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., iov_len=732}, {iov_base="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., iov_len=8192}], 2, 0) = 8924 <0.000041>
23:38:39.394908 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 0, NULL, NULL) = 1112 <0.000020>
23:38:39.394977 epoll_wait(8, [{EPOLLIN, {u32=833757648, u64=140033947476432}}], 512, 60000) = 1 <0.682829>
23:38:40.077905 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 7080, 0, NULL, NULL) = 7080 <0.000030>
23:38:40.078016 ioctl(3, FIONREAD, [24853]) = 0 <0.000022>
23:38:40.078097 pwrite64(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 8924) = 8192 <0.000039>
23:38:40.078185 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 0, NULL, NULL) = 8192 <0.000026>
23:38:40.078249 pwrite64(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 17116) = 8192 <0.000026>
23:38:40.078321 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 0, NULL, NULL) = 8192 <0.000028>
23:38:40.078397 pwrite64(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 25308) = 8192 <0.000033>
23:38:40.078471 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 0, NULL, NULL) = 8192 <0.000029>
23:38:40.078544 pwrite64(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 33500) = 8192 <0.000032>
23:38:40.078617 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 277, 0, NULL, NULL) = 277 <0.000024>
23:38:40.078686 pwrite64(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 277, 41692) = 277 <0.000026>
23:38:40.078753 epoll_ctl(8, EPOLL_CTL_MOD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=833757648, u64=140033947476432}}) = 0 <0.000025>
23:38:40.078858 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 11 <0.000031>
23:38:40.078930 ioctl(11, FIONBIO, [1]) = 0 <0.000023>
23:38:40.078998 epoll_ctl(8, EPOLL_CTL_ADD, 11, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=833757872, u64=140033947476656}}) = 0 <0.000024>
23:38:40.079072 connect(11, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("110.242.68.66")}, 16) = -1 EINPROGRESS (操作现在正在进行) <0.000056>
23:38:40.079341 epoll_wait(8, [{EPOLLOUT, {u32=833757648, u64=140033947476432}}], 512, 60000) = 1 <0.000032>
23:38:40.079432 epoll_wait(8, [{EPOLLOUT, {u32=833757872, u64=140033947476656}}], 512, 59998) = 1 <0.035487>
23:38:40.114998 getsockopt(11, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 <0.000024>
23:38:40.115103 writev(11, [{iov_base="POST /baidu HTTP/1.0\r\nHost: baid"..., iov_len=285}], 1) = 285 <0.000036>
23:38:40.115191 sendfile(11, 10, [0] => [27056], 41969) = 27056 <0.000063>
23:38:40.115302 sendfile(11, 10, [27056], 14913) = -1 EAGAIN (资源暂时不可用) <0.000026>
23:38:40.115395 epoll_wait(8, [{EPOLLOUT, {u32=833757872, u64=140033947476656}}], 512, 59962) = 1 <0.043149>
23:38:40.158615 sendfile(11, 10, [27056] => [41969], 14913) = 14913 <0.000063>
23:38:40.158745 epoll_wait(8, [{EPOLLIN|EPOLLOUT|EPOLLRDHUP, {u32=833757872, u64=140033947476656}}], 512, 60000) = 1 <0.079367>
23:38:40.238207 recvfrom(11, "HTTP/1.1 302 Found\r\nDate: Thu, 0"..., 4096, 0, NULL, NULL) = 478 <0.000028>
//client_body_in_file_only = on,所以是 close,不会清理
23:38:40.238352 close(10) = 0 <0.000026>
23:38:40.238416 readv(11, [{iov_base="", iov_len=3618}], 1) = 0 <0.000010>
23:38:40.238478 close(11) = 0 <0.000027>
23:38:40.238535 writev(3, [{iov_base="HTTP/1.1 302 Found\r\nServer: ngin"..., iov_len=274}, {iov_base="<!DOCTYPE HTML PUBLIC "-//IETF//"..., iov_len=210}], 2) = 484 <0.000017>
23:38:40.238592 shutdown(3, SHUT_WR) = 0 <0.000014>
23:38:40.238641 epoll_wait(8, [{EPOLLOUT, {u32=833757648, u64=140033947476432}}], 512, 5000) = 1 <0.000008>
23:38:40.238679 epoll_wait(8, [{EPOLLIN|EPOLLOUT|EPOLLHUP|EPOLLRDHUP, {u32=833757648, u64=140033947476432}}], 512, 5000) = 1 <0.230488>
23:38:40.469249 recvfrom(3, "", 4096, 0, NULL, NULL) = 0 <0.000027>
23:38:40.469366 write(4, "54.86.50.139 - - [05/Jun/2025:23"..., 105) = 105 <0.000032>
23:38:40.469449 close(3) = 0 <0.000031>
23:38:40.469519 epoll_wait(8, <detached ...>
可以看到有执行 close,但是没有任何对句柄 10 执行过 unlink。
5. client_body_in_file_only clean 逻辑分析
5.1. nginx.conf 配置
ini
server {
listen 80;
server_name localhost;
sendfile on;
#charset koi8-r;
#access_log logs/host.access.log main;
#location / {
# root html;
# index index.html index.htm;
#}
client_body_temp_path data/client_body;
#client_body_buffer_size 1;
#设置值为 clean
client_body_in_file_only clean;
location /baidu {
proxy_pass http://baidu.com:80;
}
}
5.2. strace 跟踪
css
23:41:17.679693 epoll_wait(8, [{EPOLLIN, {u32=3363688464, u64=140380074790928}}], 512, -1) = 1 <6.273008>
23:41:23.952979 accept4(6, {sa_family=AF_INET, sin_port=htons(58767), sin_addr=inet_addr("54.86.50.139")}, [112->16], SOCK_NONBLOCK) = 3 <0.000032>
23:41:23.953143 epoll_ctl(8, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=3363688912, u64=140380074791376}}) = 0 <0.000026>
23:41:23.953218 epoll_wait(8, [{EPOLLIN, {u32=3363688912, u64=140380074791376}}], 512, 60000) = 1 <0.000024>
23:41:23.953293 recvfrom(3, "POST /baidu HTTP/1.1\r\nConnection"..., 1024, 0, NULL, NULL) = 1024 <0.000026>
23:41:23.953394 ioctl(3, FIONREAD, [12100]) = 0 <0.000024>
23:41:23.953518 write(5, "2025/06/05 23:41:23 [error] 2424"..., 168) = 168 <0.000033>
23:41:23.953596 write(5, "2025/06/05 23:41:23 [error] 2424"..., 195) = 195 <0.000026>
23:41:23.953664 write(5, "2025/06/05 23:41:23 [error] 2424"..., 195) = 195 <0.000026>
23:41:23.953732 write(5, "2025/06/05 23:41:23 [error] 2424"..., 175) = 175 <0.000034>
23:41:23.953808 write(5, "2025/06/05 23:41:23 [error] 2424"..., 167) = 167 <0.000026>
23:41:23.953876 write(5, "2025/06/05 23:41:23 [error] 2424"..., 165) = 165 <0.000026>
23:41:23.953940 write(5, "2025/06/05 23:41:23 [error] 2424"..., 177) = 177 <0.000026>
23:41:23.954008 write(5, "2025/06/05 23:41:23 [error] 2424"..., 172) = 172 <0.000026>
23:41:23.954103 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 0, NULL, NULL) = 8192 <0.000030>
//创建文件,句柄为10
23:41:23.954188 open("/root/nginx/data/client_body/0000000001", O_RDWR|O_CREAT|O_EXCL, 0600) = 10 <0.000047>
//写入数据
23:41:23.954303 pwritev(10, [{iov_base="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., iov_len=732}, {iov_base="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., iov_len=8192}], 2, 0) = 8924 <0.000039>
23:41:23.954389 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 0, NULL, NULL) = 3908 <0.000026>
23:41:23.954463 epoll_wait(8, [{EPOLLIN, {u32=3363688912, u64=140380074791376}}], 512, 60000) = 1 <0.199747>
23:41:24.154302 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 4284, 0, NULL, NULL) = 4284 <0.000028>
23:41:24.154406 ioctl(3, FIONREAD, [9836]) = 0 <0.000024>
23:41:24.154479 pwrite64(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 8924) = 8192 <0.000041>
23:41:24.154570 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 0, NULL, NULL) = 8192 <0.000028>
23:41:24.154648 pwrite64(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 17116) = 8192 <0.000033>
23:41:24.154723 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 0, NULL, NULL) = 1644 <0.000025>
23:41:24.154795 epoll_wait(8, [{EPOLLIN, {u32=3363688912, u64=140380074791376}}], 512, 59799) = 1 <0.200448>
23:41:24.355322 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 6548, 0, NULL, NULL) = 6548 <0.000030>
23:41:24.355441 ioctl(3, FIONREAD, [8469]) = 0 <0.000024>
23:41:24.355511 pwrite64(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 25308) = 8192 <0.000045>
23:41:24.355598 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 0, NULL, NULL) = 8192 <0.000029>
23:41:24.355673 pwrite64(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192, 33500) = 8192 <0.000032>
23:41:24.355746 recvfrom(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 277, 0, NULL, NULL) = 277 <0.000025>
23:41:24.355816 pwrite64(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 277, 41692) = 277 <0.000026>
23:41:24.355883 epoll_ctl(8, EPOLL_CTL_MOD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=3363688912, u64=140380074791376}}) = 0 <0.000036>
23:41:24.356001 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 11 <0.000031>
23:41:24.356079 ioctl(11, FIONBIO, [1]) = 0 <0.000024>
23:41:24.356146 epoll_ctl(8, EPOLL_CTL_ADD, 11, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=3363689136, u64=140380074791600}}) = 0 <0.000025>
23:41:24.356217 connect(11, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("110.242.68.66")}, 16) = -1 EINPROGRESS (操作现在正在进行) <0.000046>
23:41:24.356471 epoll_wait(8, [{EPOLLOUT, {u32=3363688912, u64=140380074791376}}], 512, 60000) = 1 <0.000022>
23:41:24.356537 epoll_wait(8, [{EPOLLOUT, {u32=3363689136, u64=140380074791600}}], 512, 59999) = 1 <0.035463>
23:41:24.392100 getsockopt(11, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 <0.000023>
23:41:24.392210 writev(11, [{iov_base="POST /baidu HTTP/1.0\r\nHost: baid"..., iov_len=285}], 1) = 285 <0.000036>
23:41:24.392298 sendfile(11, 10, [0] => [27056], 41969) = 27056 <0.000069>
23:41:24.392418 sendfile(11, 10, [27056], 14913) = -1 EAGAIN (资源暂时不可用) <0.000025>
23:41:24.392505 epoll_wait(8, [{EPOLLOUT, {u32=3363689136, u64=140380074791600}}], 512, 59963) = 1 <0.045318>
23:41:24.437898 sendfile(11, 10, [27056] => [41969], 14913) = 14913 <0.000054>
23:41:24.438033 epoll_wait(8, [{EPOLLIN|EPOLLOUT|EPOLLRDHUP, {u32=3363689136, u64=140380074791600}}], 512, 60000) = 1 <0.081656>
23:41:24.519788 recvfrom(11, "HTTP/1.1 302 Found\r\nDate: Thu, 0"..., 4096, 0, NULL, NULL) = 478 <0.000030>
23:41:24.519955 readv(11, [{iov_base="", iov_len=3618}], 1) = 0 <0.000040>
23:41:24.520056 close(11) = 0 <0.000041>
23:41:24.520144 writev(3, [{iov_base="HTTP/1.1 302 Found\r\nServer: ngin"..., iov_len=274}, {iov_base="<!DOCTYPE HTML PUBLIC "-//IETF//"..., iov_len=210}], 2) = 484 <0.000031>
23:41:24.520223 shutdown(3, SHUT_WR) = 0 <0.000028>
23:41:24.520297 epoll_wait(8, [{EPOLLOUT, {u32=3363688912, u64=140380074791376}}], 512, 5000) = 1 <0.000023>
23:41:24.520362 epoll_wait(8, [{EPOLLIN|EPOLLOUT|EPOLLHUP|EPOLLRDHUP, {u32=3363688912, u64=140380074791376}}], 512, 4999) = 1 <0.202023>
23:41:24.722466 recvfrom(3, "", 4096, 0, NULL, NULL) = 0 <0.000026>
23:41:24.722584 write(4, "54.86.50.139 - - [05/Jun/2025:23"..., 105) = 105 <0.000031>
//执行 unlink,即 delete
23:41:24.722673 unlink("/root/nginx/data/client_body/0000000001") = 0 <0.000037>
//使用close,关闭句柄
23:41:24.722760 close(10) = 0 <0.000025>
23:41:24.722818 close(3) = 0 <0.000016>
23:41:24.722866 epoll_wait(8, <detached ...>