前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Nginx11】Nginx学习:HTTP核心模块(八)文件处理

【Nginx11】Nginx学习:HTTP核心模块(八)文件处理

作者头像
硬核项目经理
发布2023-08-09 11:17:10
1730
发布2023-08-09 11:17:10
举报

Nginx学习:HTTP核心模块(八)文件处理

继续我们的 HTTP 核心模块之旅。今天主要是文件相关的一些处理操作,包括 DirectIO、文件缓存以及 sendfile 相关的配置。这三个配置中,大家应该会见过 sendfile ,但是另外两个就比较少见了。包括我之前也从来没见过,不过还好,DirectIO 并不是一个完全的陌生人,文件缓存优化也与操作系统基础知识有关,而 sendfile 一般默认就是开启的,所以大家也不要有太大的压力哦。

directio

是不是看着很眼熟,没错,早前我们在 PHP 的小课堂文章中学习过。没印象或者想不起来的小伙伴可以移步 PHP中DirectIO直操作文件扩展的使用https://mp.weixin.qq.com/s/fS6X2IlrnrBrBZwwqRF3vA (去博客搜索也可以哦) 去回忆一下哦。别的不多说了,直接来看看相关的配置。由于不知道要怎么测试,所以就简单地介绍一些这些配置就好了,如果有小伙伴了解这一块要怎么测试的,可以评论区留言哦。

directio

当读入长度大于等于指定 size 的文件时,开启 DirectIO 功能。

代码语言:javascript
复制
directio size | off;

具体的做法是, 在 FreeBSD 或 Linux 系统开启使用 O_DIRECT 标志, 在 Mac OS X 系统开启使用 F_NOCACHE 标志, 在 Solaris 系统开启使用 directio() 功能。这条指令自动关闭 sendfile(0.7.15版) 。它在处理大文件时 directio 4m; 或者在 Linux 系统使用 aio 时比较有用。默认 off 。

directio_alignment

为 DirectIO 设置文件偏移量对齐。

代码语言:javascript
复制
directio_alignment size;

大多数情况下,按512字节对齐足矣, 但在 Linux 系统下使用 XFS ,需要将值扩大到 4K 。

文件优化缓存

这个缓存是个什么东西呢?它可以用于减少 Nginx 的系统调用,缓存文件句柄、大小和修改时间等。具体作用我们在最后会看到。

open_file_cache

用于配置文件缓存。

代码语言:javascript
复制
open_file_cache off;
open_file_cache max=N [inactive=time];

默认是 off ,也就是关闭状态的。可配置的值包括:

  • max=N,设置缓存中元素的最大数量,当缓存溢出时,使用 LRU(最近最少使用) 算法删除缓存中的元素
  • inactive=time,在这段时间内缓存元素如果没有被访问,将从缓存中删除。默认超时是60秒

它可以缓存的内容包括:

  • 打开文件的描述符,大小和修改时间
  • 目录查找结果
  • 文件查找时的错误结果,诸如“file not found”(文件不存在)、“no read permission”(无读权限) 等等

在使用文件缓存的时候,最好 open_file_cache_errors 也开启,这个命令我们后面马上会说。

open_file_cache_errors

开启或者关闭缓存文件查找的错误结果

代码语言:javascript
复制
open_file_cache_errors on | off;

默认值是 off ,如果确定要使用文件缓存的话,最好把它也打开。

open_file_cache_min_uses

设置在由 open_file_cache 指令的 inactive 参数配置的超时时间内, 文件应该被访问的最小 number(次数) 。

代码语言:javascript
复制
open_file_cache_min_uses number;

如果访问次数大于等于此值,文件描述符会保留在缓存中,否则从缓存中删除。

open_file_cache_valid

设置检查 open_file_cache 缓存的元素的时间间隔。

代码语言:javascript
复制
open_file_cache_valid time;

默认 60s 检查一次。

配置测试

首先使用 strace 命令追踪一下当前 Nginx 的 Worker 进程,为了方便测试,咱们可以把 worker_processes 设置为 1 ,只启动一个工作进程。然后随便请求一个 URL 就会出现下面的内容。

代码语言:javascript
复制
[root@localhost html]# strace -p 2365
strace: Process 2365 attached
epoll_wait(15, [{EPOLLIN, {u32=2915635216, u64=140495590854672}}], 512, -1) = 1
accept4(8, {sa_family=AF_INET, sin_port=htons(54245), sin_addr=inet_addr("192.168.56.1")}, [112->16], SOCK_NONBLOCK) = 5
epoll_ctl(15, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2915635912, u64=140495590855368}}) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 60000) = 1
recvfrom(5, "GET /aaa HTTP/1.1\r\nTEST_UNDERLIN"..., 1024, 0, NULL, NULL) = 224
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa", O_RDONLY|O_NONBLOCK) = 6
fstat(6, {st_mode=S_IFDIR|0755, st_size=24, ...}) = 0
close(6)                                = 0
// =======注意这里=======
writev(5, [{iov_base="HTTP/1.1 301 Moved Permanently\r\n"..., iov_len=200}, {iov_base="<html>\r\n<head><title>301 Moved P"..., iov_len=116}, {iov_base="<hr><center>nginx/1.23.0</center"..., iov_len=53}], 3) = 369
write(7, "192.168.56.1 - - [07/Aug/2022:22"..., 102) = 102
setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(5, "GET /aaa/ HTTP/1.1\r\nTEST_UNDERLI"..., 1024, 0, NULL, NULL) = 260
stat("/usr/local/nginx/html/aaa/index.html", {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa/index.html", O_RDONLY|O_NONBLOCK) = 6
fstat(6, {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
// =======注意这里=======
writev(5, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=235}], 1) = 235
sendfile(5, 6, [0] => [10], 10)         = 10
write(7, "192.168.56.1 - - [07/Aug/2022:22"..., 125) = 125
close(6)                                = 0
epoll_wait(15, 

主要需要看的就是上面注释中的部分,有三个操作,分别是 openat、fstat 和 close 操作,分别对应着打开文件句柄、获取文件统计信息以及关闭文件句柄这三个操作。目前的情况下,多次访问,还是一样的结果,每次都会有这三个步骤。

接下来,我们就简单配置下文件缓存,直接使用官方文档中提供的示例配置。这几个命令可以配置在 http、server、location 中,我们就简单地在 server 中进行配置吧。

代码语言:javascript
复制
server {
  ...
  open_file_cache          max=1000 inactive=20s;
  open_file_cache_valid    30s;
  open_file_cache_min_uses 2;
  open_file_cache_errors   on;
  ...
}

然后再次访问,第一次还是会正常出现 openat、fstat 和 close 这三个系统函数的调用。但是之后再次访问,就会发现这三个系统调用不见了。

代码语言:javascript
复制
[root@localhost html]# strace -p 2423
strace: Process 2423 attached
epoll_wait(15, [{EPOLLIN, {u32=2915635216, u64=140495590854672}}], 512, -1) = 1
accept4(8, {sa_family=AF_INET, sin_port=htons(65255), sin_addr=inet_addr("192.168.56.1")}, [112->16], SOCK_NONBLOCK) = 7
epoll_ctl(15, EPOLL_CTL_ADD, 7, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2915635912, u64=140495590855368}}) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 60000) = 1
recvfrom(7, "GET /aaa HTTP/1.1\r\nTEST_UNDERLIN"..., 1024, 0, NULL, NULL) = 224
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa", O_RDONLY|O_NONBLOCK) = 12
fstat(12, {st_mode=S_IFDIR|0755, st_size=24, ...}) = 0
close(12)                               = 0
// =======注意这里=======
writev(7, [{iov_base="HTTP/1.1 301 Moved Permanently\r\n"..., iov_len=200}, {iov_base="<html>\r\n<head><title>301 Moved P"..., iov_len=116}, {iov_base="<hr><center>nginx/1.23.0</center"..., iov_len=53}], 3) = 369
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 102) = 102
setsockopt(7, SOL_TCP, TCP_NODELAY, [1], 4) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(7, "GET /aaa/ HTTP/1.1\r\nTEST_UNDERLI"..., 1024, 0, NULL, NULL) = 260
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa/index.html", O_RDONLY|O_NONBLOCK) = 12
fstat(12, {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
// =======注意这里=======
writev(7, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=235}], 1) = 235
sendfile(7, 12, [0] => [10], 10)        = 10
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 125) = 125
// 这里开始是后续访问
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(7, "GET /aaa HTTP/1.1\r\nTEST_UNDERLIN"..., 1024, 0, NULL, NULL) = 224
writev(7, [{iov_base="HTTP/1.1 301 Moved Permanently\r\n"..., iov_len=200}, {iov_base="<html>\r\n<head><title>301 Moved P"..., iov_len=116}, {iov_base="<hr><center>nginx/1.23.0</center"..., iov_len=53}], 3) = 369
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 102) = 102
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(7, "GET /aaa/ HTTP/1.1\r\nTEST_UNDERLI"..., 1024, 0, NULL, NULL) = 260
writev(7, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=235}], 1) = 235
sendfile(7, 12, [0] => [10], 10)        = 10
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 125) = 125
epoll_wait(15,

很明显,这就是文件缓存在起作用。减少了文件相关的系统调用读取的次数。为什么我们上面访问的内容会有两遍请求呢?我访问的是 /aaa 目录,直接访问目录会找这个目录下面的 index.html 文件,因此有一次 301 跳转。你也可以直接访问 /aaa/index.html 就会看得更清楚一些了。

这一套文件缓存不会缓存文件的具体内容,而只是操作符句柄及文件的一些统计信息,Nginx 虽然已经对静态内容做过优化。但在高流量网站的情况下,仍然可以使用 open_file_cache 进一步提高性能,减少系统调用。通过扩大这个缓存的容量可以提高线上的实际命中率。但是缓存容量并不是越大越好,比如当达到 20000 个元素的容量时,共享内存的锁就成了瓶颈。所以,可以在确定访问频次非常高的静态文件 location 或者 server 上开启这一套文件缓存,数量也不用太多,可以让性能有更进一步的提升。

PHP-FPM 或者反向代理之类的和这个文件缓存就没啥关系了,PHP-FPM 走的是 socket 句柄,通过连接 PHP-FPM 进行操作,而打开 php 文件的是 PHP-FPM ,不是 Nginx 。

sendfile

这一块又要牵涉到操作系统了。在操作系统编程中,sendfile() 是系统调用,而 read() 这种是函数调用,因此,使用 sendfile() 对于文件读取来说会带来性能的提升。

读取发送文件的时候,使用了 sendfile() 那么 Nginx 就会直接向系统内核发送指令,然后发送文件也是系统内核直接完成,只有一次复制操作,实现了异步网络 IO 的形式。而传统情况则是从磁盘中以流的形式加载文件,然后再将文件流复制到系统内核中,内核再发送。区别就在这里。其实就是说,传统方式在读写文件时,会从硬盘到系统空间,再到用户空间这样走一圈,而使用 sendfile() 则只在系统空间,不用走到用户空间,从而实现零拷贝,减少空间切换的消耗。

当然,sendfile 只对静态网站有用,也就是确实需要进行文件读写发送的。如果是动态网站,比如 FastCGI 或反向代理的,对接的实际上是 socket 接口,真正的文件处理是在动态语言中进行的,比如 PHP 的模板文件加载等。因此,它只对静态页面或文件有性能提升的效果。

sendfile

开启或关闭使用 sendfile() 调用。

代码语言:javascript
复制
sendfile on | off;

现在默认就是打开的,从 nginx 0.8.12 和 FreeBSD 5.2.1 开始,可以使用 aio 预加载 sendfile的数据,Linux 没有哦。

send_lowat

如果设置成非 0 值,Nginx 将尝试最小化向客户端发送数据的次数。

代码语言:javascript
复制
send_lowat size;

默认值是 0 ,它是通过将 kqueue 方法的 NOTE_LOWAT 标志, 或者将套接字的 SO_SNDLOWA T属性设置成指定的 size 实现的。这条指令在Linux、Solaris和Windows操作系统无效。

sendfile_max_chunk

设置为非0值时,可以限制在一次 sendfile() 调用时传输的数据量。

代码语言:javascript
复制
sendfile_max_chunk size;

最新的版本默认 2m,如果不进行限制,一个快速的连接可能会霸占整个worker进程的所有资源。在版本1.21.4之前,默认情况下是 0 ,没有限制。

总结

好吧好吧,今天的内容有点水,因为我确实不知道 DirectIO 和 sendfile 在 Nginx 中要怎么测试。如果有了解过的小伙伴记得评论留言哦。不过通常来说,我们会在刚安装好的 Nginx 配置文件中看到 sendfile 是 on 的状态,一般来说,不懂的就别瞎配了,咱惹不起还躲不起嘛,其它的配置让它们就走默认好了。还是那句话,留个印象,将来如果有用到的时候,能够想起来并且查资料能有个方向就够了。毕竟,现在的水平只到这里,谁知道将来我们能不能成为大神呢,要是真成了,再回来好好补上呗!

参考文档:

http://nginx.org/en/docs/http/ngx_http_core_module.html

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农老张 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Nginx学习:HTTP核心模块(八)文件处理
    • directio
      • directio
      • directio_alignment
    • 文件优化缓存
      • open_file_cache
      • open_file_cache_errors
      • open_file_cache_min_uses
      • open_file_cache_valid
      • 配置测试
    • sendfile
      • sendfile
      • send_lowat
      • sendfile_max_chunk
    • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档