NginX进程模型-Cache Manager/Cache Loader

来源:互联网 发布:cordova 源码 编辑:程序博客网 时间:2024/06/03 21:29

   NginX多进程模型中,Master进程管理其派生的子进程,Worker进程处理连接请求。在某些情况下,NginX Master进程会启动两个与Cache相关的进程,即:Cache Manager/Cache Loader用于缓存上游服务器的数据(譬如:ngx_http_proxy_module中设置proxy_cache_path)。本文将介绍Cache Manager/Cache Loader的内部运行机制。

   NginX中cache的原理是:将上游服务器返回的数据缓存在本地文件中,如果请求命中缓存,则直接通过sendfile将缓存文件的内容发送给下游。以ngx_http_proxy_module为例来说明cache的原理。ngx_http_proxy_module通过proxy_cache_path指令完成file cache的初始化。

1. Cache的管理

    Cache的管理由Cache Loader/Cache Manager两个进程合作完成;Cache Manager的功能是定期清理过期缓存(基本策略为LRU);Cache Loader的功能是将已经缓存文件映射到内存中。

1.1 初始化

    通过调用ngx_http_file_cache_set_slot函数,NginX能获得一个Cache管理的目录实例;NginX在ngx_init_cycle阶段完成目录创建工作。  

    ngx_start_cache_manager_processes函数是触发Cache Loader/Cache Manager进程启动的入口。接下来将分别进行介绍。

1.2 Cache Manager

    Cache Manager进程的核心逻辑是一个定时器任务处理;其回调函数为ngx_cache_manager_process_handler。接下来分析一下代码:

static voidngx_cache_manager_process_handler(ngx_event_t *ev){    time_t        next, n;    ngx_uint_t    i;    ngx_path_t  **path;    next = 60 * 60;   /// 默认为1小时    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);  /// 执行回调(ngx_http_file_cache_manager)  返回n表示下一个过期检查时间点。            next = (n <= next) ? n : next;            ngx_time_update();        }    }    if (next == 0) {        next = 1;    }<strong>    ngx_add_timer(ev, next * 1000);   /// 再次将任务追加到定时器中。</strong>}

       ngx_http_file_cache_manager回调函数功能为:(1)删除时间过期的缓存文件  (2) 缓存文件大小超限,删除最近最久的缓存文件。接下来看一下代码:

static time_tngx_http_file_cache_manager(void *data){    ngx_http_file_cache_t  *cache = data;    off_t   size;    time_t  next, wait;    next = ngx_http_file_cache_expire(cache); /// 删除时间过期的缓存文件    cache->last = ngx_current_msec;    cache->files = 0;    for ( ;; ) {        ngx_shmtx_lock(&cache->shpool->mutex);        size = cache->sh->size;        ngx_shmtx_unlock(&cache->shpool->mutex);        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,                       "http file cache size: %O", size);        if (size < cache->max_size) {            return next;        }        wait = ngx_http_file_cache_forced_expire(cache);  /// 缓存文件大小超限,删除最近最久的缓存文件        if (wait > 0) {            return wait;        }        if (ngx_quit || ngx_terminate) {            return next;        }    }}

    首先看一下ngx_http_file_cache_expire函数,代码分析如下:

static time_tngx_http_file_cache_expire(ngx_http_file_cache_t *cache){    u_char                      *name, *p;    size_t                       len;    time_t                       now, wait;    ngx_path_t                  *path;    ngx_queue_t                 *q;    ngx_http_file_cache_node_t  *fcn;    u_char                       key[2 * NGX_HTTP_CACHE_KEY_LEN];    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,                   "http file cache expire");    path = cache->path;    len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN; /// cache key的长度    name = ngx_alloc(len + 1, ngx_cycle->log);    if (name == NULL) {        return 10;    }    ngx_memcpy(name, path->name.data, path->name.len);   /// 赋值目录    now = ngx_time();    ngx_shmtx_lock(&cache->shpool->mutex);    for ( ;; ) {        if (ngx_quit || ngx_terminate) {            wait = 1;            break;        }        if (ngx_queue_empty(&cache->sh->queue)) {   /// 缓存为空,不需要进行删除            wait = 10;            break;        }        q = ngx_queue_last(&cache->sh->queue);           fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);  /// 取最久未被使用的缓存文件句柄        wait = fcn->expire - now;        <strong>if (wait > 0) {   /// 没有超时。</strong>            wait = wait > 10 ? 10 : wait;            break;        }        ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,                       "http file cache expire: #%d %d %02xd%02xd%02xd%02xd",                       fcn->count, fcn->exists,                       fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);        if (fcn->count == 0) { /// 当前引用计数为0, 且已经超时,则执行删除操作。            ngx_http_file_cache_delete(cache, q, name);            continue;        }       if (fcn->deleting) {  /// 如果正在删除则返回。            wait = 1;            break;        }       /// 没有超时或者有引用计数大于0,将设置为最新引用。LRU操作。        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);        /*         * abnormally exited workers may leave locked cache entries,         * and although it may be safe to remove them completely,         * <strong>we prefer to just move them to the top of the inactive queue</strong>         */        ngx_queue_remove(q);        fcn->expire = ngx_time() + cache->inactive;        ngx_queue_insert_head(&cache->sh->queue, &fcn->queue);        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,                      "ignore long locked inactive cache entry %*s, count:%d",                      2 * NGX_HTTP_CACHE_KEY_LEN, key, fcn->count);    }    ngx_shmtx_unlock(&cache->shpool->mutex);    ngx_free(name);    return wait;}

    再来看一下ngx_http_file_cache_forced_expire函数,代码分析如下:

static time_tngx_http_file_cache_forced_expire(ngx_http_file_cache_t *cache){    u_char                      *name;    size_t                       len;    time_t                       wait;    ngx_uint_t                   tries;    ngx_path_t                  *path;    ngx_queue_t                 *q;    ngx_http_file_cache_node_t  *fcn;    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,                   "http file cache forced expire");    path = cache->path;    len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;    name = ngx_alloc(len + 1, ngx_cycle->log);    if (name == NULL) {        return 10;    }    ngx_memcpy(name, path->name.data, path->name.len);    wait = 10;    tries = 20;    ngx_shmtx_lock(&cache->shpool->mutex);    for (q = ngx_queue_last(&cache->sh->queue);         q != ngx_queue_sentinel(&cache->sh->queue);         q = ngx_queue_prev(q))       /// 从最久往最新遍历    {        fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);        ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,                  "http file cache forced expire: #%d %d %02xd%02xd%02xd%02xd",                  fcn->count, fcn->exists,                  fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);       if (fcn->count == 0) {  /// 如果引用计数为0,强制删除,不用管是否过期。然后准备退出。wait时间为0.            ngx_http_file_cache_delete(cache, q, name);            wait = 0;        } else {  <strong>/// 最多往前探测20个缓存文件</strong>。            if (--tries) {                continue;            }            wait = 1;   /// <strong>探测了20个缓存文件引用计数都非0, wait 1秒钟</strong>        }        break;    }    ngx_shmtx_unlock(&cache->shpool->mutex);    ngx_free(name);    return wait;}

    从上述代码可以看出,具体的删除操作的函数为:ngx_http_file_cache_delete。改函数的代码分析如下

static voidngx_http_file_cache_delete(ngx_http_file_cache_t *cache, ngx_queue_t *q,    u_char *name){    u_char                      *p;    size_t                       len;    ngx_path_t                  *path;    ngx_http_file_cache_node_t  *fcn;    fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue); /// 取ngx_http_file_cache_node_t数据。    if (fcn->exists) {    /// 如果缓存文件存在        cache->sh->size -= fcn->fs_size;        path = cache->path;        p = name + path->name.len + 1 + path->len;        p = ngx_hex_dump(p, (u_char *) &fcn->node.key,                         sizeof(ngx_rbtree_key_t));        len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t);        p = ngx_hex_dump(p, fcn->key, len);        *p = '\0';        fcn->count++;        fcn->deleting = 1;        ngx_shmtx_unlock(&cache->shpool->mutex);        len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;        ngx_create_hashed_filename(path, name, len);        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,                       "http file cache expire: \"%s\"", name);        if (ngx_delete_file(name) == NGX_FILE_ERROR) {   /// 删除文件            ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,                          ngx_delete_file_n " \"%s\" failed", 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);    }}

1.3 Cache Loader

   Cache Loader进程的核心逻辑也是一个定时器任务处理;其回调函数为ngx_cache_loader_process_handler。接下来分析一下代码:

static voidngx_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->paths.elts;    for (i = 0; i < cycle->paths.nelts; i++) {        if (ngx_terminate || ngx_quit) {            break;        }        if (path[i]->loader) {            path[i]->loader(path[i]->data);  /// 实际执行函数为: ngx_http_file_cache_loader函数。            ngx_time_update();        }    }    exit(0);}

注: 执行一次回调之后,Cache Loader进程退出。启动Cache Loader 60秒之后,执行定时器任务回调函数ngx_cache_loader_process_handler。

     接下来,介绍一下ngx_http_file_cache_loader函数:

static voidngx_http_file_cache_loader(void *data){    ngx_http_file_cache_t  *cache = data;    ngx_tree_ctx_t  tree;    if (!cache->sh->cold || cache->sh->loading) {  /// 系统启动后cold = 1, loading = 0  (loading = 1)        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 cache loader");    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;    tree.alloc = 0;    tree.log = ngx_cycle->log;    cache->last = ngx_current_msec;    cache->files = 0;    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);}

   接下来分析一下ngx_walk_tree函数。

ngx_int_tngx_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);    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);        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;            }        }        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) {  //// 处理文件的操作。实际执行函数为ngx_http_file_cache_manage_file                goto failed;            }       } 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);            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);            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;}

    实际文件的加载函数为ngx_http_file_cache_manage_file,相关代码分析如下:

static ngx_int_tngx_http_file_cache_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path){    ngx_msec_t              elapsed;    ngx_http_file_cache_t  *cache;    cache = ctx->data;    if (ngx_http_file_cache_add_file(ctx, path) != NGX_OK) {   /// 将文件添加到Cache        (void) ngx_http_file_cache_delete_file(ctx, path);    }    if (++cache->files >= cache->loader_files) {  /// 文件个数太大,则睡眠并清理files        ngx_http_file_cache_loader_sleep(cache);    } else {        ngx_time_update();        elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,                       "http file cache loader time elapsed: %M", elapsed);        if (elapsed >= cache->loader_threshold) {  /// loader时间过长 则睡眠            ngx_http_file_cache_loader_sleep(cache);        }    }    return (ngx_quit || ngx_terminate) ? NGX_ABORT : NGX_OK;}

总结: 1> Cache Loader的任务是加载本地的缓存文件,加载完毕后退出。

0 0