nginx -cache loader process 进程分析

本文主要对nginx的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.      结构先行

保存了当前cache的一些信息,包含managerloader函数指针和传入数据地址

(1) ngx_path_t;

typedef struct {

    ngx_str_t                  name;

    size_t                     len;

    size_t                     level[3];

    ngx_path_manager_pt        manager;

    ngx_path_loader_pt         loader;   //cache loader的函数指针,在运行cache 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=16cache_key-4rbt_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管理进程

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;

}


你可能感兴趣的:(nginx -cache loader process 进程分析)