nginx proxy cache的实现原理

除了proxy cache,nginx还可对fastcgi的响应数据进行缓存,即fastcgi_cache,两者的配置和实现原理是一致的;
 
本文以fastcgi_cache为例,描述一下其实现原理,主要参考链接 http://www.tuicool.com/articles/QnMNr23

先回顾一下配置参数

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所涉及的数据结构

 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_pathcache目录位于同一文件系统;

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;

...


 ngx_http_file_cache_node_t

保存每个缓存文件在内存中的描述信息。

这些信息需要存储于共享内存中,以便多个 worker 进程共享。所以,为了提高利用率,此结构体多个字段使用了位域 (Bit field),同时,缓存 key 中用作查询树键值( ngx_rbtree_key_t ) 的部分字节不再重复存储:


 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]


 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_handlerngx_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_lookupngx_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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值