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

文章目录
    分类:存储运维 标签:Nginx

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

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

    directio

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

    directio

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

    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 设置文件偏移量对齐。

    directio_alignment size;

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

    文件优化缓存

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

    open_file_cache

    用于配置文件缓存。

    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

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

    open_file_cache_errors on | off;

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

    open_file_cache_min_uses

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

    open_file_cache_min_uses number;

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

    open_file_cache_valid

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

    open_file_cache_valid time;

    默认 60s 检查一次。

    配置测试

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

    [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 中进行配置吧。

    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 这三个系统函数的调用。但是之后再次访问,就会发现这三个系统调用不见了。

    [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() 调用。

    sendfile on | off;

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

    send_lowat

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

    send_lowat size;

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

    sendfile_max_chunk

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

    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

    视频链接

    微信文章地址:https://mp.weixin.qq.com/s/7FXQvjGbKbevYEQN137Ixg

    B站视频地址:https://www.bilibili.com/video/BV1h94y1p75s/

    搜索
    关注