本节主要讲述一下nginx对静态文件的缓存相关操作。
1. nginx静态文件缓存
这里在介绍具体的源代码之前,我们首先简要介绍一下Nginx服务器中静态文件Cache的一个大体处理流程。事实上,整个nginx中只有两个地方会用到这种静态文件缓存:
-
ngx_http_core_module
-
ngx_http_log_module
nginx静态文件缓存中,可以存储如下信息:
注意: 如果要缓存错误信息的话,那么还需要单独启用 'open_file_cache_errors' 子令
其基本使用示例如下:
open_file_cache max=64 inactive=30d;
open_file_cache_min_uses 8;
open_file_cache_valid 3m;
open_file_cache_errors on;
上面 ‘max=64’ 表示设置缓冲文件的最大数目为64。超过此数目后,Nginx将按照LRU原则丢弃冷数据。‘inactive=30d’ 与 ‘open_file_cache_min_uses 8’ 表示如果在30天内某文件的访问次数低于8次,那就将它从缓存中删除。
‘open_file_cache_valid 3m’表示每3分钟检查一次缓存中的文件信息是否正确,如果不是则更新之。
1.1 原理介绍
这里我们主要介绍一下Linux下Nginx的静态文件缓存的实现。对于BSD版本,由于其可以通过kqueue来监听文件改变的事件,因此其实现会有一个不同的做法,这里我们不对其做详细介绍。
在Linux下,Nginx并没有使用Inotify
,而是每次都会判断文件的st_ino来得到文件是否被修改,不过这样会有个缺点就是如果你是使用open(),然后write()来修改文件的话,因为是属于修改同一个文件,因此st_ino
是相同的,此时Nginx是无法感知到文件被修改了。因此要修改文件的话,最好使用先删除再覆盖的命令(比如cp命令)。
Nginx中的Cache只是cache文件句柄,因为静态文件的发送,一般来说,Nginx都是尽量使用sendfile()来进行发送的。因此只需要cache文件句柄就足够了。
所有的Cache对象包含在两个数据结构里面,整个机制最关键的也是这两个东西,一个是红黑树,另一个就是队列。其中红黑树是为了方便查找(能根据文件名迅速得到fd),而队列是为了方便超时管理(按照读取顺序插入,在队列头的就是最近存取的文件),由于所有的句柄的超时时间都是一样的,因此每次只需要判断最后几个元素就够了,因为这个超时并不需要那么实时。如下画出cache存储结构的整体图景:
假如现在客户端的请求是GET test.html HTTP/1.1
,则Nginx是这么处理的,如果test.html在cache中存在,则从cache中取得这个句柄,然后正常返回;如果test.html不存在,则是打开这个文件,然后插入到cache中。不过这里面有很多细节需要处理,比如超时,比如红黑树的插入等等。我们会在后面的章节来较为详细的介绍这些。
1.2 文件缓存更新周期
上面提到的配置文件中,若文件在30天内访问的次数低于8次,那么将会从缓存中丢弃。每3分钟做一次信息有效性监测,这里我们暂且把3分钟
称为缓存更新周期
。那在这3分钟之内文件发生变化了会怎样呢?
1) 文件被删除
由于nginx还持有原文件的fd,所以你删除此文件后,文件并不会真正消失,client还是能够通过原路径访问此文件。即便你删除后又新建了一个同名文件,在当前缓存更新周期内能访问到的还是原文件的内容。
2) 文件内容被修改
文件内容被修改可以分成两种情况:
- 文件大小不变或增大: 由于nginx缓存了文件的size,并且使用缓存中的size调用sendfile(2),所以此种情况的后果是:
1. 从文件开始到原size字节中的变化可以被client看到;
2. 原size之后的内容不会被sendfile(2)发送,因此client看不到此部分内容;
- 文件大小减小: 此种情况下,由于同样的原因,nginx在HTTP Header中告诉client文件大小还是原来的尺寸,而sendfile(2)只能发送真正的文件数据,长度小于HTTP Header中设置的大小,所以client会等待到自己超时或者Nginx在epoll_wait超时后关闭连接。
1.3 如何设置Nginx静态缓存
我们在使用Nginx静态文件缓存时,可以考虑如下:
-
如果你的静态文件内容变化频繁并且对时效性要求较高,一般应该把 ‘open_file_cache_valid’ 设置的小一些,以便及时检测和更新;
-
如果变化相当不频繁的话, 就可以设置大一点。在变化后用reload nginx的方式来强制更新缓存;
-
对静态文件访问的error和access log不关心的话,可以关闭以提升效率。
3. 相关静态函数声明
这里nginx对打开文件的缓存,主要有三种类型:
1) 打开文件的句柄,以及该文件对应额stat()信息
2) 目录的stat()信息;
3) 与文件查找相关的任何错误消息,例如file not found
、no read permission
4. 函数ngx_open_file_cache_init()
本函数较为简单,主要是初始化了ngx_open_file_cache_t
数据结构,并将其加入到pool cleanup中,来让其自动的管理cache的回收工作。
5. 函数ngx_open_file_cache_cleanup()
本函数用于从超时队列及红黑树中删除相应的缓存文件数据。一般来说,把所有的缓存数据从超时队列中移除之后,cache->current的值就会为0,如果不为0,表示出现了相应的错误,这里我们也只是打印相应的错误日志消息。
6. 函数ngx_open_cached_file()
nginx静态文件缓存的主要代码都包含在本函数中。下面我们详细介绍一下:
1) 处理cache为NULL时的特殊情况
一般只有在临时情况下传递的cache参数才会为NULL,这种情况通常只是简单获取ngx_open_file_info_t
信息。此种情况下我们看到也会从pool池中申请一块小内存来管理打开文件的清除操作,但是注意这里绑定的handler也只是单个文件的关闭,与上面我们介绍的 ‘清除cache中的所有打开的缓存文件’是不同的。
2) 查找文件
这里首先会从池中申请一小块ngx_open_file_cache_cleanup_t
空间,用于清除单个打开的文件,我们可以从后边所绑定的handler看出其中的不同。接着通过要打开的文件名name
来从红黑树中查找相应的缓存。这里我们注意到,是对文件名做crc32之后,来进行查找的,也即是说红黑树中所存储信息的key是crc32(file_name)。
3) 缓存文件查找成功
在第2)步中,我们会从红黑树中查找文件。如果查找成功,那么使用对应的fd来获取文件的最新信息,然后更新缓存并返回。另外:
4) cache中文件未找到
此处,cache中未找到对应的缓存文件,那么Nginx就会打开该文件,然后将其存入缓存中。这里我们注意到cache->current
达到了指定的最大值时,Nginx就会强制淘汰若干个(3个以下)文件。
5) 更新相应文件信息,并插入到超时队列
此处,更新前面获取到的文件信息到file
变量,然后再插入到超时队列。值得注意的是,这里同样会为该打开的缓存文件绑定一个缓存清除回调
函数。
6) 处理失败情况
处理失败时,从缓存中清除相应的缓存信息,并关闭对应的文件句柄。
7. 函数
这里由于openat()
并不能判断出所打开的文件是否是一个链接文件,因此必须要比较fstat()与fstatat()获取到的owner信息。
8. 函数ngx_file_o_path_info()
此函数也主要用于支持使用openat()函数来获取文件信息。
[参看]
-
Nginx服务器中静态文件cache的处理流程
-
使用Nginx缓存静态文件
-
Nginx Open File Cache
-
Nginx使用教程(五):使用Nginx缓存之缓存静态内容
-
open_file_cache