问题:Nginx client_body_temp_path 文件会删除吗,删除时机?

引言

问题: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

unlink 是 Linux 系统中的一个 系统调用(system call),用于从文件系统中删除一个文件的名称(即目录项)。它的核心作用是 解除文件路径与底层数据的关联,但实际删除文件数据的时机取决于文件的引用计数。以下是详细解析。

rm 命令和 Nginx delete 的实现都是 unlink。unlink 的重要特性之一是执行 unlink 后只要还持有句柄,那么依旧可以继续写入文件以及使用sendfile。只要仍有进程打开该文件(持有文件描述符),那么数据会保留到最后一个文件描述符关闭。

所以经常可以看到即使调用 unlink 删除路径,但是磁盘占用还没有下去,主要还是因为句柄还在,可以通过 lsof 查看句柄。

  1. 删除文件路径:移除文件在文件系统中的路径(例如 /path/to/file),使该路径不再指向实际数据。

  2. 减少引用计数:每个文件在文件系统中都有一个 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 ...>
相关推荐
EndingCoder44 分钟前
React从基础入门到高级实战:React 实战项目 - 项目三:实时聊天应用
前端·react.js·架构·前端框架
后海 0_o6 小时前
2025前端微服务 - 无界 的实战应用
前端·微服务·架构
喵叔哟6 小时前
24.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--认证微服务
微服务·架构·.net
java干货6 小时前
虚拟线程与消息队列:Spring Boot 3.5 中异步架构的演进与选择
spring boot·后端·架构
SoFlu软件机器人6 小时前
智能生成完整 Java 后端架构,告别手动编写 ControllerServiceDao
java·开发语言·架构
西陵7 小时前
前端框架渲染DOM的的方式你知道多少?
前端·javascript·架构
hsg7711 小时前
基于nacos2.5.1的MCP服务端微服务项目开发环境配置简介
微服务·云原生·架构
DemonAvenger11 小时前
减少内存分配:Go中值类型与指针类型的选择
性能优化·架构·go
fydw_71511 小时前
生产环境中安装和配置 Nginx 以部署 Flask 应用的详细指南
运维·nginx·flask