• 正文
  • 相关推荐
申请入驻 产业图谱

Nginx | open_file_cache 功能,实现解决频繁 open/close 的性能顽疾

5小时前
129
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

大家好,我是 WeiyiGeek,一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(DevSecOps),自动化运维工具开发与实践,企业网络安全防护,欢迎各位道友一起学习交流、一起进步 ,若此文对你有帮助,一定记得点个关注⭐与小红星❤️或加入到作者知识星球『 全栈工程师修炼指南』,转发收藏学习不迷路  。

Nginx 使用 open_file_cache 缓存文件句柄及其信息提升性能

前面文章中我们一起学习了 Nginx 前端、后端(反向代理)缓存机制,并且实践了如何配置 Nginx 在不同环境下缓存,在此基础之上,本章将讲解 Nginx 中另外一个非常重要的缓存功能 open_file_cache,它通过缓存文件句柄及其信息,如修改时间、大小等,显著提升性能,尤其是避免频繁的open和close操作减少系统调用开销,对于优化 NGX 性能非常有帮助。

OK,接下来我们来看看 open_file_cache 缓存什么,它又是怎样对性能提示起到帮助的,最后通过一个案例来讲解如何配置 Nginx 的 open_file_cache 缓存功能。

温馨提示:若文章代码块中存在乱码或不能复制,请联系作者,也可通过文末的阅读原文链接,加入知识星球中阅读,原文链接:https://articles.zsxq.com/id_19oatlbj0w64.html

简单介绍

open_file_cache 属于 ngx_http_core_module 模块中的指令,其默认已经编译安装进入 Nginx,但是默认是不开启的,它可以缓存以下元信息:

[x] 文件句柄(fb): 缓存它意味着打开文件后不用每次 close 文件,并且再次使用时不用 open 文件,直接使用文件句柄即可,从而减少系统调用,提高性能

[x] 文件修改时间(mtime): 缓存它意味着在文件被频繁访问时,无需每次都调用 stat 系统调用来获取文件的修改时间

[x] 文件大小(size): 缓存它意味着在文件被频繁访问时,无需每次都调用 stat 系统调用来获取文件的大小

[x] 错误信息(err): 做文件查询中文件无权限访问时响应 403 Forbidden,无需每次都调用 stat 系统调用来获取文件的错误信息

[x] 文件存在性与类型: 缓存它意味着在文件被频繁访问时,无需每次都调用 stat 系统调用来判断文件是否真实存在

weiyigeek.top-open_file_cache缓存的元信息图

此外,open_file_cache 不仅适用于静态资源,还广泛用于日志和缓存文件操作,值得注意的时为保持缓存一致性,需合理设置超时时间,适应文件可能被外部进程频繁修改的场景。

指令参数

open_file_cache 指令在内存缓存打开文件的描述符,减少文件打开操作的次数,设定max值限制缓存文件数量,利用链表淘汰机制和inactive时间参数管理缓存,确保资源有效利用

Syntax: open_file_cache off;
        open_file_cache max=N [inactive=time];
Default: open_file_cache off;
Context: http, server, location

# 参数说明:
  max=N 设置内存缓存的最多元素数量,默认为1024个文件描述符,当缓存元素数量超过这个值时,会按照inactive参数指定的时间间隔进行清理最少使用(LRU)的元素。
  inactive 设置缓存元素的有效时间,默认为60s,如果元素在此时间内未被访问,则该元素将从该缓存中清除。
  off 关闭缓存功能,等同于不配置该指令

open_file_cache_error 指令用于启用或禁用文件查找错误的缓存, 在缓存有效期内会直接返回之前的错误信息而不会重新进行打开操作,减少不必要的系统调用。

Syntax: open_file_cache_errors on | off;
Default: open_file_cache_errors off;
Context: http, server, location

open_file_cache_min_uses 指令用于设置文件在 inactive 时间内至少需要被访问的次数后才能继续留在缓存中,类似于缓存热点文件(频繁访问)。

Syntax: open_file_cache_min_uses number;
Default: open_file_cache_min_uses 1;
Context: http, server, location

open_file_cache_valid 指令用于检查缓存文件句柄的有效性,确保文件被外部进程更新后,能够取得最新的文件信息,避免取得过时的文件内容。

Syntax: open_file_cache_valid time;
Default: open_file_cache_valid 60s;
Context: http, server, location

下面是 Nginx 官网文档给出的一个配置示例,用于演示如何使用 open_file_cache 相关指令。

Example:
  # 开启缓存,最大缓存1000个文件描述符,在单位时间 60s 内未被访问将被移除
  open_file_cache          max=1000 inactive=60s;
  # 30秒内检查缓存文件句柄的有效性,确保获取最新的文件信息
  open_file_cache_valid    30s;
  # 60秒内最少使用2次后才留继续在缓存中。
  open_file_cache_min_uses 2;
  # 开启错误信息缓存
  open_file_cache_errors   on;

 

好了,上面介绍了 open_file_cache 相关指令的参数,接下来通过一个实际例子来讲解如何配置 Nginx 的 open_file_cache 缓存功能。

实践案例

步骤 01.在 Nginx 配置文件中先不开启 open_file_cache 缓存功能,验证与开启后的差异,这里准备两个 location 设置根目录为本地静态资源,api 目录为反向代理上游。

$ vim /usr/local/nginx/conf.d/test.conf
server {
  listen 80;
  server_name test.weiyigeek.top;
  charset utf-8;
  default_type text/html;

# 启用 HTTP/2 支持
  http2 on;

# 日志文件
  access_log /var/log/nginx/test.log main;
  error_log /var/log/nginx/test.err.log debug;

# 静态资源 location 配置
  location / {
    root  /usr/local/nginx/html/slice;
    index index.html;
  }

# 反向代理 location 配置
  location ^~ /api/ {
    proxy_pass http://10.20.172.213:8088/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

 

步骤 02.重启 Nginx 服务,分布访问静态资源与反向代理资源,通过 strace 命令查看系统调用情况,由下图可以看出,在未开启 open_file_cache 缓存功能时,访问静态资源会频繁的进行 open 和 close 系统调用,对于性能明显有影响。

# 查询到 nginx work 进程号,这里为 653926
$ ps -ef | grep "nginx: worker process" | grep -v "grep"
nginx     653926  579435  0 08:18 ?        00:00:00 nginx: worker process

# 请求本地静态资源时,查看 nginx 工作进程的系统调用情况,可以发现 open 和 close 系统调用
$ strace -p 653926
strace: Process 653926 attached
epoll_wait(19, [], 512, 1223)           = 0  # 等待外部请求进来
epoll_wait(19, [{events=EPOLLIN, data={u32=864584440, u64=139707560788728}}], 512, 5000) = 1 # 外部请求进来
accept4(13, {sa_family=AF_INET, sin_port=htons(57858), sin_addr=inet_addr("10.20.10.103")}, [112 => 16], SOCK_NONBLOCK) = 4 # 接收外部请求
epoll_ctl(19, EPOLL_CTL_ADD, 4, {events=EPOLLIN|EPOLLRDHUP|EPOLLET, data={u32=864584936, u64=139707560789224}}) = 0 # 将外部请求加入 epoll 事件监听
epoll_wait(19, [{events=EPOLLIN, data={u32=864584936, u64=139707560789224}}], 512, 16) = 1 # 外部请求到来,开始处理
recvfrom(4, "GET /index.html HTTP/1.1rnHost: "..., 1024, 0, NULL, NULL) = 596  # 读取外部请求内容
getrandom("xd1x92x36x92", 4, 0)     = 4
getrandom("xfexa1x0ax52", 4, 0)     = 4
getrandom("xd3x6axdbxb4", 4, 0)     = 4
getrandom("x1cx85x2bx2f", 4, 0)     = 4
openat(AT_FDCWD, "/usr/local/nginx/html/index.html", O_RDONLY|O_NONBLOCK) = 6  # 关键点: 系统调用 open 打开文件
newfstatat(6, "", {st_mode=S_IFREG|0644, st_size=615, ...}, AT_EMPTY_PATH) = 0
writev(4, [{iov_base="HTTP/1.1 200 OKrnServer: nginx/1"..., iov_len=253}], 1) = 253
sendfile(4, 6, [0] => [615], 615)       = 615   # 关键点:sendfile 是一个优化点,利用零拷贝技术,减少内核态与用户态的切换。
write(15, "10.20.10.103 - - [09/Jan/2026:0"..., 236) = 236
getsockname(4, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("10.20.172.214")}, [112 => 16]) = 0
close(6)                                = 0                                   # 关键点:系统调用 close 关闭文件
epoll_wait(15, [{events=EPOLLIN, data={u32=304555240, u64=140402785461480}}], 512, 3930) = 1
recvfrom(3, "GET /index.html HTTP/1.1rnHost: "..., 1024, 0, NULL, NULL) = 544  # 再次请求相同的外部资源时,仍然会进行 open 和 close 系统调用
openat(AT_FDCWD, "/usr/local/nginx/html/index.html", O_RDONLY|O_NONBLOCK) = 20    # 关键点
newfstatat(20, "", {st_mode=S_IFREG|0644, st_size=615, ...}, AT_EMPTY_PATH) = 0
writev(3, [{iov_base="HTTP/1.1 200 OKrnServer: nginx/1"..., iov_len=253}], 1) = 253
sendfile(3, 20, [0] => [615], 615)      = 615
write(8, "10.20.10.103 - - [09/Jan/2026:1"..., 236) = 236
close(20)                               = 0    # 关键点
epoll_wait(15, [], 512, 1721)           = 0
write(18, "1", 8)        = 8
epoll_wait(15, strace: Process 653926 detached
 <detached ...>

# 请求反向代理上游资源时,查看 nginx 工作进程的系统调用情况
$ strace -p 653926
strace: Process 653926 attached
epoll_wait(19, [], 512, 1813)           = 0
epoll_wait(19, [{events=EPOLLIN, data={u32=864584936, u64=139707560789224}}], 512, 5000) = 1  # 外部请求进来
recvfrom(4, "GET /api/getuser HTTP/1.1rnHost:"..., 1024, 0, NULL, NULL) = 545 # 读取外部请求内容
epoll_ctl(19, EPOLL_CTL_MOD, 4, {events=EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data={u32=864584936, u64=139707560789224}}) = 0 # 将外部请求加入 epoll 事件监听
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 6
ioctl(6, FIONBIO, [1])                  = 0
epoll_ctl(19, EPOLL_CTL_ADD, 6, {events=EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data={u32=864585184, u64=139707560789472}}) = 0
connect(6, {sa_family=AF_INET, sin_port=htons(8088), sin_addr=inet_addr("10.20.172.213")}, 16) = -1 EINPROGRESS (Operation now in progress) # 连接反向代理上游服务器
epoll_wait(19, [{events=EPOLLOUT, data={u32=864584936, u64=139707560789224}}], 512, 4456) = 1
epoll_wait(19, [{events=EPOLLOUT, data={u32=864585184, u64=139707560789472}}], 512, 4455) = 1
getsockopt(6, SOL_SOCKET, SO_ERROR, [0], [4]) = 0  # 连接成功
writev(6, [{iov_base="GET /getuser HTTP/1.0rnHost: tes"..., iov_len=739}], 1) = 739 # 发送请求到反向代理上游服务器
epoll_wait(19, [{events=EPOLLIN|EPOLLOUT, data={u32=864585184, u64=139707560789472}}], 512, 4455) = 1 # 等待反向代理上游服务器响应,开始处理
recvfrom(6, "HTTP/1.1 200 OKrnX-Powered-By: E"..., 4096, 0, NULL, NULL) = 1380      # 接收反向代理上游服务器响应
close(6)                                = 0                                           # 关闭反向代理上游服务器连接
writev(4, [{iov_base="HTTP/1.1 200 OKrnServer: nginx/1"..., iov_len=237}, {iov_base="{n  "path": "/getuser",n  "heade"..., iov_len=1170}], 2) = 1407 # 将反向代理上游服务器响应发送给外部请求者
write(15, "10.20.10.103 - - [09/Jan/2026:0"..., 238) = 238
epoll_wait(19, strace: Process 653926 detached
 <detached ...>

温馨提示:上述事件中有一个 sendfile 它是一个优化点,利用零拷贝技术,减少内核态与用户态的切换,即磁盘中文件不需要先读到用户态,再从用户态发送到内核态,而是直接从内核态只能够将磁盘内容发送到网卡,所以说其性能很高。

温馨提示:Nginx 在等待外部请求进来时处于 epoll_wait 阶段,这个我们在前面文章《Nginx | 核心知识150讲,百万并发下性能优化之事件驱动框架笔记》中已经讲解过,当外部请求进来时,Nginx 会从 epoll_wait 阶段切换到系统调用阶段。

 

步骤 03.更改 Nginx 配置文件,启用 open_file_cache 开启缓存文件句柄及其信息功能,减少系统调用次数。

tee /usr/local/nginx/conf.d/test.conf <<'EOF'
server {
  listen 80;
  server_name test.weiyigeek.top;
  charset utf-8;
  default_type text/html;

# 启用 HTTP/2 支持
  http2 on;

# 日志文件
  access_log /var/log/nginx/test.log main;
  error_log /var/log/nginx/test.err.log debug;

# 静态资源 location 配置
  location / {
    root  /usr/local/nginx/html;
    index index.html;
    # 开启缓存,最大缓存1000个文件描述符,在单位时间 60s 内未被访问将被移除
    open_file_cache          max=1000 inactive=60s;
    # 30秒内检查缓存文件句柄的有效性,确保获取最新的文件信息
    open_file_cache_valid    30s;
    # 单位时间内最少使用2次后加入缓存
    open_file_cache_min_uses 2;
    # 开启错误信息缓存
    open_file_cache_errors   on;
  }

# 反向代理 location 配置
  location ^~ /api/ {
    proxy_pass http://10.20.172.213:8088/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}
EOF

温馨提示:作者只在本地静态资源场景下配置缓存文件句柄及其信息,反向代理中并未开启缓存,因为连接上游服务器不会打开文件句柄,只是会连接到上游将请求转发给上游,并接收处理响应。

 

步骤 04.验证重启 Nginx 服务,同样使用 strace 命令监控系统调用情况,使用浏览器访问 http://test.weiyigeek.top/index.html 访问四次,查看系统调用情况。

# 重载
nginx -s reload

# 查看 nginx 工作进程的 PID
$ ps -ef | grep "nginx: worker process" | grep -v "grep"
nginx     670912  667054  0 11:24 ?        00:00:00 nginx: worker process

# 查看 nginx 工作进程的系统调用情况
$ strace -p 670912
strace: Process 670912 attached
epoll_wait(15, [{events=EPOLLIN, data={u32=3197899512, u64=139623994757880}}], 512, -1) = 1
accept4(13, {sa_family=AF_INET, sin_port=htons(65250), sin_addr=inet_addr("10.20.172.103")}, [112 => 16], SOCK_NONBLOCK) = 3
epoll_ctl(15, EPOLL_CTL_ADD, 3, {events=EPOLLIN|EPOLLRDHUP|EPOLLET, data={u32=3197900008, u64=139623994758376}}) = 0
epoll_wait(15, [{events=EPOLLIN, data={u32=3197900008, u64=139623994758376}}], 512, 60000) = 1
recvfrom(3, "GET /index.html HTTP/1.1rnHost: "..., 1024, 0, NULL, NULL) = 625
# 首次打开文件句柄
openat(AT_FDCWD, "/usr/local/nginx/html/index.html", O_RDONLY|O_NONBLOCK) = 17  # 关键点
recvfrom(3, "GET /index.html HTTP/1.1rnHost: "..., 1024, 0, NULL, NULL) = 587
writev(3, [{iov_base="HTTP/1.1 200 OKrnServer: nginx/1"..., iov_len=253}], 1) = 253
sendfile(3, 17, [0] => [615], 615)      = 615                                  
write(8, "10.20.172.103 - - [09/Jan/2026:1"..., 234) = 234
close(17)           = 0  # 关键点,
setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
epoll_wait(15, [{events=EPOLLIN, data={u32=3197900008, u64=139623994758376}}], 512, 65000) = 1
recvfrom(3, "GET /index.html HTTP/1.1rnHost: "..., 1024, 0, NULL, NULL) = 625
# 第二打开文件句柄,加入缓存(由于前面设置了 open_file_cache_min_uses 指令最少两次),后续2️⃣次访问均命中缓存
openat(AT_FDCWD, "/usr/local/nginx/html/index.html", O_RDONLY|O_NONBLOCK) = 17   # 关键点,在内存中缓存了文件句柄,并且在缓存时间内不关闭文件句柄,后续也不再打开文件句柄。
newfstatat(17, "", {st_mode=S_IFREG|0644, st_size=615, ...}, AT_EMPTY_PATH) = 0
writev(3, [{iov_base="HTTP/1.1 304 Not ModifiedrnServe"..., iov_len=180}], 1) = 180  
write(8, "10.20.172.103 - - [09/Jan/2026:1"..., 234) = 234
epoll_wait(15, [{events=EPOLLIN, data={u32=3197900008, u64=139623994758376}}], 512, 65000) = 1
# 第三次访问,直接返回304状态码,不再打开文件句柄,而是直接使用缓存文件句柄
recvfrom(3, "GET /index.html HTTP/1.1rnHost: "..., 1024, 0, NULL, NULL) = 625
writev(3, [{iov_base="HTTP/1.1 304 Not ModifiedrnServe"..., iov_len=180}], 1) = 180  
write(8, "10.20.172.103 - - [09/Jan/2026:1"..., 234) = 234
epoll_wait(15, [{events=EPOLLIN, data={u32=3197900008, u64=139623994758376}}], 512, 65000) = 1 
# 最后一次,使用 F5 刷新访问,不再打开文件句柄,而是直接使用缓存文件句柄
recvfrom(3, "GET /index.html HTTP/1.1rnHost: "..., 1024, 0, NULL, NULL) = 625
writev(3, [{iov_base="HTTP/1.1 200 OKrnServer: nginx/1"..., iov_len=253}], 1) = 253  
sendfile(3, 17, [0] => [615], 615)      = 615
write(8, "10.20.172.103 - - [09/Jan/2026:1"..., 236) = 236
epoll_wait(15,

weiyigeek.top-缓存文件句柄及其信息效果图

至此,我们已经完成了 Nginx 静态资源服务性能优化的两大核心步骤,一则是返回给客户端的缓存头,另外一个就是 open_file_cache 利用其缓存文件句柄和其它信息,减少系统调用从而提升性能。

好了,以上我们介绍了 Nginx 中 Open_File_Cache 缓存文件句柄及其信息功能,此功能使用范围非常广泛,也是生产环境中常用的一个功能,它可以大幅度提升静态资源服务的性能。

加入:作者【全栈工程师修炼指南】知识星球

相关推荐