原文链接http://blog.csdn.net/lvxin_1986/article/details/7744079
cache loader process进程分析
本文主要对nginx的cache loader process进程进行分析,并进行记录。
一、揭开cache loader process神秘面纱
在nginx启动1分钟之后,会启动一个名为cache loader process的进程,该进程运行了一段时间之后,该进程就会自动退出。
在该进程运行期间主要做了以下事情:遍历配置文件中proxy_cache_path命令指定的路径中的所有的缓存文件,并且针对遍历到的各个缓存文件的MD5编码先遍历红黑树和相应的ngx_http_file_cache_node_t节点,如果不存在就创建新的ngx_http_file_cache_node_t,并将该对象中的rbnode和queue分别插入到红黑树和过期队列中;如果存在,则更新相应的属性。
通过上述操作,完成根据缓存文件进行索引数据的重建工作。
二、刨根究底
下面对该过程进行详细研究:
1. 结构先行
(1)ngx_path_t:保存了当前cache的一些信息,包含manager和loader函数指针和传入数据地址。
typedef struct {
ngx_str_t name;
size_t len;
size_t level[3];
ngx_path_manager_pt manager;
//cache loader的函数指针,在运行cache loader进程的时候
//通过该函数完成索引元数据的重建。
ngx_path_loader_pt loader;
void *data;
u_char *conf_file;
ngx_uint_t line;
} ngx_path_t;
(2)ngx_http_file_cache_node_t:一个缓存数据对应一个ngx_http_file_cache_node_t结构。
typedef struct {
ngx_rbtree_node_t node;//缓存文件对应的红黑树节点
ngx_queue_t queue;//队列
//cache_key 12位=16位cache_key-4位rbt_key
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;//是否存在对应的cache文件
unsigned updating:1;//是否在更新
/* 12unused bits */
ngx_file_uniq_t uniq;//文件的uniq
time_t expire;//失效时间
time_t valid_sec;//max-age?
size_t body_start;//body 起始位置
off_t fs_size;//文件大小
} ngx_http_file_cache_node_t;
(3)ngx_cache_manager_ctx_t
typedef struct {
ngx_event_handler_pt handler; //上下文执行方法
char *name;
ngx_msec_t delay; //延迟多长时间执行handler
}ngx_cache_manager_ctx_t;
2. 代码分析
(1)首先初始化缓存管理上下文:
//ngx_cache_loader_ctx会用来注册超时事件对象,然后加入到事件和超时树中.
//ngx_cache_loader_process_handler是超时的处理方法,60000是设定的超时时间, //表示60000毫秒=60秒=1分钟.
static ngx_cache_manager_ctx_t ngx_cache_loader_ctx = {
ngx_cache_loader_process_handler, "cache loader process",60000
};
(2)在master执行代码中创建cache 管理进程
void
ngx_master_process_cycle(ngx_cycle_t *cycle)
{
…..
…..
//启动worker进程
ngx_start_worker_processes(cycle,ccf->worker_processes,
NGX_PROCESS_RESPAWN);
//启动cache manager进程
ngx_start_cache_manager_processes(cycle,0);
…..
…..
}
(3)启动cache loader进程
static void
ngx_start_cache_manager_processes(ngx_cycle_t*cycle, ngx_uint_t respawn)
{
…..
…..
//启动cache manager 进程
//ngx_cache_manager_process_cycle是进程执行方法体, ngx_cache_manager_ctx
//是方法体的传入参数
//ngx_cache_manager_ctx是ngx_cache_manager_ctx-ngx_event_handler_pt将
//会赋值给event-handler用于处理定时事件
ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
&ngx_cache_manager_ctx, "cache manager process",
respawn ?NGX_PROCESS_JUST_RESPAWN :
NGX_PROCESS_RESPAWN);
ch.command = NGX_CMD_OPEN_CHANNEL;
ch.pid = ngx_processes[ngx_process_slot].pid;
ch.slot = ngx_process_slot;
ch.fd = ngx_processes[ngx_process_slot].channel[0];
ngx_pass_open_channel(cycle, &ch);
if(loader == 0) {
return;
}
//启动cache loader进程,该进程的作用就是根据缓存文件在内存中重建索引元数据
//该进程实际上就是执行ngx_cache_manager_process_cycle(cycle,
// ngx_cache_loader_ctx);
ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
&ngx_cache_loader_ctx, "cacheloader process",
respawn ? NGX_PROCESS_JUST_SPAWN :
NGX_PROCESS_NORESPAWN);
ch.command = NGX_CMD_OPEN_CHANNEL;
ch.pid = ngx_processes[ngx_process_slot].pid;
ch.slot = ngx_process_slot;
ch.fd = ngx_processes[ngx_process_slot].channel[0];
ngx_pass_open_channel(cycle, &ch);
}
(4)ngx_cache_manager_process_cycle函数分析
这是cache loader进程的执行体,如果启动的是cache loader ,data传递的是ngx_cache_loader_ctx。
static void
ngx_cache_manager_process_cycle(ngx_cycle_t*cycle, void *data)
{
ngx_cache_manager_ctx_t *ctx = data;
//将ngx_cache_manager_ctx_t->handler或者
//ngx_cache_loader_ctx_t->handler赋值给event-handler。
ev.handler = ctx->handler;
ev.data = ident;
ev.log = cycle->log;
ident[3] = (void *) -1;
ngx_use_accept_mutex = 0;
ngx_setproctitle(ctx->name);
//将ev中的timer加入到timer红黑树中,其中timer的类型是ngx_rbtree_node_t
ngx_add_timer(&ev, ctx->delay);
//进入无尽的循环,不停的处理事件和超时
for ( ;; ) {
if (ngx_terminate || ngx_quit) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
exit(0);
}
if (ngx_reopen) {
ngx_reopen = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopeninglogs");
ngx_reopen_files(cycle, -1);
}
//处理事件和超时
ngx_process_events_and_timers(cycle);
}
}
(5)再看ctx定义
ngx_cache_loader_ctx会用来注册超时事件对象,然后加入到事件和超时树中,ngx_cache_loader_process_handler是超时的处理方法,60000是设定的超时时间,表示60000毫秒=60秒=1分钟。
static ngx_cache_manager_ctx_t ngx_cache_loader_ctx = {
ngx_cache_loader_process_handler, "cache loader process",60000
};
可以看出,1分钟之后会执行ngx_cache_loader_process_handler方法,再看看该方法里面到底做了什么。
(6)ngx_cache_loader_process_handler方法分析
该方法同时是注册在事件和超时红黑树节点中事件的处理方法。
static void
ngx_cache_loader_process_handler(ngx_event_t*ev)
{
ngx_uint_t i;
ngx_path_t **path;
ngx_cycle_t *cycle;
cycle = (ngx_cycle_t *) ngx_cycle;
path = cycle->pathes.elts;
for (i = 0; i < cycle->pathes.nelts;i++) {
if (ngx_terminate || ngx_quit) {
break;
}
//cache path中的loader在ngx_http_file_cache_set_slot方法中完成的注册,
//并指向函数ngx_http_file_cache_loader。
if (path[i]->loader) {
path[i]->loader(path[i]->data);
ngx_time_update();
}
}
exit(0);
}
(7)ngx_http_file_cache_loader函数分析
这是cache loader进程中主要的处理方法,该方法主要根据磁盘上的缓存文件来重建缓存元数据。该方法在ngx_http_file_cache_set_slot方法中进行注册,并在ngx_cache_loader_process_handler方法中进行依次调用,每个缓存路径都要注册一个该cache loader 方法。
static void
ngx_http_file_cache_loader( void *data)
{
ngx_http_file_cache_t *cache = data;
ngx_tree_ctx_t tree;
//进入loading状态
if (!cache->sh->cold || cache->sh->loading) {
return;
}
if(!ngx_atomic_cmp_set(&cache->sh->loading, 0, ngx_pid)) {
return;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP,ngx_cycle->log, 0,
"http file cacheloader");
//初始化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; //注册删除缓存文件方法
//上述的注册方法都会在ngx_walk_tree方法中进行调用
tree.data = cache;
tree.alloc = 0;
tree.log = ngx_cycle->log;
cache->last = ngx_current_msec;
cache->files = 0;
/*
ngx_walk_tree是递归函数,打开每层路径(dir)直到每个文件(file),根据其路径和文 件名得到key,在缓存的rbtree(红黑树)里面找这个key(部分),如果没有找到的话,就在内存中分配一个映射这个文件的node(但是不会把文件的内容进行缓存),然后插入到红黑树中和加入队列。
ctx->file_handler=>
ngx_http_file_cache_manage_file=>
ngx_http_file_cache_add_file=>
ngx_http_file_cache_add
从n = ngx_read_file(...)函数可以看出,每个磁盘缓存文件的开头的 sizeof(ngx_http_file_cache_header_t)个byte存放了跟缓存相关的信息
*/
if (ngx_walk_tree(&tree,&cache->path->name) == NGX_ABORT) {
cache->sh->loading = 0;
return;
}
cache->sh->cold = 0;
cache->sh->loading = 0;
ngx_log_error(NGX_LOG_NOTICE,ngx_cycle->log, 0,
"http file cache: %V%.3fM, bsize: %uz",
&cache->path->name,
((double)cache->sh->size * cache->bsize) / (1024 * 1024),
cache->bsize);
}
(8)最后分析ngx_walk_tree方法
/*
*ctx->init_handler() - see ctx->alloc
*ctx->file_handler() - file handler
*ctx->pre_tree_handler() - handler is called before entering directory
*ctx->post_tree_handler() - handler is called after leaving directory
*ctx->spec_handler() - special (socket, FIFO, etc.) file handler
*
*ctx->data - some data structure, it may be the same on all levels, or
* reallocated if ctx->alloc is nonzero
*
*ctx->alloc - a size of data structure that is allocated at every level
* and is initilialized by ctx->init_handler()
*
*ctx->log - a log
*
* onfatal (memory) error handler must return NGX_ABORT to stop walking tree
*/
/*
ngx_walk_tree是递归函数,打开每层路径(dir)直到每个文件(file),根据其路径和文件名得到key,在缓存的rbtree(红黑树)里面找这个key(部分),如果没有找到的话,就在内存中分配一个映射这个文件的node(但是不会把文件的内容进行缓存),然后插入到红黑树中和加入队列。
ctx->file_handler=>
ngx_http_file_cache_manage_file=>
ngx_http_file_cache_add_file=>
ngx_http_file_cache_add
从n = ngx_read_file(...)函数可以看出,每个磁盘缓存文件的开头的sizeof(ngx_http_file_cache_header_t)个byte存放了跟缓存相关的信息
*/
ngx_int_t
ngx_walk_tree(ngx_tree_ctx_t *ctx, ngx_str_t*tree)
{
void *data, *prev;
u_char *p, *name;
size_t len;
ngx_int_t rc;
ngx_err_t err;
ngx_str_t file, buf;
ngx_dir_t dir;
ngx_str_null(&buf);
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
"walk tree\"%V\"", tree);
//打开name指定的目录,并将返回值以DIR*的形式赋值给dir->dir后
//面对该目录下所有文件的访问都可以通过dir->dir进行
if(ngx_open_dir(tree, &dir) == NGX_ERROR) {
ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
ngx_open_dir_n "\"%s\" failed", tree->data);
return NGX_ERROR;
}
prev = ctx->data;
if(ctx->alloc) {
data = ngx_alloc(ctx->alloc, ctx->log);
if (data == NULL) {
goto failed;
}
if (ctx->init_handler(data, prev) == NGX_ABORT) {
goto failed;
}
ctx->data = data;
} else {
data = NULL;
}
for ( ;; ) {
ngx_set_errno(0);
//返回下一个目录进入点,并赋值给dir->de
if (ngx_read_dir(&dir) == NGX_ERROR) {
err = ngx_errno;
if (err == NGX_ENOMOREFILES) {
rc = NGX_OK;
} else {
ngx_log_error(NGX_LOG_CRIT,ctx->log, err,
ngx_read_dir_n" \"%s\" failed", tree->data);
rc = NGX_ERROR;
}
goto done;
}
len = ngx_de_namelen(&dir);
name = ngx_de_name(&dir);
ngx_log_debug2(NGX_LOG_DEBUG_CORE, ctx->log, 0,
"tree name%uz:\"%s\"", len, name);
if (len == 1 && name[0] == '.') {
continue;
}
if (len == 2 && name[0] == '.' && name[1] == '.') {
continue;
}
file.len = tree->len + 1 + len;
if (file.len + NGX_DIR_MASK_LEN > buf.len) {
if (buf.len) {
ngx_free(buf.data);
}
buf.len = tree->len + 1 + len + NGX_DIR_MASK_LEN;
buf.data = ngx_alloc(buf.len + 1, ctx->log);
if (buf.data == NULL) {
goto failed;
}
}
p = ngx_cpymem(buf.data, tree->data, tree->len);
*p++ = '/';
ngx_memcpy(p, name, len + 1);
file.data = buf.data;
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
"tree path \"%s\"",file.data);
if (!dir.valid_info) {
if (ngx_de_info(file.data, &dir) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_CRIT,ctx->log, ngx_errno,
ngx_de_info_n" \"%s\" failed", file.data);
continue;
}
}
//dir->de指向的是文件
if (ngx_de_is_file(&dir)) {
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
"tree file\"%s\"", file.data);
ctx->size = ngx_de_size(&dir);
ctx->fs_size = ngx_de_fs_size(&dir);
ctx->access = ngx_de_access(&dir);
ctx->mtime = ngx_de_mtime(&dir);
//进行索引重建
if (ctx->file_handler(ctx, &file) == NGX_ABORT) {
goto failed;
}
//dir->de指向的是目录
} else if (ngx_de_is_dir(&dir)) {
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
"tree enter dir\"%s\"", file.data);
ctx->access = ngx_de_access(&dir);
ctx->mtime = ngx_de_mtime(&dir);
//其实在ngx_http_file_cache_loader方法中已经完成了注册,只是返回NGX_OK
if (ctx->pre_tree_handler(ctx, &file) == NGX_ABORT) {
goto failed;
}
//针对于子目录进行递归调用
if (ngx_walk_tree(ctx, &file) == NGX_ABORT) {
goto failed;
}
ctx->access = ngx_de_access(&dir);
ctx->mtime = ngx_de_mtime(&dir);
//其实在ngx_http_file_cache_loader方法中已经完成了注册,只是返回NGX_OK
if (ctx->post_tree_handler(ctx, &file) == NGX_ABORT) {
goto failed;
}
} else {
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
"tree special\"%s\"", file.data);
if (ctx->spec_handler(ctx, &file) == NGX_ABORT) {
goto failed;
}
}
}
failed:
rc= NGX_ABORT;
done:
if(buf.len) {
ngx_free(buf.data);
}
if(data) {
ngx_free(data);
ctx->data = prev;
}
if(ngx_close_dir(&dir) == NGX_ERROR) {
ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
ngx_close_dir_n "\"%s\" failed", tree->data);
}
return rc;
}
(全文完)