代码分析版本为 1.4.4,并且所有讨论都是在 Nginx 启用 HTTP Cache 机制 (定义了 NGX_HTTP_CACHE
宏) 的前提下进行。同时,由于 fastcgi/proxy 模块在对 缓存的使用上基本一致,本篇上下文设定为 fastcgi 模块。
Nginx manual
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 the loader_threshold
paramter. Between iterations, a pause configured by the loader_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 by ngx_add_path
while parsing conf
ngx_create_paths
- called in ngx_init_cycle
, calls ngx_create_dir
to mkdir
.
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
来讲,其 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
来讲,其 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
的数据发送流程关系紧密,本篇就不再分析了。
从缓存树中的节点信息得到完整的缓存 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
Category:Nginx Tagged:c/c++ devel nginx notes