转载

Nginx 源代码笔记 - upstream 缓存

代码分析版本为 1.4.4,并且所有讨论都是在 Nginx 启用 HTTP Cache 机制 (定义了 NGX_HTTP_CACHE 宏) 的前提下进行。同时,由于 fastcgi/proxy 模块在对 缓存的使用上基本一致,本篇上下文设定为 fastcgi 模块。

Nginx manual

Both the key and file name in a cache are a result of applying the MD5 function to the proxied URL.

A cached response is first written to a temporary file, and then the file is renamed...It is thus recommended that for any given location both cache and a directory holding temporary files ( proxy_temp_path ) are put on the same file system.

...all active keys and information about data are stored in a shared memory zone...Cached data that are not accessed during the time specified by the inactive parameter get removed from the cache regardless of their freshness.

The special "cache manager" process monitors the maximum cache size set by the max_size paramater. When this size is exceeded, it removes the least recently used data.

A minute after the start the special "cache loader" process is activated. It loads information about previously cached data stored on file system into a cache zone. The loading is done in iterations. During one iteration no more than loader_files items are loaded. The duration of one iteration is limited by the loader_threshold paramter. Between iterations, a pause configured by the loader_sleep parameter is made.

相关结构和配置指令

  • proxy_temp_path - set the path for storing temporary files with data received from proxied servers. Up to three-level subdirectory hierarchy can be used underneath the specified directory.

  • ngx_path_t - directory 的路径 ( name )、子目录层级定义 ( level ) 和 可定制 的缓存管理行为 ( manager , loader )。

    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;
  • ngx_cycle_t::paths - all dirs added by ngx_add_path while parsing conf

  • ngx_create_paths - called in ngx_init_cycle , calls ngx_create_dir to mkdir .

  • ngx_add_path - 不同调用者生成不同类型的 ngx_path_t 对象,并调用此函数将它 加入 ngx_cycle_t::paths 数组。

    • 用于设置临时目录的指令 (比如 fastcgi_temp_path , proxy_temp_path 等) 调用 ngx_http_file_cache_set_slot 函数读取临时目录配置,这类目录不需要缓 存管理进程进行维护:

      path->manager = NULL; path->loader = NULL;
    • 用于设置缓存文件目录的指令 (比如 fastcgi_cache_path , proxy_cache_path 等) 调用 ngx_http_file_cache_set_slot 函数读取缓存目录配置,并设置对此缓 存目录进行管理的方法:

      path->manager = ngx_http_file_cache_manager; path->loader = ngx_http_file_cache_loader; path->data = cache;
  • 每条 proxy_cache_path 指令创建的 cache 都由 ngx_http_file_cache_t 结构表 示。其中, ngx_http_file_cache_sh_t * 类型的成员 sh 维护 LRU 结构用于保存缓 存节点 和 缓存的当前状态 (是否正在从磁盘加载、当前缓存大小等);成员 shpool 是 用于管理共享内存的 slab allocator ,所有缓存节点占用空间都由它进行分配;其它成 员用于保存此缓存的相关配置信息。缓存结构体由设定的 ngx_shm_zone_t 初始化函数 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;      /* inactive queue */     ngx_atomic_t                cold;       /* 缓存是否可用 (加载完毕) */     ngx_atomic_t                *loading;   /* 是否正在被 loader 进程加载 */     off_t                       size;       /* 初始化为 0 */ } ngx_http_file_cache_sh_t;  struct ngx_http_file_cache_s {     ngx_http_file_cache_sh_t        *sh;     /* from slab allocator */     ngx_slab_pool_t                 *shpool; /* shm_zone->shm.addr */      ngx_path_t                      *path;      off_t                            max_size; /* how many blocks */     size_t                           bsize; /* fs block size */      time_t                           inactive;      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;         /* .init = ngx_http_file_cache_init /* */            .data = cache         */ };
  • 结构体 ngx_http_file_cache_node_t 保存磁盘缓存文件在内存中的描述信息。这些 信息需要存储于共享内存中,以便多个 worker 进程共享。所以,为了提高利用率,此 结构体多个字段使用了位域 (Bit field),同时,缓存 key 中用作查询树键值 ( ngx_rbtree_key_t ) 的部分字节不再重复存储:

    typedef struct {     ngx_rbtree_node_t                node;  /* 缓存查询树的节点 */     ngx_queue_t                      queue; /* LRU 队列中的节点 */      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;      unsigned                         updating:1;     unsigned                         deleting:1; /* 正在被清理中 */                                      /* 11 unused bits */      ngx_file_uniq_t                  uniq;     time_t                           expire;      time_t                           valid_sec;     size_t                           body_start;     off_t                            fs_size; } ngx_http_file_cache_node_t;
    • 关键字段详细含义:

      • error - 当后端响应码 >= NGX_HTTP_SPECIAL_RESPONSE , 并且打开了 fastcgi_intercept_errors 配置,同时 fastcgi_cache_valid 配置指令和 error_page 配置指令也对该响应码做了设定 的情部下,该字段记录响应码, 并列的 valid_sec 字段记录该响应码的持续时间。这种 error 节点并不对 应实际的缓存文件。

      • exists - 该缓存节点是否有对应的缓存文件。新创建的缓存节点或者过期的 error 节点 (参见 error 字段,当 error 不等于 0 时,Nginx 随后也不 会再关心该节点的 exists 字段值) 该字段值为 0。当正常节点 ( error 等 于 0) 的 exists 为 0 时,进入 cache lock 模式。

      • valid_sec , valid_msec - 缓存内容的过期时间,缓存内容过期后被查询 时会由 ngx_http_file_cache_read 返回 NGX_HTTP_CACHE_STALE ,然后由 fastcgi_cache_use_stale 配置指令决定是否及何种情况下使用过期内容。

      • expires - 缓存节点的可回收时间 (附带缓存内容)。

      • updating - 缓存内容过期,某个请求正在获取有效的后端响应并更新此缓存 节点。参见 ngx_http_cache_t::updating

  • 每个文件系统中的缓存文件都有固定的存储格式,其中 ngx_http_file_cache_header_t 为包头结构,存储缓存文件的相关信息 (修改时间、缓存 key 的 crc32 值、和用于指明 HTTP 响应包头和包体在缓存文件中偏移位置的字段等):

    typedef struct {     time_t                           valid_sec;     time_t                           last_modified;     time_t                           date;     uint32_t                         crc32;     u_short                          valid_msec;     u_short                          header_start;     u_short                          body_start; } ngx_http_file_cache_header_t;
    • 缓存文件格式
      [ngx_http_file_cache_header_t]["/nKEY: "][orig_key]["/n"][header][body]
  • 请求对应的缓存条目的完整信息 (请求使用的缓存 file_cache 、缓存条目对应的缓存 节点信息 node 、缓存文件 file 、key 值及其检验 crc32 等等) 都临时保存于 ngx_http_cache_t ( r->cache ) 结构体中,这个结构体中的信息量基本上相当于 ngx_http_file_cache_header_tngx_http_file_cache_node_t 的总和:

    struct ngx_http_cache_s {     ngx_file_t                       file; /* 缓存文件描述结构体 */     ngx_array_t                      keys;     uint32_t                         crc32; /* crc32 of literal key */     u_char                           key[NGX_HTTP_CACHE_KEY_LEN];                                             /* md5sum of literal key */     ngx_file_uniq_t                  uniq;     time_t                           valid_sec; /* from/to fcn->valid_sec */     time_t                           last_modified;     time_t                           date;      size_t                           header_start; /* offset in cache file */     size_t                           body_start;   /* offset in cache file */     off_t                            length;     off_t                            fs_size;      ngx_uint_t                       min_uses;     ngx_uint_t                       error; /* from/to fcn->error */     ngx_uint_t                       valid_msec; /* from/to fcn->valid_msec */     ngx_buf_t                       *buf; /* 存储缓存文件头 */      ngx_http_file_cache_t           *file_cache; /* worker-shared cache */     ngx_http_file_cache_node_t      *node; /* cache node by `key` */      ngx_msec_t                       lock_timeout;     ngx_msec_t                       wait_time;      ngx_event_t                      wait_event;      unsigned                         lock:1; /* use cache lock or not */     unsigned                         waiting:1; /* wait by cache_lock */      unsigned                         updated:1;     unsigned                         updating:1;     unsigned                         exists:1;     unsigned                         temp_file:1; };
    • 关键字段详细含义:

      • updating - 缓存内容己过期,并且当前请求正在获取有效的后端响应并更新 此缓存节点。参见 ngx_http_file_cache_node:updating

      • waiting - 缓存内容己过期,当前请求正等待其它请求更新此缓存节点。

配置

fastcgi_cache_path 指令用于 “声明” 一个全新的缓存,指令参数指定缓存文件在文件 系统中的路径、共享内存的名字、共享内存大小等信息。

这个配置指令处理函数 ngx_http_file_cache_set_slot 完成以下工作:

  • 全局内存池 ( cycle->pool ) 中创建 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;
  • 调用 ngx_add_path 将缓存文件路径加入全局路径数组 ( cycle->paths ) 中,这些路 径在配置解析完毕后,由 Nginx 检查并创建 ( ngx_init_cycle 调用 ngx_create_paths 函数完成)。

  • 调用 ngx_shared_memory_add 定义共存内存,在配置解析完毕后,由 Nginx 完成共享 内存创建和初始化工作。

    /* ngx_http_file_cache_set_slot */ 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;  ... /* ngx_init_cycle */ ngx_shm_alloc(&shm_zone[i].shm); ngx_init_zone_pool(cycle, shm_zone[i]);          shm_zone[i].init(&shm_zone[i], NULL); /* ngx_http_file_cache_init */  /* ngx_http_file_cache_init */ cache->shpool = (ngx_slab_pool_t) shm_zone->shm.addr; ... cache->sh = ngx_slab_alloc(cache->shpool, sizeof(ngx_http_file_cache_sh_t)); cache->shpool->data = cache->sh;  ngx_rbtree_init(&cache->sh->rbtree, &cache->sh->sentinel,                 ngx_http-file_cache_rbtree_insert_value); ngx_queue_init(&cache->sh->queue);

fastcgi_cache 指令指定了在当前作用域中使用哪个缓存维护缓存条目,参数对应的缓 存必须事先由 fastcgi_cache_path 指令定义。

这个配置指令处理函数 ngx_http_fastcgi_cache 负责初始化 ngx_http_upstream_conf_t::cache 字段:

/* ngx_http_fastcgi_cache */ ngx_http_fastcgi_loc_conf_t *flcf = conf; ... flcf->upstream.cache = ngx_shared_memory_add(cf, &value[1], 0,                                              &ngx_http_fastcgi_module);

上述的配置解析工作完成以后,由 flcf->upstream.cache 就可以引用到已经初始化后 的缓存 ( 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);     ... }  /* ngx_http_upstream_cache */ ngx_http_cache_t *c;  c = r->cache;  if (c == NULL) {     ...     ngx_http_file_cache_new(r);     ...     c = r->cache;      c->min_uses = u->conf->cache_min_uses;     c->body_start = u->conf->buffer_size;     c->file_cache = u->conf->cache->data;     ... }

加载 & 管理

Nginx 进程启动时,会试图从缓存对应的文件系统路径下的文件读取必要数据,然后重建 缓存的内存结构。这个过程由 cache loader 进程完成。

同时,常驻 Nginx 子进程 cache manager 负责维护缓存文件,定期清理过期的缓存条 目。同时,它也会检查缓存目录总大小,如果超出配置限制的话,强制清理掉最老的缓存 条目。

两个进程均在 Nginx 主进程初始化完成后,由 ngx_start_cache_manager_processes 函数创建并开始运行:

static void ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn) {     ...     manager = 0;     loader = 0;      path = ngx_cycle->paths.elts;     for (i = 0; i < ngx_cycle->paths.nelts; i++) {          if (path[i]->manager) {             manager = 1;         }          if (path[i]->loader) {             loader = 1;         }     }      if (manager == 0) {         return;     }      ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,                       &ngx_cache_manager_ctx, "cache manager process",                       respawn ? NGX_PROCESS_JUST_RESPAWN : NGX_PROCESS_RESPAWN);     ...     if (loader == 0) {         return;     }      ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,                       &ngx_cache_loader_ctx, "cache loader process",                       respawn ? NGX_PROCESS_JUST_RESPAWN : NGX_PROCESS_RESPAWN);     ... }

对上述代码的补充说明:

  • cache manager 进程和 cache loader 进程只在配置文件中定义了缓存目录后,才 会被创建并运行。

  • 当前版本 (1.4.4) 中, loadermanager 变量的值一致,要么同为 0,要么同为 1。

  • cache manager 进程和 cache loader 进程的入口函数一样,区别在于入口函数的参 数不同。这个参数决定了 ngx_cache_manager_process_cycle 的行为。

    static void ngx_cache_manager_process_cycle(ngx_cycle_t *cycle, void *data) {     ngx_cache_manager_ctx_t *ctx = data;     ngx_event_t ev;      ...     ev.handler = ctx->handler;     ev.data = ident;     ...     ngx_add_timer(&ev, ctx->delay);      for ( ;; ) {         ...         ngx_process_events_and_timers(cycle);     } }

cache loader

cache loader 来讲,其 ngx_cache_manager_ctx_t 的值为 ngx_cache_loader_ctx

static ngx_cache_manager_ctx_t ngx_cache_loader_ctx = {     ngx_cache_loader_process_handler, "cache loader process", 60000 };

也就是说, cache loader 进程在 60000 ms 后才会开始第一次执行,并且执行回调函 数 ngx_cache_loader_process_handler

static void 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); }

可以看到, ngx_cache_loader_process_handler 只是对缓存各个缓存目录调用预先指定 的 loader 回调函数。所有 loader 函数执行完后,调用 exit(0) 函数销毁 cache loader 进程。

针对 fastcgi/proxy 模块, loader 回调函数被 ngx_http_file_cache_set_slot 函数设置为 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 递归遍历缓存目录,并对不同类型的文件根据回调函数做不同的 处理。

  • ngx_tree_ctx_t 类型中回调函数成员被调用的时机:

    • file_handler - 文件节点为普通文件时调用
    • pre_tree_handler - 在递归进入目录节点时调用
    • post_tree_handler - 在递归遍历完目录节点后调用
    • spec_handler - 文件节点为特殊文件时调用

ngx_http_file_cache_noop 函数并未执行任何操作; ngx_http_file_cache_delete_file 函数将会删除缓存目录中的特殊文件; ngx_http_file_cache_manage_file 将缓存文件 信息存入缓存中。

static ngx_int_t ngx_http_file_cache_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) {     ngx_msec_t              elapsed;     ngx_http_file_cache_t   *cache;      if (ngx_http_file_cache_add_file(ctx, path) != NGX_OK) {         (void) ngx_http_file_cache_delete_file(ctx, path);     }      if (++cache->files >= cache->loader_files) {         ngx_http_file_cache_loader_sleep(cache);      } else {         ngx_time_update();          elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));          if (elapsed >= cache->loader_threshold) {             ngx_http_file_cache_loader_sleep(cache);         }     }      return (ngx_quit || ngx_terminate) ? NGX_ABORT : NGX_OK; }

对上述代码的补充说明:

  • ngx_http_file_cache_nanage_file 将缓存文件信息放到缓存中,它完成的主要工作 有:

    • 检查缓存文件的有效性 (文件名长度是否符合规则、文件大小是否满足最小缓存文 件大小要求等)

    • 将文件名中的 32 字节字符摘要转换为 16 字节二制形式。

    • 调用 ngx_http_file_cache_add 函数将此节点加入 ngx_http_file_cache_sh_t 类型的缓存管理机制中。

  • 同时,根据配置控制缓存的读取速度 ( loader_filesloader_threshold ),以便 在缓存文件很多的情况下降低初次启动时对系统资源的消耗。

到此为止,缓存磁盘文件就被加载到内存中了。

cache manager

cache manager 来讲,其 ngx_cache_manager_ctx_t 的值为 ngx_cache_manager_ctx

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

也就是说, cache manager 进程在被创建后马上开始执行 ngx_cache_manager_process_handler 回调函数。同时,根据缓存中最快过期的节点时 间设定下一次调用此回调函数的时间:

static void ngx_cache_manager_process_handler(ngx_event_t *ev) {     ...     next = 60 * 60      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);              next = (n < next) ? n : next;              ngx_time_update();         }     }      if (next == 0) {         next = 1;     }      ngx_add_timer(ev, next * 1000); }

对上述代码的补充说明:

  • 和个缓存设计的 manager 回调函数需要返回它里面最近将要过期的缓存条目距当前时 间点的间隔时长。

  • cache manager 进程检查缓存条目有效性的间隔最长为 1 个小时。并且,为了避免 占用过多 CPU, cache manger 最短检查间隔保证为 1 秒。

针对 fastcgi/proxy 模块, manager 回调函数被 ngx_http_file_cache_set_slot 函数设置为 ngx_http_file_cache_manager ,此函数的主要完成以下工作:

  • 调用 ngx_http_file_cache_expire 函数清除过期缓存条目 (删除其占用的共享内存 和对应的磁盘文件)。

  • 检查缓存磁盘目录是否超过设定大小限制。如果超限,调用函数 ngx_http_file_cache_forced_expireinactive queue 队尾开始扫描,直到找到 可以被清理的当前未使用节点 ( fcn->count == 0 且不论它是否过期) 或者查找了 20 个 节点后仍未找到符合条件的节点。

  • 两个函数 ngx_http_file_cache_expirengx_http_file_cache_forced_expire 均使用了 malloc 动态分配用于存储缓存文件路径的内存区域。

缓存文件清理过程均调用了 ngx_http_file_cache_delete 函数,并且调用它的前提条 件是当前函数已经获得了 cache->shpool->mutex 锁,同时,当前缓存节点的引用计数 为 0。下面来看一下该函数的具体逻辑:

static void ngx_http_file_cache_delete(ngx_http_file_cache_t *cache, ngx_queue_t *q,     u_char *name) {     ...     ngx_http_file_cache_node_t  *fcn;      fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);      if (fcn->exists) {         cache->sh->size -= fcn->fs_size;         ...         fcn->count++;         fcn->deleting = 1;         ngx_shmtx_unlock(&cache->shpool->mutex);         ...         ngx_delete_file(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);     } }

对上述代码的补充说明: / FIXME /

  • 由于文件删除操作 ( ngx_delete_file ) 可能发生阻塞,所以进行这个操作期间,函数 将缓存锁先释放掉,以免其它进程因为等待这个锁而阻塞。

  • count 加 1 以避免其它进程再次尝试清理此节点 (当前代码中还不会有这种情况发生)。

  • deleting 标识此缓存节点正在被删除,其它函数或进程因视其为无效节点。

  • 在缓存锁处于释放状态的过程中 (磁盘文件被删除过程中),依然可能被请求正常访问到。 这种情形下:要么因为请求造成的缓存文件打开增加了文件的引用,造成文件不会被真正 删除;要么请求打开文件时发生错误,请求将后端响应重新填充到缓存文件中。

至此,缓存文件加载和管理部分代码分析完结了。

使用

下面进入到运行时状态,再来分析一下在正常请求处理过程中,一个请求是怎样使用缓存 数据的 (同时,在分析代码过程中还需要注意的是, Nginx 允许 cache loader 进程在 加载缓存文件信息的同时响应这些对缓存的请求,这个特性用于提高 Nginx 本身的可用性)。

/* ngx_http_upstream_init_request */  if (u->conf->cache) {     ngx_int_rc;      rc = ngx_http_upstream_cache(r, u);     ... }

ngx_http_upstream_cache

就这样,所有请求缓存使用的尝试,都是通过 ngx_http_upstream_cache 函数开始的。 这个函数主要完成了以下几个功能:

  • 如果还未给当前请求分配缓存相关结构体 ( ngx_http_cache_t ) 时,创建此类型字段 ( r->cache ) 并初始化:

    /* 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` 值 */ u->cacheable = 1; /* 默认所有请求的响应结果都是可被缓存的 */ ... c = r->cache;  c->min_uses = u->conf->cache_min_uses; c->body_start = u->conf->buffer_size;   /* 后续会进行调整 */ c->file_cache = u->conf->cache->data;  c->lock = u->conf->cache_lock; c->lock_timeout = u->conf->cache_lock_timeout;  u->cache_status = NGX_HTTP_CACHE_MISS;
  • 根据配置文件中 ( fastcgi_cache_bypass ) 缓存绕过条件和请求信息,判断是否应该 继续尝试使用缓存数据响应该请求:

    /* ngx_http_upstream_cache */ switch (ngx_http_test_predicates(r, u->conf->cache_bypass)) {  case NGX_ERROR:     return NGX_ERROR;  case NGX_DECLINED:     u->cache_status = NGX_HTTP_CACHE_BYPASS;     return NGX_DECLINED;  default: /* NGX_OK */     break; }
  • 调用 ngx_http_file_cache_open 函数查找是否有对应的有效缓存数据 (该函数的其它 返回值和对应含义见下一节):

    /* ngx_http_upstream_cache */ 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 请求处理流程。

ngx_http_file_cache_open

ngx_http_file_cache_open 函数负责缓存文件定位、缓存文件打开和校验等操作,其返 回值及对应含义,以及其调用者 ngx_http_upstream_cache 对应的行为总结如下:

NGX_OK      - 缓存正常命中     - 设置 `cache_status` 为 `NGX_HTTP_CACHE_HIT`,然后向客户端发送缓存内容  NGX_HTTP_CACHE_STALE      - 缓存内容过期,当前请求需要向后端请求新的响应数据。     - 设置 `cache_status` 为 `NGX_HTTP_CACHE_EXPIRED`,并返回 `NGX_DECLINED`       以继续请求处理 (`r->cached = 0; c->valid_sec = 0`)。  NGX_HTTP_CACHE_UPDATING      - 缓存内容过期,同时己有同样使用该缓存节点的其它请求正在请求新的响应数据。     - 如果 fastcgi_cache_use_stale 启用了 "updating",设置 `cache_status` 为       `NGX_HTTP_CACHE_UPDATING`,然后向客户端发送过期缓存内容。否则,将返回       值重设为 `NGX_HTTP_CACHE_STALE`。  NGX_HTTP_CACHE_SCARCE      - 因缓存节点被查询次数还未达 `min_uses`,对此请求禁用缓存机制      - 继续请求处理,但是不再缓存其响应数据 (`u->cacheable = 0`)。  NGX_DECLINED      - 缓存内容因为不存在 (`c->exists == 0`)、缓存内容未通过校验、或者当前请       求正在更新缓存等原因,暂时无法使用缓存。     - 继续请求处理,并尝试对其响应数据进行缓存。  NGX_AGAIN      - 缓存内容过期,并且当前缓存节点正在被其它请求更新,或者 还未能从缓存文       件中读到足够的数据 (aio 模块下)。     - 返回 `NGX_BUSY`,Nginx 会再次尝试读取缓存。  NGX_ERROR      - 内存相关、文件系统等系统错误。     - 返回 `NGX_ERROR`,Nginx 会调用 `ngx_http_finalize_request` 终止此请求。  NGX_HTTP_SPECIAL_RESPONSE      - 打开 `fastcgi_intercept_errors` 配置情况下,直接返回缓存的错误码。     - 设置 `cache_status` 为 `NGX_HTTP_CACHE_HIT` 并返回错误码。

函数 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 的缓存节点:

      1. 如果该请求第一次使用此缓存节点,则增加相关引用和使用次数,继续下面条 件判断;

      2. 如果 fastcgi_cache_valid 配置指令对此节点过期时间做了特殊设定,检查 节点是否过期。如果过期,重置节点,并返回 NGX_DECLINED ; 如果未过期,返 回 NGX_OK

      3. 如果缓存文件存在 或者 缓存节点被使用次数超过 fastcgi_cache_min_uses 配置值,置 c->error = fcn->error ,并返回 NGX_OK

      4. 条件 2, 3 都不满足时,此次查找失败,返回 NGX_AGAIN

    • 如果未找到对应 c->key 的缓存节点,创建并创始化新的缓存节点,同时返回 NGX_DECLINED

  • 调用 ngx_http_file_cache_name 函数组合缓存文件完整文件名。

  • 调用 ngx_open_cached_file 函数尝试打开并获取文件缓存信息。
  • 创建用于存储 缓存文件头 的临时缓冲区 c->buf
  • 调用 ngx_http_file_cache_read 函数读取缓存文件头并进行有效性验证。

缓存读取涉及的基本函数大致分析完毕了,其中涉及 cache_lock 等诸多细节因为篇幅 原因,暂时不做分析。接下来,分析一下接到响应后,Nginx 是怎么决定是否对其进行缓 存和如何缓存的。

生成

接收上游请求并准备向下游发送响应之前, upstream 模块判断是否需要对响应进行缓存 并设置相关信息。这个过程主要在函数 ngx_http_upstream_send_response 中完成。

/* ngx_http_upstream_send_response */ switch (ngx_http_test_predicates(r, u->conf->no_cache)) { ... case NGX_DECLINED:     u->cacheable = 0;     break;  default: /* NGX_OK */      if (u->cache_status == NGX_HTTP_CACHE_BYPASS) {         ...         ngx_http_file_cache_create(r);         ...     }      break; }  if (u->cacheable) {     ...     valid = r->cache->valid_sec;      if (valid == 0) {         valid = ngx_http_file_cache_valid(u->conf->cache_valid,                                           u->headers_in.status_n);         if (valid) {             r->cache->valid_sec = now + valid;         }     }      if (valid) {         ...         r->cache->body_start = (u_short) (u->buffer.pos - u->buffer.start);          ngx_http_file_cache_set_header(r, u->buffer.start);      } else {         u->cacheable = 0;         ...     } }  if (u->cacheable == 0 && r->cache) {     ngx_http_file_cache_free(r->cache, u->pipe->temp_file); }

对上述代码的补充说明:

  • u->cacheable 用于控制是否对响应进行缓存操作。其默认值为 1,在缓存读取过程中 可因某些条件将其设置为 0,即不在缓存该请求的响应数据。

  • fastcgi_no_cache 配置指令可以使 upstream 模块不再缓存满足既定条件的请求得 到的响应。由上面 ngx_http_test_predicates 函数及相关代码完成。

  • fastcgi_cache_bypass 配置指令可以使满足既定条件的请求绕过缓存数据,但是这些 请求的响应数据依然可以被 upstream 模块缓存。

  • 缓存内容的有效时间由 fastcgi_cache_valid 配置指令设置,并且未经该指令设置的 响应数据是不会被 upstream 模块缓存的。

  • upstream 模块在申请 u->buffer 空间时,已经预先为缓存文件包头分配了空间, 所以可以直接调用 ngx_http_file_cache_set_header 在此空间中初始化缓存文件包头:

    /* ngx_http_upstream_process_header */ if (r->cache) {     u->buffer.pos += r->cache->header_start;     u->buffer.last = u->buffer.pos; }
  • 如果响应最终不需要被缓存,调用 ngx_http_file_cache_free 释放相关资源.

缓存文件的生成及其它细节和 upstream 的数据发送流程关系紧密,本篇就不再分析了。

Misc

  • 从缓存树中的节点信息得到完整的缓存 key 的步骤:

    u_char              *p; u_char              key[2 * NGX_HTTP_CACHE_KEY_LEN];  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);
  • 生成缓存文件完整路径

    void ngx_create_hashed_filename(ngx_path_t *path, u_char *file, size_t len) {     size_t      i, level;     ngx_uint_t  n;      i = path->name.len + 1;      file[path->name.len + path->len]  = '/';      for (n = 0; n < 3; n++) {         level = path->level[n];          if (level == 0) {             break;         }          len -= level;         file[i - 1] = '/';         ngx_memcpy(&file[i], &file[len], level);         i += level + 1;     } }                       | i      | path->name.len + path->len                      v        v ------------------------------------------------------------------ /path/to/cache      /10/89    /   xxxxxxxxxXXXXXXXXXXXXXXXXXX8910 ------------------------------------------------------------------ ^                   ^             ^ | path->name.len    | path->len   | 32

Category:Nginx Tagged:c/c++ devel nginx notes

Comments

正文到此结束
Loading...