Nginx Open File Cache提高静态缓存效率与sendfile函数

分享于:2020-11-16 14:49:34

open_file_cache 相关配置


Nginx 的 open_file_cache 相关配置可以缓存静态文件的元信息,在这些静态文件被频繁访问时可以显著提升性能。


被缓存的文件元信息包括:


857040-20190515165029651-736926945.png



配置例子:


open_file_cache max=1000 inactive=20s; 
open_file_cache_valid   30s; 
open_file_cache_min_uses 2;
open_file_cache_errors  on;


open_file_cache 可以在 http{},server{},location{} 使用

解释:


最多可以缓存1000个文件, 超过此数字后 Nginx 将按照 LRU 原则丢弃冷数据。

inactive=20s; (其他单位:分m,时h,天d)20秒内使用次数至少2次(open_file_cache_min_uses 2;),不然不缓存。

open_file_cache_valid  30s; 被缓存的文件30秒内检查一次是否更新。


sendfile函数


为什么open_file_cache只缓存文件元信息而不缓存文件内容?

这个问题的关键是 sendfile函数。


在apache,nginx,lighttpd等webserver其中,都有一项sendfile相关的配置,在一些网上的资料都有谈到sendfile会提升文件传输性能,那sendfile究竟是什么呢?它的原理又是怎样呢?

在传统的文件传输里面(read/write方式),在实现上是比较复杂的,须要经过多次上下文的切换。参考如下:


2020-11-16_150145.png


当要对一个文件进行传输的时候,其流程以下:


1、调用read函数,文件数据被copy到内核缓冲区
2、read函数返回,文件数据从内核缓冲区copy到用户缓冲区
3、write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。
4、数据从socket缓冲区copy到相关协议引擎。


read(file, tmp_buf, len);
write(socket, tmp_buf, len);


一般来说一个网络应用是通过读硬盘数据,然后写数据到 socket 来完成网络传输的。上面2行简单的代码掩盖了底层的非常多操作。来看看底层是怎么运行上面2行代码的:

1、系统调用 read() 产生一个上下文切换:从 user mode(用户模式) 切换到 kernel mode(内核模式),然后 DMA 运行拷贝,把文件数据从硬盘读到一个 kernel buffer 里。
2、数据从 kernel buffer 复制到 user buffer,然后系统调用 read() 返回,这时又产生一个上下文切换:从kernel mode 切换到 user mode。
3、系统调用 write() 产生一个上下文切换:从 user mode 切换到 kernel mode,然后把步骤2读到 user buffer 的数据复制到 kernel buffer(数据第2次复制到 kernel buffer),只是这次是个不同的 kernel buffer,这个 buffer 和 socket 相关联。
4、系统调用 write() 返回,产生一个上下文切换:从 kernel mode 切换到 user mode(第4次切换了),然后 DMA 从 kernel buffer 拷贝数据到协议栈(第4次拷贝了)。

上面4个步骤有4次上下文切换,有4次拷贝,我们发现假设能降低切换次数和拷贝次数将会有效提升性能。在kernel 2.0+ 版本号中,系统调用 sendfile() 就是用来简化上面步骤提升性能的。sendfile() 不但能降低切换次数并且还能降低拷贝次数。


以上细节是传统read/write方式进行网络文件传输的方式,我们能够看到,在这个过程其中,文件数据实际上是经过了四次copy操作:


硬盘—>内核buf—>用户buf—>socket相关缓冲区—>协议引擎

而sendfile系统调用则提供了一种降低以上多次copy,提升文件传输性能的方法。Sendfile系统调用是在2.1版本号内核时引进的:

sendfile(socket, file, len);


执行流程如下:
1、sendfile系统调用,文件数据被copy至内核缓冲区
2、再从内核缓冲区copy至内核中socket相关的缓冲区
3、最后再socket相关的缓冲区copy到协议引擎

相较传统read/write方式,2.1版本号内核引进的sendfile已经降低了【内核缓冲区到user缓冲区,再由user缓冲区到socket相关缓冲区】的文件copy。

而在内核版本号2.4之后,文件描述符结果被改变,sendfile实现了更简单的方式,系统调用方式仍然一样,细节与2.1版本不同之处在于:

当文件数据被拷贝到内核缓冲区时,不再将全部数据copy到socket相关的缓冲区,而是只将记录数据位置长度相关的数据保存到socket相关的缓存,而实际数据将由DMA模块直接发送到协议引擎,再次降低了一次copy操作。


nginx使用sendfile函数,需要在配置文件中打开

sendfile on;


在这样的机制下,我们缓存中有文件的 fd 和 size,直接调用 sendfile() 就可以了。如果要 Nginx 连内容一起缓存,那就需要每次文件变化都要用 read() 将数据从 kernel space 复制到 user space,然后放在 user space,每次应答请求的时候再从 user space 复制到 kernel space 然后写入 socket。比起前面的方式,这样的方式毫无优点。


在文件缓存更新周期内文件被修改会发生什么?


(1) 文件被删除

由于 nginx 还持有原文件的 fd,所以你删除此文件后,文件并不会真正消失, client 还是能通过原路径访问此文件。即便你删除后又新建了一个同名文件,在当前缓存更新周期内能访问到的还是原文件的内容。


(2) 文件内容被修改

文件内容被修改可以分为两种情况:


文件大小不变或增大

由于 nginx 缓存了文件的 size 并且使用 这个缓存中 size 调用 sendfile(),所以此种情况的后果是:

从文件开始到原 size 字节中的变化可以被 client 看到,原 size 之后的内容不会被 sendfile() 发送,因此 client 看不到此部份内容


文件大小减小

此种情况下,由于同样原因,nginx 在 HTTP Header 中告诉 client 文件大小还是原来的尺寸,而 sendfile() 只能发送真正的文件数据。长度小于 HTTP Header 中设置的大小,所以 client 会等待到自己超时或者 Nginx 在 epoll_wait 超时后关闭连接。



设置建议


如果你的静态文件内容变化频繁并且对时效性要求较高,一般应该把 open_file_cache_valid 设置的小一些,以便及时检测和更新。

如果变化相当不频繁的话,那就可以设置大一点,在变化后用 reload nginx 的方式来强制更新缓存。

对静态文件访问的 error 和 access log 不关心的话,可以关闭已提升效率。


来源:https://www.cnblogs.com/cmfwm/p/7659179.html

来源:https://www.cnblogs.com/zfyouxi/p/4196170.html