代码分析版本为 1.4.4,并且所有讨论都是在 Nginx 启用 HTTP Cache 机制
(定义了 NGX_HTTP_CACHE
宏) 的前提下进行。同时,由于 fastcgi/proxy 模块在对
缓存的使用上基本一致,本篇上下文设定为 fastcgi 模块。
Both the key and file name in a cache are a result of applying the MD5 function to the proxied URL.
A cached response is first written to a temporary file, and then the file is renamed...It is thus recommended that for any given location both cache and a directory holding temporary files (
proxy_temp_path
) are put on the same file system....all active keys and information about data are stored in a shared memory zone...Cached data that are not accessed during the time specified by the
inactive
parameter get removed from the cache regardless of their freshness.The special "cache manager" process monitors the maximum cache size set by the
max_size
paramater. When this size is exceeded, it removes the least recently used data.A minute after the start the special "cache loader" process is activated. It loads information about previously cached data stored on file system into a cache zone. * The loading is done in iterations. * During one iteration no more than
loader_files
items are loaded. * The duration of one iteration is limited by theloader_threshold
paramter. * Between iterations, a pause configured by theloader_sleep
parameter is made.
相关结构和配置指令
-
proxy_temp_path
- set the path for storing temporary files with data received from proxied servers. Up to three-level subdirectory hierarchy can be used underneath the specified directory. -
ngx_path_t
- directory 的路径 (name
)、子目录层级定义 (level
) 和 可定制 的缓存管理行为 (manager
,loader
)。typedef struct { ngx_str_t name; size_t level[3]; /* 至多3层, 每层最多2个字符 */ ngx_path_manager_pt manager; ngx_path_loader_pt loader; void *data; u_char *conf_file; /* NULL值表示默认路径 */ ngx_uint_t line; } ngx_path_t;
-
ngx_cycle_t::paths
- all dirs added byngx_add_path
while parsing conf -
ngx_create_paths
- called inngx_init_cycle
, callsngx_create_dir
tomkdir
. -
ngx_add_path
- 不同调用者生成不同类型的ngx_path_t
对象,并调用此函数将它 加入ngx_cycle_t::paths
数组。-
用于设置临时目录的指令 (比如
fastcgi_temp_path
,proxy_temp_path
等) 调用ngx_http_file_cache_set_slot
函数读取临时目录配置,这类目录不需要缓 存管理进程进行维护:path->manager = NULL; path->loader = NULL;
-
用于设置缓存文件目录的指令 (比如
fastcgi_cache_path
,proxy_cache_path
等) 调用ngx_http_file_cache_set_slot
函数读取缓存目录配置,并设置对此缓 存目录进行管理的方法:path->manager = ngx_http_file_cache_manager; path->loader = ngx_http_file_cache_loader; path->data = cache;
-
-
每条
proxy_cache_path
指令创建的 cache 都由ngx_http_file_cache_t
结构表 示。其中,ngx_http_file_cache_sh_t *
类型的成员sh
维护 LRU 结构用于保存缓 存节点 和 缓存的当前状态 (是否正在从磁盘加载、当前缓存大小等);成员shpool
是 用于管理共享内存的 slab allocator,所有缓存节点占用空间都由它进行分配;其它成 员用于保存此缓存的相关配置信息。缓存结构体由设定的ngx_shm_zone_t
初始化函数ngx_http_file_cache_init
初始化 (在ngx_init_cycle
中先调用ngx_http_init_zone_pool
函数对共享内存进行初始化,然后调用ngx_http_file_cache_init
函数对缓存和成员进行初始化)。typedef struct { ngx_rbtree_t rbtree; ngx_rbtree_node_t sentinel; ngx_queue_t queue; /* inactive queue */ ngx_atomic_t cold; /* 缓存是否可用 (加载完毕) */ ngx_atomic_t *loading; /* 是否正在被 loader 进程加载 */ off_t size; /* 初始化为 0 */ } ngx_http_file_cache_sh_t; struct ngx_http_file_cache_s { ngx_http_file_cache_sh_t *sh; /* from slab allocator */ ngx_slab_pool_t *shpool; /* shm_zone->shm.addr */ ngx_path_t *path; off_t max_size; /* how many blocks */ size_t bsize; /* fs block size */ time_t inactive; ngx_uint_t files; ngx_uint_t loader_files; ngx_msec_t last; ngx_msec_t loader_sleep; ngx_msec_t loader_threshold; ngx_shm_zone_t *shm_zone; /* .init = ngx_http_file_cache_init /* */ .data = cache */ };
-
结构体
ngx_http_file_cache_node_t
保存磁盘缓存文件在内存中的描述信息。这些 信息需要存储于共享内存中,以便多个worker
进程共享。所以,为了提高利用率,此 结构体多个字段使用了位域 (Bit field),同时,缓存 key 中用作查询树键值 (ngx_rbtree_key_t
) 的部分字节不再重复存储:typedef struct { ngx_rbtree_node_t node; /* 缓存查询树的节点 */ ngx_queue_t queue; /* LRU 队列中的节点 */ u_char key[NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t)]; unsigned count:20; /* 引用计数 */ unsigned uses:10; /* 被请求查询到的次数 */ unsigned valid_msec:10; unsigned error:10; unsigned exists:1; unsigned updating:1; unsigned deleting:1; /* 正在被清理中 */ /* 11 unused bits */ ngx_file_uniq_t uniq; time_t expire; time_t valid_sec; size_t body_start; off_t fs_size; } ngx_http_file_cache_node_t;
-
关键字段详细含义:
-
error
- 当后端响应码>= NGX_HTTP_SPECIAL_RESPONSE
, 并且打开了fastcgi_intercept_errors
配置,同时fastcgi_cache_valid
配置指令和error_page
配置指令也对该响应码做了设定 的情部下,该字段记录响应码, 并列的valid_sec
字段记录该响应码的持续时间。这种error
节点并不对 应实际的缓存文件。 -
exists
- 该缓存节点是否有对应的缓存文件。新创建的缓存节点或者过期的error
节点 (参见error
字段,当error
不等于 0 时,Nginx 随后也不 会再关心该节点的exists
字段值) 该字段值为 0。当正常节点 (error
等 于 0) 的exists
为 0 时,进入 cache lock 模式。 -
valid_sec
,valid_msec
- 缓存内容的过期时间,缓存内容过期后被查询 时会由ngx_http_file_cache_read
返回NGX_HTTP_CACHE_STALE
,然后由fastcgi_cache_use_stale
配置指令决定是否及何种情况下使用过期内容。 -
expires
- 缓存节点的可回收时间 (附带缓存内容)。 -
updating
- 缓存内容过期,某个请求正在获取有效的后端响应并更新此缓存 节点。参见ngx_http_cache_t::updating
。
-
-
-
每个文件系统中的缓存文件都有固定的存储格式,其中
ngx_http_file_cache_header_t
为包头结构,存储缓存文件的相关信息 (修改时间、缓存 key 的 crc32 值、和用于指明 HTTP 响应包头和包体在缓存文件中偏移位置的字段等):typedef struct { time_t valid_sec; time_t last_modified; time_t date; uint32_t crc32; u_short valid_msec; u_short header_start; u_short body_start; } ngx_http_file_cache_header_t;
- 缓存文件格式
[ngx_http_file_cache_header_t]["\nKEY: "][orig_key]["\n"][header][body]
- 缓存文件格式
-
请求对应的缓存条目的完整信息 (请求使用的缓存
file_cache
、缓存条目对应的缓存 节点信息node
、缓存文件file
、key 值及其检验crc32
等等) 都临时保存于ngx_http_cache_t
(r->cache
) 结构体中,这个结构体中的信息量基本上相当于ngx_http_file_cache_header_t
和ngx_http_file_cache_node_t
的总和:struct ngx_http_cache_s { ngx_file_t file; /* 缓存文件描述结构体 */ ngx_array_t keys; uint32_t crc32; /* crc32 of literal key */ u_char key[NGX_HTTP_CACHE_KEY_LEN]; /* md5sum of literal key */ ngx_file_uniq_t uniq; time_t valid_sec; /* from/to fcn->valid_sec */ time_t last_modified; time_t date; size_t header_start; /* offset in cache file */ size_t body_start; /* offset in cache file */ off_t length; off_t fs_size; ngx_uint_t min_uses; ngx_uint_t error; /* from/to fcn->error */ ngx_uint_t valid_msec; /* from/to fcn->valid_msec */ ngx_buf_t *buf; /* 存储缓存文件头 */ ngx_http_file_cache_t *file_cache; /* worker-shared cache */ ngx_http_file_cache_node_t *node; /* cache node by `key` */ ngx_msec_t lock_timeout; ngx_msec_t wait_time; ngx_event_t wait_event; unsigned lock:1; /* use cache lock or not */ unsigned waiting:1; /* wait by cache_lock */ unsigned updated:1; unsigned updating:1; unsigned exists:1; unsigned temp_file:1; };
-
关键字段详细含义:
-
updating
- 缓存内容己过期,并且当前请求正在获取有效的后端响应并更新 此缓存节点。参见ngx_http_file_cache_node:updating
。 -
waiting
- 缓存内容己过期,当前请求正等待其它请求更新此缓存节点。
-
-
配置
fastcgi_cache_path
指令用于 “声明” 一个全新的缓存,指令参数指定缓存文件在文件
系统中的路径、共享内存的名字、共享内存大小等信息。
这个配置指令处理函数 ngx_http_file_cache_set_slot
完成以下工作:
-
全局内存池 (
cycle->pool
) 中创建ngx_http_file_cache_t
对象,并根据配置参数 对其进行初始化:/* ngx_http_file_cache_set_slot */ cache = ngx_pcalloc(cf->pool, sizeof(ngx_http_file_cache_t); ... cache->path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t)); ... cache->path->manager = ngx_http_file_cache_manager; cache->path->loader = ngx_http_file_cache_loader; cache->path->data = cache; ... cache->loader_files = loader_files;
-
调用
ngx_add_path
将缓存文件路径加入全局路径数组 (cycle->paths
) 中,这些路 径在配置解析完毕后,由 Nginx 检查并创建 (ngx_init_cycle
调用ngx_create_paths
函数完成)。 -
调用
ngx_shared_memory_add
定义共存内存,在配置解析完毕后,由 Nginx 完成共享 内存创建和初始化工作。/* ngx_http_file_cache_set_slot */ cache->shm_zone = ngx_shared_memory_add(cf, &name, size, cmd->post); ... cache->shm_zone->init = ngx_http_file_cache_init; cache->shm_zone->data = cache; ... /* ngx_init_cycle */ ngx_shm_alloc(&shm_zone[i].shm); ngx_init_zone_pool(cycle, shm_zone[i]); shm_zone[i].init(&shm_zone[i], NULL); /* ngx_http_file_cache_init */ /* ngx_http_file_cache_init */ cache->shpool = (ngx_slab_pool_t) shm_zone->shm.addr; ... cache->sh = ngx_slab_alloc(cache->shpool, sizeof(ngx_http_file_cache_sh_t)); cache->shpool->data = cache->sh; ngx_rbtree_init(&cache->sh->rbtree, &cache->sh->sentinel, ngx_http-file_cache_rbtree_insert_value); ngx_queue_init(&cache->sh->queue);
fastcgi_cache
指令指定了在当前作用域中使用哪个缓存维护缓存条目,参数对应的缓
存必须事先由 fastcgi_cache_path
指令定义。
这个配置指令处理函数 ngx_http_fastcgi_cache
负责初始化
ngx_http_upstream_conf_t::cache
字段:
/* ngx_http_fastcgi_cache */
ngx_http_fastcgi_loc_conf_t *flcf = conf;
...
flcf->upstream.cache = ngx_shared_memory_add(cf, &value[1], 0,
&ngx_http_fastcgi_module);
上述的配置解析工作完成以后,由 flcf->upstream.cache
就可以引用到已经初始化后
的缓存 (ngx_http_file_cache_t
类型结构体) 了。
/* ngx_http_fastcgi_handler */
u = r->upstream;
...
u->conf = &flcf->upstream;
/* ngx_http_upstream_init_request */
if (u->conf->cache) {
...
rc = ngx_http_upstream_cache(r, u);
...
}
/* ngx_http_upstream_cache */
ngx_http_cache_t *c;
c = r->cache;
if (c == NULL) {
...
ngx_http_file_cache_new(r);
...
c = r->cache;
c->min_uses = u->conf->cache_min_uses;
c->body_start = u->conf->buffer_size;
c->file_cache = u->conf->cache->data;
...
}
加载 & 管理
Nginx 进程启动时,会试图从缓存对应的文件系统路径下的文件读取必要数据,然后重建
缓存的内存结构。这个过程由 cache loader
进程完成。
同时,常驻 Nginx 子进程 cache manager
负责维护缓存文件,定期清理过期的缓存条
目。同时,它也会检查缓存目录总大小,如果超出配置限制的话,强制清理掉最老的缓存
条目。
两个进程均在 Nginx 主进程初始化完成后,由 ngx_start_cache_manager_processes
函数创建并开始运行:
static void
ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn)
{
...
manager = 0;
loader = 0;
path = ngx_cycle->paths.elts;
for (i = 0; i < ngx_cycle->paths.nelts; i++) {
if (path[i]->manager) {
manager = 1;
}
if (path[i]->loader) {
loader = 1;
}
}
if (manager == 0) {
return;
}
ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
&ngx_cache_manager_ctx, "cache manager process",
respawn ? NGX_PROCESS_JUST_RESPAWN : NGX_PROCESS_RESPAWN);
...
if (loader == 0) {
return;
}
ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
&ngx_cache_loader_ctx, "cache loader process",
respawn ? NGX_PROCESS_JUST_RESPAWN : NGX_PROCESS_RESPAWN);
...
}
对上述代码的补充说明:
-
cache manager
进程和cache loader
进程只在配置文件中定义了缓存目录后,才 会被创建并运行。 -
当前版本 (1.4.4) 中,
loader
和manager
变量的值一致,要么同为 0,要么同为 1。 -
cache manager
进程和cache loader
进程的入口函数一样,区别在于入口函数的参 数不同。这个参数决定了ngx_cache_manager_process_cycle
的行为。static void ngx_cache_manager_process_cycle(ngx_cycle_t *cycle, void *data) { ngx_cache_manager_ctx_t *ctx = data; ngx_event_t ev; ... ev.handler = ctx->handler; ev.data = ident; ... ngx_add_timer(&ev, ctx->delay); for ( ;; ) { ... ngx_process_events_and_timers(cycle); } }
cache loader
对 cache loader
来讲,其 ngx_cache_manager_ctx_t
的值为 ngx_cache_loader_ctx
:
static ngx_cache_manager_ctx_t ngx_cache_loader_ctx = {
ngx_cache_loader_process_handler, "cache loader process", 60000
};
也就是说,cache loader
进程在 60000 ms
后才会开始第一次执行,并且执行回调函
数 ngx_cache_loader_process_handler
:
static void
ngx_cache_loader_process_handler(ngx_event_t *ev)
{
...
cycle = (ngx_cycle_t *) ngx_cycle;
path = cycle->paths.elts;
for (i = 0; i < cycle->paths.nelts; i++) {
...
if (path[i]->loader) {
path[i]->loader(path[i]->data);
ngx_time_update();
}
}
exit(0);
}
可以看到,ngx_cache_loader_process_handler
只是对缓存各个缓存目录调用预先指定
的 loader
回调函数。所有 loader
函数执行完后,调用 exit(0)
函数销毁
cache loader
进程。
针对 fastcgi/proxy
模块,loader
回调函数被 ngx_http_file_cache_set_slot
函数设置为 ngx_http_file_cache_loader
,此函数的主要逻辑如下:
static void
ngx_http_file_cache_loader(void *data)
{
ngx_http_file_cache_t *cache = data;
ngx_tree_ctx_t tree;
tree.init_handler = NULL;
tree.file_handler = ngx_http_file_cache_manage_file;
tree.pre_tree_handler = ngx_http_file_cache_noop;
tree.post_tree_handler = ngx_http_file_cache_noop;
tree.spec_handler = ngx_http_file_cache_delete_file;
tree.data = cache;
...
ngx_walk_tree(&tree, &cache->path->name);
...
}
对上述代码的补充说明:
-
使用
ngx_walk_tree
递归遍历缓存目录,并对不同类型的文件根据回调函数做不同的 处理。 -
ngx_tree_ctx_t
类型中回调函数成员被调用的时机:file_handler
- 文件节点为普通文件时调用pre_tree_handler
- 在递归进入目录节点时调用post_tree_handler
- 在递归遍历完目录节点后调用spec_handler
- 文件节点为特殊文件时调用
ngx_http_file_cache_noop
函数并未执行任何操作;ngx_http_file_cache_delete_file
函数将会删除缓存目录中的特殊文件;ngx_http_file_cache_manage_file
将缓存文件
信息存入缓存中。
static ngx_int_t
ngx_http_file_cache_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
{
ngx_msec_t elapsed;
ngx_http_file_cache_t *cache;
if (ngx_http_file_cache_add_file(ctx, path) != NGX_OK) {
(void) ngx_http_file_cache_delete_file(ctx, path);
}
if (++cache->files >= cache->loader_files) {
ngx_http_file_cache_loader_sleep(cache);
} else {
ngx_time_update();
elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));
if (elapsed >= cache->loader_threshold) {
ngx_http_file_cache_loader_sleep(cache);
}
}
return (ngx_quit || ngx_terminate) ? NGX_ABORT : NGX_OK;
}
对上述代码的补充说明:
-
ngx_http_file_cache_nanage_file
将缓存文件信息放到缓存中,它完成的主要工作 有:-
检查缓存文件的有效性 (文件名长度是否符合规则、文件大小是否满足最小缓存文 件大小要求等)
-
将文件名中的 32 字节字符摘要转换为 16 字节二制形式。
-
调用
ngx_http_file_cache_add
函数将此节点加入ngx_http_file_cache_sh_t
类型的缓存管理机制中。
-
-
同时,根据配置控制缓存的读取速度 (
loader_files
和loader_threshold
),以便 在缓存文件很多的情况下降低初次启动时对系统资源的消耗。
到此为止,缓存磁盘文件就被加载到内存中了。
cache manager
对 cache manager
来讲,其 ngx_cache_manager_ctx_t
的值为 ngx_cache_manager_ctx
:
static ngx_cache_manager_ctx_t ngx_cache_manager_ctx = {
ngx_cache_manager_process_handler, "cache manager process", 0
};
也就是说,cache manager
进程在被创建后马上开始执行
ngx_cache_manager_process_handler
回调函数。同时,根据缓存中最快过期的节点时
间设定下一次调用此回调函数的时间:
static void
ngx_cache_manager_process_handler(ngx_event_t *ev)
{
...
next = 60 * 60
path = ngx_cycle->paths.elts;
for (i = 0; i < ngx_cycle->paths.nelts; i++) {
if (path[i]->manager) {
n = path[i]->manager(path[i]->data);
next = (n < next) ? n : next;
ngx_time_update();
}
}
if (next == 0) {
next = 1;
}
ngx_add_timer(ev, next * 1000);
}
对上述代码的补充说明:
-
和个缓存设计的
manager
回调函数需要返回它里面最近将要过期的缓存条目距当前时 间点的间隔时长。 -
cache manager
进程检查缓存条目有效性的间隔最长为 1 个小时。并且,为了避免 占用过多 CPU,cache manger
最短检查间隔保证为 1 秒。
针对 fastcgi/proxy
模块,manager
回调函数被 ngx_http_file_cache_set_slot
函数设置为 ngx_http_file_cache_manager
,此函数的主要完成以下工作:
-
调用
ngx_http_file_cache_expire
函数清除过期缓存条目 (删除其占用的共享内存 和对应的磁盘文件)。 -
检查缓存磁盘目录是否超过设定大小限制。如果超限,调用函数
ngx_http_file_cache_forced_expire
从inactive queue
队尾开始扫描,直到找到 可以被清理的当前未使用节点 (fcn->count == 0
且不论它是否过期) 或者查找了 20 个 节点后仍未找到符合条件的节点。 -
两个函数
ngx_http_file_cache_expire
和ngx_http_file_cache_forced_expire
均使用了malloc
动态分配用于存储缓存文件路径的内存区域。
缓存文件清理过程均调用了 ngx_http_file_cache_delete
函数,并且调用它的前提条
件是当前函数已经获得了 cache->shpool->mutex
锁,同时,当前缓存节点的引用计数
为 0。下面来看一下该函数的具体逻辑:
static void
ngx_http_file_cache_delete(ngx_http_file_cache_t *cache, ngx_queue_t *q,
u_char *name)
{
...
ngx_http_file_cache_node_t *fcn;
fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
if (fcn->exists) {
cache->sh->size -= fcn->fs_size;
...
fcn->count++;
fcn->deleting = 1;
ngx_shmtx_unlock(&cache->shpool->mutex);
...
ngx_delete_file(name);
ngx_shmtx_lock(&cache->shpool->mutex);
fcn->count--;
fcn->deleting = 0;
}
if (fcn->count == 0) {
ngx_queue_remove(q);
ngx_rbtree_delete(&cache->sh->rbtree, &fcn->node);
ngx_slab_free_locked(cache->shpool, fcn);
}
}
对上述代码的补充说明: / FIXME /
-
由于文件删除操作 (
ngx_delete_file
) 可能发生阻塞,所以进行这个操作期间,函数 将缓存锁先释放掉,以免其它进程因为等待这个锁而阻塞。 -
count
加 1 以避免其它进程再次尝试清理此节点 (当前代码中还不会有这种情况发生)。 -
deleting
标识此缓存节点正在被删除,其它函数或进程因视其为无效节点。 -
在缓存锁处于释放状态的过程中 (磁盘文件被删除过程中),依然可能被请求正常访问到。 这种情形下:要么因为请求造成的缓存文件打开增加了文件的引用,造成文件不会被真正 删除;要么请求打开文件时发生错误,请求将后端响应重新填充到缓存文件中。
至此,缓存文件加载和管理部分代码分析完结了。
使用
下面进入到运行时状态,再来分析一下在正常请求处理过程中,一个请求是怎样使用缓存
数据的 (同时,在分析代码过程中还需要注意的是, Nginx 允许 cache loader
进程在
加载缓存文件信息的同时响应这些对缓存的请求,这个特性用于提高 Nginx 本身的可用性)。
/* ngx_http_upstream_init_request */
if (u->conf->cache) {
ngx_int_rc;
rc = ngx_http_upstream_cache(r, u);
...
}
ngx_http_upstream_cache
就这样,所有请求缓存使用的尝试,都是通过 ngx_http_upstream_cache
函数开始的。
这个函数主要完成了以下几个功能:
-
如果还未给当前请求分配缓存相关结构体 (
ngx_http_cache_t
) 时,创建此类型字段 (r->cache
) 并初始化:/* ngx_http_upstream_cache */ ngx_http_file_cache_new(r); u->create_key(r); /* ngx_http_fastcgi_create_key 将 `fastcgi_cache_key` 配置指令定义的缓存 key 根据请求信息进行取值 */ ngx_http_file_cache_create_key(r); /* 生成 md5sum(key) 和 crc32(key) 并计算 `c->header_start` 值 */ u->cacheable = 1; /* 默认所有请求的响应结果都是可被缓存的 */ ... c = r->cache; c->min_uses = u->conf->cache_min_uses; c->body_start = u->conf->buffer_size; /* 后续会进行调整 */ c->file_cache = u->conf->cache->data; c->lock = u->conf->cache_lock; c->lock_timeout = u->conf->cache_lock_timeout; u->cache_status = NGX_HTTP_CACHE_MISS;
-
根据配置文件中 (
fastcgi_cache_bypass
) 缓存绕过条件和请求信息,判断是否应该 继续尝试使用缓存数据响应该请求:/* ngx_http_upstream_cache */ switch (ngx_http_test_predicates(r, u->conf->cache_bypass)) { case NGX_ERROR: return NGX_ERROR; case NGX_DECLINED: u->cache_status = NGX_HTTP_CACHE_BYPASS; return NGX_DECLINED; default: /* NGX_OK */ break; }
-
调用
ngx_http_file_cache_open
函数查找是否有对应的有效缓存数据 (该函数的其它 返回值和对应含义见下一节):/* ngx_http_upstream_cache */ rc = ngx_http_file_cache_open(r); switch (rc) { case NGX_HTTP_CACHE_UPDATING: ... case NGX_OK: u->cache_status = NGX_HTTP_CACHE_HIT; }
-
缓存命中后,调用
ngx_http_upstream_cache_send
函数发送缓存数据给请求者;缓 存未命中时,继续正常 upstream 请求处理流程。
ngx_http_file_cache_open
ngx_http_file_cache_open
函数负责缓存文件定位、缓存文件打开和校验等操作,其返
回值及对应含义,以及其调用者 ngx_http_upstream_cache
对应的行为总结如下:
NGX_OK
- 缓存正常命中
- 设置 `cache_status` 为 `NGX_HTTP_CACHE_HIT`,然后向客户端发送缓存内容
NGX_HTTP_CACHE_STALE
- 缓存内容过期,当前请求需要向后端请求新的响应数据。
- 设置 `cache_status` 为 `NGX_HTTP_CACHE_EXPIRED`,并返回 `NGX_DECLINED`
以继续请求处理 (`r->cached = 0; c->valid_sec = 0`)。
NGX_HTTP_CACHE_UPDATING
- 缓存内容过期,同时己有同样使用该缓存节点的其它请求正在请求新的响应数据。
- 如果 fastcgi_cache_use_stale 启用了 "updating",设置 `cache_status` 为
`NGX_HTTP_CACHE_UPDATING`,然后向客户端发送过期缓存内容。否则,将返回
值重设为 `NGX_HTTP_CACHE_STALE`。
NGX_HTTP_CACHE_SCARCE
- 因缓存节点被查询次数还未达 `min_uses`,对此请求禁用缓存机制
- 继续请求处理,但是不再缓存其响应数据 (`u->cacheable = 0`)。
NGX_DECLINED
- 缓存内容因为不存在 (`c->exists == 0`)、缓存内容未通过校验、或者当前请
求正在更新缓存等原因,暂时无法使用缓存。
- 继续请求处理,并尝试对其响应数据进行缓存。
NGX_AGAIN
- 缓存内容过期,并且当前缓存节点正在被其它请求更新,或者 还未能从缓存文
件中读到足够的数据 (aio 模块下)。
- 返回 `NGX_BUSY`,Nginx 会再次尝试读取缓存。
NGX_ERROR
- 内存相关、文件系统等系统错误。
- 返回 `NGX_ERROR`,Nginx 会调用 `ngx_http_finalize_request` 终止此请求。
NGX_HTTP_SPECIAL_RESPONSE
- 打开 `fastcgi_intercept_errors` 配置情况下,直接返回缓存的错误码。
- 设置 `cache_status` 为 `NGX_HTTP_CACHE_HIT` 并返回错误码。
函数 ngx_http_file_cache_open
的主要逻辑如下:
-
第一次根据请求信息生成的 key 查找对应缓存节点时,先注册一下请求内存池级别的清 理函数:
if (c->node == NULL) { cln = ngx_pool_cleanup_add(r->pool, 0); ... cln->handler = ngx_http_file_cache_cleanup; cln->data = c; }
-
调用
ngx_http_file_cache_exists
函数,使用ngx_http_file_cache_lookup
函 数以c->key
为查找条件从缓存中查找缓存节点:-
如果找到了对应
c->key
的缓存节点:-
如果该请求第一次使用此缓存节点,则增加相关引用和使用次数,继续下面条 件判断;
-
如果
fastcgi_cache_valid
配置指令对此节点过期时间做了特殊设定,检查 节点是否过期。如果过期,重置节点,并返回NGX_DECLINED
; 如果未过期,返 回NGX_OK
; -
如果缓存文件存在 或者 缓存节点被使用次数超过
fastcgi_cache_min_uses
配置值,置c->error = fcn->error
,并返回NGX_OK
; -
条件 2, 3 都不满足时,此次查找失败,返回
NGX_AGAIN
。
-
-
如果未找到对应
c->key
的缓存节点,创建并创始化新的缓存节点,同时返回NGX_DECLINED
。
-
-
调用
ngx_http_file_cache_name
函数组合缓存文件完整文件名。 - 调用
ngx_open_cached_file
函数尝试打开并获取文件缓存信息。 - 创建用于存储 缓存文件头 的临时缓冲区
c->buf
。 - 调用
ngx_http_file_cache_read
函数读取缓存文件头并进行有效性验证。
缓存读取涉及的基本函数大致分析完毕了,其中涉及 cache_lock
等诸多细节因为篇幅
原因,暂时不做分析。接下来,分析一下接到响应后,Nginx 是怎么决定是否对其进行缓
存和如何缓存的。
生成
接收上游请求并准备向下游发送响应之前,upstream
模块判断是否需要对响应进行缓存
并设置相关信息。这个过程主要在函数 ngx_http_upstream_send_response
中完成。
/* ngx_http_upstream_send_response */
switch (ngx_http_test_predicates(r, u->conf->no_cache)) {
...
case NGX_DECLINED:
u->cacheable = 0;
break;
default: /* NGX_OK */
if (u->cache_status == NGX_HTTP_CACHE_BYPASS) {
...
ngx_http_file_cache_create(r);
...
}
break;
}
if (u->cacheable) {
...
valid = r->cache->valid_sec;
if (valid == 0) {
valid = ngx_http_file_cache_valid(u->conf->cache_valid,
u->headers_in.status_n);
if (valid) {
r->cache->valid_sec = now + valid;
}
}
if (valid) {
...
r->cache->body_start = (u_short) (u->buffer.pos - u->buffer.start);
ngx_http_file_cache_set_header(r, u->buffer.start);
} else {
u->cacheable = 0;
...
}
}
if (u->cacheable == 0 && r->cache) {
ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
}
对上述代码的补充说明:
-
u->cacheable
用于控制是否对响应进行缓存操作。其默认值为 1,在缓存读取过程中 可因某些条件将其设置为 0,即不在缓存该请求的响应数据。 -
fastcgi_no_cache
配置指令可以使upstream
模块不再缓存满足既定条件的请求得 到的响应。由上面ngx_http_test_predicates
函数及相关代码完成。 -
fastcgi_cache_bypass
配置指令可以使满足既定条件的请求绕过缓存数据,但是这些 请求的响应数据依然可以被upstream
模块缓存。 -
缓存内容的有效时间由
fastcgi_cache_valid
配置指令设置,并且未经该指令设置的 响应数据是不会被upstream
模块缓存的。 -
upstream
模块在申请u->buffer
空间时,已经预先为缓存文件包头分配了空间, 所以可以直接调用ngx_http_file_cache_set_header
在此空间中初始化缓存文件包头:/* ngx_http_upstream_process_header */ if (r->cache) { u->buffer.pos += r->cache->header_start; u->buffer.last = u->buffer.pos; }
-
如果响应最终不需要被缓存,调用
ngx_http_file_cache_free
释放相关资源.
缓存文件的生成及其它细节和 upstream
的数据发送流程关系紧密,本篇就不再分析了。
Misc
-
从缓存树中的节点信息得到完整的缓存 key 的步骤:
u_char *p; u_char key[2 * NGX_HTTP_CACHE_KEY_LEN]; p = ngx_hex_dump(key, (u_char *) &fcn->node.key, sizeof(ngx_rbtree_key_t)); len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t); (void) ngx_hex_dump(p, fcn->key, len);
-
生成缓存文件完整路径
void ngx_create_hashed_filename(ngx_path_t *path, u_char *file, size_t len) { size_t i, level; ngx_uint_t n; i = path->name.len + 1; file[path->name.len + path->len] = '/'; for (n = 0; n < 3; n++) { level = path->level[n]; if (level == 0) { break; } len -= level; file[i - 1] = '/'; ngx_memcpy(&file[i], &file[len], level); i += level + 1; } } | i | path->name.len + path->len v v ------------------------------------------------------------------ /path/to/cache /10/89 / xxxxxxxxxXXXXXXXXXXXXXXXXXX8910 ------------------------------------------------------------------ ^ ^ ^ | path->name.len | path->len | 32
Comments
不要轻轻地离开我,请留下点什么...