先回顾一下配置参数
proxy_cache_path:缓存的存储路径和索引信息;
path 缓存目录的根路径
level=N:N在目录的第几级hash目录缓存数据;
keys_zone=name:size 缓存索引重建进程建立索引时用于存放索引的内存区域名和大小;
interval=time 强制更新缓存时间,规定时间内没有访问则从内存删除,默认10s;
max_size=size 硬盘中缓存数据的上限,由cache manager管理,超出则根据LRU策略删除;
loader_files=number 重建索引时每次加载数据元素的上限,进程递归遍历读取硬盘上的缓存目录和文件,对每个文件在内存中建立索引,每建立一个索引称为加载
一个数据元素,每次遍历时可同时加载多个数据元素,默认100;
loader_sleep=time索引重建进程在两次遍历间的暂停时长,默认50ms;
fastcgi_cache/proxy_cache zone|off:
存放缓存的索引数据,描述的缓存区必须事先由 fastcgi_cache_path 指令定义;
对应的数据结构为ngx_http_file_cache_t,该关键字的解析函数为ngx_http_fastcgi_cache ,该location下所有的upstream request都会引用到该cache zone进行缓存请求(详见下文的ngx_http_file_cache_t介绍);
/* ngx_http_fastcgi_cache */
ngx_http_fastcgi_loc_conf_t *flcf = conf;
...
flcf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0, &ngx_http_fastcgi_module);
上述的配置解析工作完成以后,由 flcf->upstream.cache_zone 就可以引用到已经初始化后的缓存 ( 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);
...
}
数据结构
描述一下cache所涉及的数据结构
1 ngx_http_file_cache_t
每条 proxy_cache_path 指令创建的 cache zone 都由 ngx_http_file_cache_t 结构表示;
ngx_http_file_cache_sh_t * 类型的成员 sh 维护 LRU 队列和红黑树,以及缓存文件的当前状态 (是否正在从磁盘加载、当前缓存大小等);
由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;
ngx_atomic_t cold;
ngx_atomic_t loading;
off_t size;
} ngx_http_file_cache_sh_t;
ngx_http_file_cache_s {
ngx_http_file_cache_sh_t *sh;
ngx_slab_pool_t *shpool;
ngx_path_t *path;
off_t max_size;
size_t bsize;
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;
};
注1:解析conf时,一旦遇到fastcgi_cache_path/proxy_cache_path关键字,则由ngx_http_file_cache_set_slot进行解析,初始化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;
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;
...
2 ngx_path_t
描述每个存储缓存文件的的目录信息
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;
所有的path都由ngx_cycle_t->paths集中管理;
临时目录:响应数据先写入临时文件,然后将其重命名为缓存文件,因此推荐proxy_temp_path和cache目录位于同一文件系统;
由ngx_http_file_cache_set_slot负责初始化
/* 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;
...
3 ngx_http_file_cache_node_t
保存每个缓存文件在内存中的描述信息。
这些信息需要存储于共享内存中,以便多个 worker 进程共享。所以,为了提高利用率,此结构体多个字段使用了位域 (Bit field),同时,缓存 key 中用作查询树键值( ngx_rbtree_key_t ) 的部分字节不再重复存储:
4 ngx_http_file_cache_header_t
每个文件系统中的缓存文件都有固定的存储格式,其中 ngx_http_file_cache_header_t为包头结构,存储缓存文件的相关信息 (修改时间、缓存 key 的 crc32 值、和用于指明
HTTP 响应包头和包体在缓存文件中偏移位置的字段等):
缓存文件格式 [ngx_http_file_cache_header_t]["\nKEY: "][orig_key]["\n"][header][body]
5 ngx_http_cache_t
每个http request对应的缓存条目的完整信息 (请求使用的缓存 file_cache 、缓存条目对应的缓存节点信息 node 、缓存文件 file 、key 值及其检验 crc32 等等) 都临时保存于ngx_http_cache_t (ngx_http_request_t->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;
u_char key[NGX_HTTP_CACHE_KEY_LEN];
ngx_file_uniq_t uniq;
time_t valid_sec;
time_t last_modified;
time_t date;
size_t header_start;
size_t body_start;
off_t length;
off_t fs_size;
ngx_uint_t min_uses;
ngx_uint_t error;
ngx_uint_t valid_msec;
ngx_buf_t *buf;
ngx_http_file_cache_t *file_cache;
ngx_http_file_cache_node_t *node;
ngx_msec_t lock_timeout;
ngx_msec_t wait_time;
ngx_event_t wait_event;
unsigned lock:1;
unsigned waiting:1;
unsigned updated:1;
unsigned updating:1;
unsigned exists:1;
unsigned temp_file:1;
};
加载cache
在nginx启动60秒后,调用loader进程,遍历所有的proxy path,对其下的所有文件创建红黑树(以fastcgi_cache_path为单位,每个对应一个红黑树)
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);
}
对缓存各个缓存目录调用预先指定的loader 回调函数(由ngx_http_file_cache_set_slot初始化ngx_path_t时赋值),针对 fastcgi/proxy 模块,loader为 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
||
ctx->file_handler即ngx_http_file_cache_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
||
ngx_http_file_cache_add_file(ctx, path)
||
ngx_http_file_cache_add(cache, &c);
||
ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node);
管理cache
loader进程只运行一次且完成后立即退出,剩余的维护工作交由cache manager进程;
ngx_cache_manager_process_handler(ngx_event_t *ev)
for( i = 0; i < ngx_cycle->pathes.nelts; i++)
path[i]->manager(path[i]->data);—调用每个磁盘缓存管理对象的manager函数
ngx_add_timer(ev, next * 1000);
static time_t ngx_http_file_cache_manager(void *data)
{
ngx_http_file_cache_t *cache = data;
//先删过期的缓存
next = ngx_http_file_cache_expire(cache);
for ( ;; ) {
//获取最更新的缓存空间的大小
ngx_shmtx_lock(&cache->shpool->mutex);
size = cache->sh->size;
ngx_shmtx_unlock(&cache->shpool->mutex);
//如果空间在指定范围内,不用再删了,return
//...
//如果size超过磁盘的使用空间,即size >= cache->max_size 强制把部分缓存删除,以保证缓存使用的空间在指定范围内
next = ngx_http_file_cache_forced_expire(cache);
}
}
如何使用cache
nignx向上游服务器发出upstream请求时,会先检查是否有可用缓存。
1 ngx_http_upstream_init_request
if (u->conf->cache) { --检查该location是否配置了proxy_cache/fastcgi_cache,如果是则使用缓存(详见上文的ngx_http_fastcgi_cache)
ngx_int_rc;
rc = ngx_http_upstream_cache(r, u);
...
}
2 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` 值 */
--根据配置文件中 ( fastcgi_cache_bypass ) 缓存绕过条件和请求信息,判断是否应该继续尝试使用缓存数据响应该请求:
/* ngx_http_upstream_cache */
switch (ngx_http_test_predicates(r, u->conf->cache_bypass)) {
……
}
--调用 ngx_http_file_cache_open 函数查找是否有对应的有效缓存数据
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 请求处理流程。
3 ngx_http_file_cache_open
函数 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 函数尝试打开并获取文件缓存信息,调用ngx_open_file_lookup从ngx_open_file_cache_t->rbtree查找对应文件名的hash值;
创建用于存储 缓存文件头 的临时缓冲区 c->buf 。
调用 ngx_http_file_cache_read 函数读取缓存文件头并进行有效性验证。
注2:nginx只负责将缓存文件的metadata加载到共享内存中,并采用红黑树和LRU队列管理,前者用于快速查找和超时管理,后者则用于维护删除;
而所谓的索引,其实是提取proxy_cache_key定义的字段并进行MD5运算,将计算结果当作缓存文件的名字,常用格式为$host$uri$is_args$args,确保只要cache_key相同则为相同的请求;
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/15480802/viewspace-1421409/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/15480802/viewspace-1421409/