浏览 2.1k
缓存是计算机领域的一种非常重要设计。主要用来提升响应速度。比如计算机系统中的存储体系就是这一经典缓存的实现。它是由寄存器,缓存,内存,磁盘,网络组成的一个层级体系,用来存储各种信息。类比日常生活中,我们对于知识的存储系统也是一个层级的缓存体系。它是由我们的大脑,笔记本,身边朋友,书籍,网络组成的。这些层级系统有一个共同的特点就是层级越高它的容量越小,速度越快但是同时价格也越贵。
在我们使用浏览器获取信息时,也有一个缓存体系在发挥作用。那就是客户端浏览器缓存,中间各级代理设备缓存,服务器缓存,服务器源文件。
这种网络的缓存对于提升响应速度有非常好的作用。尤其是在分布式系统中,起到了非常大的作用。具体说来,网络缓存的主要作用有:
1. 缓存减少了冗余的数据传输,节省了网络费用。
2. 缓存减缓了网络瓶颈的问题,不需要更高的带宽就能够更快地加载页面。
3. 缓存减少了对原始服务器的请求,服务器可以更快地响应,避免过载。
4. 缓存减缓了距离时延,因为从较远的地方加载页面会更慢一些。
NGINX作为代理设备通过自身的缓存功能来提高响应速度。下面我们试着去分析NGINX缓存功能的工作原理和源码实现原理。
如下图所示,NGINX缓存功能就是通过把上游服务器的文件存放到本地磁盘上,然后在一定条件下直接使用它们来应答下游请求。 这样省去了和上游服务器的交互的时间,从而提高了效率,减轻了网络和上游服务器压力。
从实现角度来讲,如下图所示,NGINX从上游服务器获取到要缓存的文件后,需要在内存中维护一份它的元数据(meta data),并且在磁盘上保存文件完整数据。同时NGINX需要对两者进行有效的管理,比如维护两者的一致性,在一定条件下删除某些文件等。
NGINX的缓存功能都是围绕上述数据结构进行的。主要包括以下四个功能:
1. 管理
维护上图数据的一致性,定期删除过期缓存文件或者强制删除某些缓存文件来释放磁盘空间。
2. 加载
在NGINX启动时,如果对应目录中存在缓存文件,则需要生成对应内存中的文件元数据。
3. 生成
从上游获取文件时,如果需要缓存则创建内存中的元数据以及对应磁盘中的文件。
4. 使用
新的请求到来时,如果有对应的缓存文件可用,则直接使用磁盘中的文件返回。
配置了缓存功能后,NGINX就会启动Cache Manager进程和Cache Loader进程用来分别完成功能1和功能2。Cache Manager是一个常驻进程,它周期性地运行来淘汰过期缓存或者强制删除某些缓存文件释放磁盘空间。在两次缓存管理器启动的间隔,缓存的数据量可能短暂超过配置的大小。
与Cache Manager不同,Cache Loader进程只在启动时运行一次,完成任务后就退出。NGINX 启动1分钟之后,Cache Loader进程生成现有的缓存文件的元数据并且加载到共享内存区域中。
功能3和功能4是NGINX的worker进程在处理HTTP请求过程中完成的。
NGINX缓存实现涉及的总体内容比较多,我们将分成两部分进行分析。本篇文章,我们侧重分析功能1和功能2,也就是NGINX是如何管理和维护缓存数据的,以及在启动阶段,磁盘中的文件是如何生成对应的内存中的元数据的。
如下所示,只需要proxy_cache_path 和 proxy_cache 两条指令就可以开启内容缓存,前者用来设置缓存的路径和配置,后者用来启用缓存。
http {
...
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
proxy_cache_valid 30m;
server {
proxy_cache mycache;
location / {
proxy_pass http://localhost:8000;
}
}
}
Syntax:proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [min_free=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time];
Default: —
Context: http
指令用来设置cache的路径和其他参数。Cache数据存放在文件中。文件在Cache中的名字就是对请求特定的keys实施MD5的结果。
参数Path:指定了缓存存放的目录。
参数Level:在上述Path目录下再设置层次结构的目录。最多可以设置3层,每一层的目录名字最多为两个字节。设置层级结构是因为将大量的文件放置在单个目录中会导致文件访问缓慢。
比如配置:proxy_cache_path /data/nginx/cache levels=1:2:1 keys_zone=one:10m;
对应的磁盘中的文件如下所示:
/data/nginx/cache/c/29/0/b7f54b2df7773722d382f4809d65029c
需要注意的是,要保证NGINX进程有权限在配置的目录下创建文件。
参数use_temp_path:NGINX 最初会先将缓存文件放入一个临时存储区域,然后通过重命名存放到参数Path指定的目录中。需要注意的是,如果临时区域目录和Path指定的目录不在一个文件系统,那么重命名操作就会变成copy操作,从而带来额外的时间开销。所以,如果启用临时存储区域,建议和Path参数指定的Cache目录位于同一个文件系统上。如果此参数设置为on或者被忽略,指令proxy_temp_path指定的目录将会被用做临时存储区域。如果被设置为off,文件会直接写入Path参数指定的cache目录。
参数key_zone:定义缓存文件元数据和key值存放的共享内存区域。需要同时指定name和size参数。其中size定义共享内存区域的大小。在开源版本的NGINX中,1M大小内存区域可以存放大于8千个key值。
参数inactive:定义缓存的文件如果在inactive时间内没有被访问,不管该文件是否过期 都需要从磁盘中删除。该参数默认值为 10 分钟(10m)。注意,inactive时间有别于过期时间。缓存的过期时间是由上游服务器通过缓存控制头部决定的。缓存的inactive时间是指定现在距离上次被使用的时间。NGINX 不会自动删除过期时间到期的缓存。缓存在 inactive 指定时间内没有被访问的情况无论是否过期都会被删除。
参数max_size:设置了缓存占用磁盘空间大小的上限。如果不指定具体值,那就是允许缓存不断增长,占用所有可用的磁盘空间。当缓存达到这个上线,NGINX便调用 Cache manager 来移除最近最少被使用(LRU)的文件,这样把缓存的使用空间降低至这个限制之下。
参数min_free:从1.19.1版本开始加入。指定如果Cache目录所在的文件系统的剩余可用空间小于min_free指定的数值,NGINX也会调用Cache manager来移除最近最少被使用(LRU)的文件,这样把文件系统的可用空间增加至参数规定数值之上。
参数manager_files, manager_threshold, manager_sleep:在上述的Cache Manager运行过程中,为了避免长时间占用CPU资源,Cache Manager采用了分批处理的策略。每一次执行最多会删除manger_files(默认100)个文件,运行的最长时间是manager_threshold毫秒(默认200)。两者只要一个条件满足,一次执行就结束。另外每两次执行之间需要睡眠manager_sleep毫秒(默认50)从而释放CPU资源。
参数loader_files, loader_threshold, loader_sleep:NGINX在启动1分钟后会启动Cache Loader进程来生成现有缓存文件对应的内存中的元数据和key值。与Cache Manager进程一样,Cache Loader进程的执行也是分批进行的。在一次执行中最多处理loader_files个文件,或者执行loader_threshold毫秒。两者只要一个条件满足,一次执行就结束。另外每两次执行之间需要睡眠loader_sleep毫秒(默认50)从而释放CPU资源。
Syntax:proxy_cache zone | off;
Default:proxy_cache off;
Context:http, server, location
指令proxy_cache主要是在特定的context下引用指令proxy_cache_path定义的缓存区域。默认值是off,用来关闭从上一层配置中定义的Cache。
与缓存相关的数据结构有ngx_path_t, ngx_http_file_cache_s , ngx_http_file_cahce_sh_t, ngx_http_file_cache_node_t, ngx_http_cache_s, ngx_http_file_cache_header_t等。
它们的主要用途是:
ngx_path_t :目录的路径 (name)、子目录层级定义 (level) 和 可定制 的缓存管理行为 (manager, loader回调函数),对应创建的Cache等。每一条proxy_cache_path指令对应一个ngx_path_t结构。
ngx_http_file_cache_t :表示每条 proxy_cache_path 指令创建的 cache以及inactive等参数。
ngx_http_file_cache_sh_t :维护 LRU 结构用于保存缓存节点以及缓存的当前状态 (是否正在从磁盘加载、当前缓存大小等),同时维护红黑树保存缓存文件的元信息,同时还有共享内存的使用情况等信息。这个结构来自于共享内存区域以便多个worker进程共享使用。
ngx_http_file_cache_node_t :保存磁盘缓存文件在内存中的描述信息,不包含实际内容。这些信息作为索引信息存放在上面ngx_http_file_cache_sh_t结构中的红黑树节点和LRU链表中。
ngx_http_file_cache_header_t :缓存文件系统中文件头结构信息。存储缓存文件的相关信息 (修改时间、缓存 key 的 crc32 值、用于指明 HTTP 响应包头和包体在缓存文件中偏移位置的字段等)。此结构信息也存储在磁盘缓存文件的头部。
ngx_http_cache_t:某一个请求对应的缓存条目的完整信息 (请求使用的缓存 file_cache、缓存条目对应的缓存节点信息 node、缓存文件 file、key 值及其检验 crc32 等等) 都临时保存于此(r->cache) 结构体中,这个结构体中的信息量基本上相当于 ngx_http_file_cache_header_t 和 ngx_http_file_cache_node_t 的总和。
这些数据结构之间的相互关系如下图所示:
它们在系统中的位置和引用关系如下图所示:
模块fastcgi/proxy/scgi/uwsgi在对缓存的使用方式上几乎是一致的。它们分别通过各自的指令来定义和使用缓存功能。我们将以proxy模块为例进行源码分析,此模块对应的指令是proxy_cache_path。
指令proxy_cache_path解析流程:
1.
2.
3.
4.
5.
指令proxy_cache解析流程:
1.
2.
3.
配置解析完毕以后,NGINX的master进程调用函数ngx_start_cache_manager_processes来判断是否需要启动Cache Manager和Cache Loader进程。判断的条件是,如果对应的全局配置的path数组不为空,并且至少有一个数组元素的manager函数指针被设置,则启动Cache Manager进程。如果至少有一个数组元素的loader函数指针被设置,则启动Cache Loader进程。
至此,相关的配置已经得到解析,Cache Manager和Cache Loader进程也被启动起来。
接下来,我们开始分析两个进行进程的运行逻辑。
Cache Manager进程主要负责清理过期缓存以及强制清理部分缓存来释放磁盘空间。
Cache Manager进程的启动和运行逻辑:
1.
2.
3.
static ngx_cache_manager_ctx_t ngx_cache_manager_ctx = {
ngx_cache_manager_process_handler, "cache manager process", 0
};
在这个结构中,第一项是回调函数,就是对应整个进程运行的主函数。第二项是进程的名称,第三项是进程需要延迟多久执行。
4.
5.
6.
函数ngx_cache_manager_process_handler的流程:
1.
2.
函数ngx_http_file_cache_manager的流程:
1.
2.
函数ngx_http_file_cache_expire的流程:
1.
2.
3.
4.
5.
函数ngx_http_file_cache_forced_expire流程:
1.
2.
3.
总结说来,Cache Manager进程的主要逻辑是将距离上次使用时间超过inactive的资源删除。另外,当缓存目录的使用率超过了一定水位(7/8),则通过LRU链表强制删除未使用的老缓存文件信息。同时为了防止过度占用CPU资源,Cache Manger进程采用了分批处理的策略。
Cache Loader进程只在NGINX启动期间运行一次。它一般是在NGINX启动60秒以后开始运行,遍历缓存目录下存在的文件,然后生成内存中文件对应的元数据。当处理完所有的磁盘的文件后,Cache Loader进程就会退出。
如同在Cache Manager进程描述的那样,NGINX的Cache Manager和Cache Loader的进程的入口函数同为ngx_cache_manager_process_cycle,但是对应的传入参数不同。对应Cache Loader进程,其参数如下:
static ngx_cache_manager_ctx_t ngx_cache_loader_ctx = {
ngx_cache_loader_process_handler, "cache loader process", 60000
};
从上面参数我们可以看到,参数60000规定了需要延迟60000ms再运行。函数ngx_cache_loader_process_handler是Cache Loader的主要逻辑。
函数ngx_cache_loader_process_handler流程:
1.
2.
file_handler = ngx_http_file_cache_manage_file 文件节点为普通文件时调用
pre_tree_handler = ngx_http_file_cache_manage_directory 在递归进入目录节点时调用
post_tree_handler = ngx_http_file_cache_noop在递归遍历完目录节点后调用
spec_handler = ngx_http_file_cache_delete_file文件节点为特殊文件时调用
3.
函数ngx_http_file_cache_manage_file流程:
1.
2.
函数ngx_http_file_cache_add_file流程:
1. 首先检查缓存文件的有效性 (文件名长度是否符合规则、文件大小是否满足最小缓存文 件大小要求等)
2. 将文件名中的 32 字节字符摘要转换为 16 字节二制形式。
3. 调用 ngx_http_file_cache_add 函数将此缓存文件的元信息节点加入 ngx_http_file_cache_sh_t 类型的缓存管理机制中。
函数ngx_http_file_cache_add流程:
1. 函数ngx_http_file_cache_add首先通过节点的key值检查文件是否已经在共享内存的红黑树中存在。
2. 如果不存在则生成一个新的节点并且把节点插入到红黑树中。
3. 如果已经存在就先把节点从LRU链表中移除。
4. 再根据inactive参数更新节点的过期时间然后在把节点加入到LRU链表的头部。
到此为止,缓存磁盘文件就被加载到内存中了。
需要注意的是,如果配置文件中指令proxy_cache_path的path或者level参数发生了变化,则在NGINX重新启动时,原来的缓存文件就不会被加载到内存中。
NGINX缓存功能是其最重要的功能之一,其代码实现也比较复杂。在实际使用中,缓存使用涉及到浏览器,NGINX代理服务器,源服务等几方面的行为。而且这几个方面需要根据HTTP协议的规定进行配合,整个过程相对比较复杂。
本文仅仅从原理和整体框架方面对NGINX的缓存功能进行了分析。阐述了Cache Manager和Cache Loader两个管理进程的工作原理。下篇,我们将试着从请求的角度去分析NGINX是如何生成新的缓存文件已经如何使用缓存文件来响应客户端的。
按点赞数排序
按时间排序