Nginx 缓存机制 详解之一 缓存管理进程

Nginx缓存的基本思路就是利用请求的局部性原理,下次访问不再连接后端服务器。
Nginx是通过设置xxx_cache_path(比如proxy_cache_path),主要用来创建管理cache的共享 内存数据结构(红黑树和对列),另一个是xxx_cache主要 用来前面的参数创建内存空间。

命令xxx_cache_path,会调用ngx_http_file_cache_set_slot函数,同时会用到ngx_http_file_cache_t这个数据结构,它主要用来管理所有的cache文件,它本身不保存cache,只是保存管理cache的数据结构。每一个xxx_cache_path 都会创建一个ngx_http_file_cache_t。因为每一个xxx_cache_path都会创建一个cache文件。


typedef struct {
    ngx_rbtree_t                     rbtree;
    ngx_rbtree_node_t                sentinel;
    ngx_queue_t                      queue;
    ngx_atomic_t                     cold;  // 表示这个cache是否已经被加载
    ngx_atomic_t                     loading;  //那个进程正在 加载这个cache
    off_t                            size;   // 文件大小
    ngx_uint_t                       count;
    ngx_uint_t                       watermark;
} ngx_http_file_cache_sh_t;


struct ngx_http_file_cache_s {
    ngx_http_file_cache_sh_t        *sh;
    ngx_slab_pool_t                 *shpool;

    ngx_path_t                      *path;   // cache的 目录
    ngx_path_t                      *temp_path; //当前的path下的 所有cache文件的最大值

    off_t                            max_size;
    size_t                           bsize;

    time_t                           inactive; //如果多久不用就会被删除

    time_t                           fail_time;
    //当前有多少个cache文件(超过loader_files之后会被清0)
    ngx_uint_t                       files;
    //这个值也就是一个阈值,当load的文件个数大于这个值之后,load进程会短暂的休眠(时间位loader_sleep)
    ngx_uint_t                       loader_files;
    //最后被manage或者loader访问的时间
    ngx_msec_t                       last;
    //和上面的loader_files配合使用,当文件个数大于loader_files,就会休眠
    ngx_msec_t                       loader_sleep;
    //配合上面的last,也就是loader遍历的休眠间隔。
    ngx_msec_t                       loader_threshold;
    //共享内存的地址
    ngx_shm_zone_t                  *shm_zone;
};

有一个很关键的结构就是ngx_path_t,它保存了当前的cache的一些信息,以及对应的cache管理回调函数manger和loader.

typedef struct {
    ngx_str_t                  name;  //cache名称
    size_t                     len;
    size_t                     level[3];
    //对应的回调,以及回调数据
    ngx_path_manager_pt        manager;
    ngx_path_loader_pt         loader;
    void                      *data;

    u_char                    *conf_file;
    ngx_uint_t                 line;
} ngx_path_t;

ngx_http_file_cache_set_slot函数

char *
ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){
   ngx_http_file_cache_t  *cache;
..............................................
//初始化cache对象,这两个回调后续会详细分析
    cache->path->manager = ngx_http_file_cache_manager;
    cache->path->loader = ngx_http_file_cache_loader;
    cache->path->data = cache;
    cache->path->conf_file = cf->conf_file->file.name.data;
    cache->path->line = cf->conf_file->line;
//初始化这几个阈值
    cache->loader_files = loader_files;
    cache->loader_sleep = (ngx_msec_t) loader_sleep;
    cache->loader_threshold = (ngx_msec_t) loader_threshold;
//将path添加到全局的路径管理中
    if (ngx_add_path(cf, &cache->path) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
//创建共享内存
    cache->shm_zone = ngx_shared_memory_add(cf, &name, size, cmd->post);
    if (cache->shm_zone == NULL) {
        return NGX_CONF_ERROR;
    }

    if (cache->shm_zone->data) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "duplicate zone \"%V\"", &name);
        return NGX_CONF_ERROR;
    }

//设置初始化函数
    cache->shm_zone->init = ngx_http_file_cache_init;
    cache->shm_zone->data = cache;
//设置cache
    cache->inactive = inactive;
    cache->max_size = max_size;
    }

上面还有一个命令xxx_cache,这个主要是调用ngx_http_fastcgi_cache函数。这个函数更简单,就是简单的查找出上面创建的共享内存

static char *
ngx_http_fastcgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
 ..................................................
//查找出共享内存
    flcf->upstream.cache = ngx_shared_memory_add(cf, &value[1], 0,
                                                 &ngx_http_fastcgi_module);
    if (flcf->upstream.cache == NULL) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}

Nginx服务器启动后,会对本地磁盘上的缓存文件进行扫描,在内存中建立缓存索引,并有专门的进程对缓存文件进行过期判断、更新等进行管理。这里就牵扯到Nginx的两个 框架中的两个进程:

  • 缓存管理器
  • 缓存加载器
    上面两个进程共用的是同一个函数ngx_cache_manager_process_cycle,只是传递的第二个参数是不一样
    第二个参数的结构体
typedef struct {
    ngx_event_handler_pt       handler;
    char                      *name;
    ngx_msec_t                 delay;
} ngx_cache_manager_ctx_t;

缓存管理器

定期检查缓存状态、查看缓存总量是否超出限制、如果超出,就移除其中最少使用的 部分
这个进程主要是调用函数ngx_cache_manager_process_cycle

ngx_cache_manager_process_cycle函数

static ngx_cache_manager_ctx_t  ngx_cache_manager_ctx = {
    ngx_cache_manager_process_handler, "cache manager process", 0
};

static void
ngx_cache_manager_process_cycle(ngx_cycle_t *cycle, void *data)
{
    ngx_cache_manager_ctx_t *ctx = data;

    void         *ident[4];
    ngx_event_t   ev;

    /*
     * Set correct process type since closing listening Unix domain socket
     * in a master process also removes the Unix domain socket file.
     */
    ngx_process = NGX_PROCESS_HELPER;

    ngx_close_listening_sockets(cycle);

    /* Set a moderate number of connections for a helper process. */
    cycle->connection_n = 512;

    ngx_worker_process_init(cycle, -1);

    ngx_memzero(&ev, sizeof(ngx_event_t));
    //最重要在这里设置传递进来的ctx->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);
    // 添加定时器
    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, "reopening logs");
            ngx_reopen_files(cycle, -1);
        }

        ngx_process_events_and_timers(cycle);
    }
}
ngx_process_events_and_timers()--->ngx_event_expire_timers()---->ngx_cache_manager_process_handler()

ngx_cache_manager_process_handler函数

static void
ngx_cache_manager_process_handler(ngx_event_t *ev)
{
    time_t        next, n;
    ngx_uint_t    i;
    ngx_path_t  **path;

    next = 60 * 60;

    path = ngx_cycle->paths.elts;
    //遍历所有的cache目录
    for (i = 0; i < ngx_cycle->paths.nelts; i++) {
    // 调用manager回调
        if (path[i]->manager) {
            n = path[i]->manager(path[i]->data);
           //取得下一次的定时器的时间,可以看到是取n和next的最小值
            next = (n <= next) ? n : next;

            ngx_time_update();
        }
    }

    if (next == 0) {
        next = 1;
    }

    ngx_add_timer(ev, next * 1000);
}

ngx_http_file_cache_manager函数

这里manger的函数是ngx_http_file_cache_manager() 函数这时Nginx在调用ngx_http_file_cache_set_slot函数解析配置指令proxy_cache_path时设置的回调值

static time_t
ngx_http_file_cache_manager(void *data)
{
    ngx_http_file_cache_t  *cache = data;

    off_t       size;
    time_t      next, wait;
    ngx_uint_t  count, watermark;
    //如果有超时的cache,就删除对应的cache
    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;
        count = cache->sh->count;
        watermark = cache->sh->watermark;

        ngx_shmtx_unlock(&cache->shpool->mutex);

        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "http file cache size: %O c:%ui w:%i",
                       size, count, (ngx_int_t) watermark);
       // 如果没有发现超过最大的大小限制,则直接返回
        if (size < cache->max_size && count < watermark) {
            return next;
        }
     //否则遍历所有的cache文件,然后删除过期的cache.
        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,一个是ngx_http_file_cache_forced_expire主要区别:前一个只有过期的cache才会去尝试删除它(在满足引用计数为0),而后一个不管有没有过期,只要引用计数为0,就会去清理。来详细看这两个函数的实现。

ngx_http_file_cache_expire函数

首先是ngx_http_file_cache_expire,这里注意nginx使用了LRU,也就是队列最尾端保存的是最长时间没有被使用的,并且这个函数返回的就是一个wait值

static time_t
ngx_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;

    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;
        }
       //如果cache队列为空,则直接退出返回
        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;
        //如果没有超时,则退出循环
        if (wait > 0) {
            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]);
        //如果引用计数为0,则删除这个cache节点
        if (fcn->count == 0) {
            ngx_http_file_cache_delete(cache, q, name);
            continue;
        }
      //如果当前节点正在删除,则退出循环
        if (fcn->deleting) {
            wait = 1;
            break;
        }

        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,
         * we prefer to just move them to the top of the inactive queue
         */
        //将当前节点放入队列最前端
        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",
                      (size_t) 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函数

ngx_http_file_cache_forced_expire,就是强制删除cache 节点,它的返回值也是wait time。它的遍历也是从后到前的。

static time_t
ngx_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]);
      //如果引用计数为0则删除cache 
        if (fcn->count == 0) {
            ngx_http_file_cache_delete(cache, q, name);
            wait = 0;

        } else {
        //否则尝试20次
            if (--tries) {
                continue;
            }

            wait = 1;
        }

        break;
    }

    ngx_shmtx_unlock(&cache->shpool->mutex);

    ngx_free(name);

    return wait;
}

还有一个函数就是ngx_http_file_cache_delete,这个函数这里就不分析了,它主要有2个功能,一个是删除cache文件,一个是删除cache管理节点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿阿三

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值