Nginx系列(二十一):HTTP Cache机制

一、配置

指令 含义
proxy_cache_path /data/nginx/tmp-test levels=1:2 keys_zone=tmp-test:100m inactive=7d max_size=1000g;
proxy_cache_path 缓存文件路径
levels 设置缓存文件目录层次;levels=1:2 表示两级目录
keys_zone 设置缓存名字和共享内存大小
inactive 在指定时间内没人访问则被删除
max_size 最大缓存空间,如果缓存空间满,默认覆盖掉缓存时间最长的资源。每一个proxy_cache_path对应一个ngx_http_file_cache_t结构体。
proxy_cache tmp-test 使用名为tmp-test的缓存配置
proxy_cache_key $uri 定义缓存唯一key,通过唯一key来进行hash存取
proxy_cache_methods 设置缓存哪些HTTP方法
proxy_cache_min_uses 指定请求至少被发送了多少次以上时才缓存,可以防止低频请求被缓存
proxy_cache_bypass 如果指定的任何一个变量值不为空,或者不等于0,nginx就不会查找缓存,直接进行代理转发
proxy_cache_lock/proxy_cache_lock_timeout 当多个客户端同时请求同一份内容时,如果开启proxy_cache_lock(默认off)则只有一个请求被发送至后端;其他请求将等待该内容返回;当第一个请求返回时,其他请求将从缓存中获取内容返回;当第一个请求超过了proxy_cache_lock_timeout超时时间(默认5s),则其他请求将同时请求到后端来获取响应,且响应不会被缓存;启用proxy_cache_lock可以应对雪崩效应。

二、数据结构和变量

Nginx系列(二十一):HTTP Cache机制_第1张图片

  • 节点红黑树(cache->sh->rbtree):核心数据结构,以key的md5值前四位作为索引形成红黑树,表示所有存在的资源节点。

  • 节点超时队列(cache->sh->queue):LRU类型数据结构,manager进程定时触发,根据fcn->expire = now + cache->inactive判断节点是否超时,超时则对节点进行释放。

  • cache->watermark:当缓存节点分配失败时,将对cache->sh->watermark设置为当前节点总数的7/8,当manager进程触发后,将根据LRU算法对超过watermark数量的节点进行释放。

  • c->lock_timeout:通常情况下,当前请求的缓存不存在,而其它请求正在更新该缓存时,当前缓存启动c->wait_event定时器,定时时间为c->lock_timeout,等待其它请求完成缓存。

  • c->lock_age:当某请求执行节点缓存更新操作时,会记录fcn->lock_time = now+c->lock_age,在该事件范围内,其它对该资源访问的请求会被阻塞等待,而超过该事件范围后,后续对该资源节点的请求将直接溯源并缓存。

  • c->stale_updating/c->stale_error:首先需要说明HTTP响应中stale-while-revalidate和stale-if-error的含义,当用户访问的资源过期时(超过cache-control:max-age),为了避免过高响应延迟,如果当前时间仍在max-age + stale-while-revalidate范围内,那么代理将返回给用户过期资源,同时后台发起资源更新。stale-if-error的含义也是类似,只是对于过期资源,将优先溯源,在溯源失败的情况下,如果当前时间仍在max-age + stale-if-error范围内,那么允许返回给用户过期资源。即使服务端响应中不携带这两种字段,通过使能nginx配置项proxy_cache_use_stale也可以起到同样的效果。

  • fcn->valid_sec:当响应到来时,将根据响应头的cache-control:max-age、proxy_cache_valid配置等信息对资源的有效期进行设置,当资源失效后,通常需对资源进行溯源更新。

三、请求处理

Nginx系列(二十一):HTTP Cache机制_第2张图片

  • ngx_http_upstream_cache

      /*根据proxy_cache,查找全局的缓存配置proxy_cache_path,找到相应配置*/
      rc = ngx_http_upstream_cache_get(r, u, &cache);
       
      /*创建ngx_http_cache_t结构体(r->cache)*/
      if (ngx_http_file_cache_new(r) != NGX_OK) {
      return NGX_ERROR;
      }
       
      /*实际调用ngx_http_proxy_create_key函数,创建key值(r->cache->keys)*/
      if (u->create_key(r) != NGX_OK) {
      return NGX_ERROR;
      }
       
      /*根据r->cache->keys的值,生成16字节长的crc32值(r->cache->crc32)和md5值(r->cache->key和r->cache->main)*/
      ngx_http_file_cache_create_key(r);
       
      /*查询proxy_cache_bypass的配置,确认该请求是否不从缓存获取而是从后端获取*/
      switch (ngx_http_test_predicates(r, u->conf->cache_bypass))
       
      rc = ngx_http_file_cache_open(r);
      /*
      缓存已过期(valid_sec)情况下,可能返回NGX_HTTP_CACHE_STALE或NGX_HTTP_CACHE_UPDATING。
      如果返回NGX_HTTP_CACHE_STALE,表示需要本请求去后端更新。但在cache_use_stale为updating和cache_background_update使能的情况下,允许发送过期缓存(返回NGX_OK),同时创建子请求用于缓存更新。
      如果返回NGX_HTTP_CACHE_UPDATING,表示已经有请求去后端更新,但在cache_use_stale为updating情况下,允许发送过期缓存(返回NGX_OK)。*/
      switch (rc){
       
      }
    
  • ngx_http_file_cache_open

      /*判断是否已经存在缓存*/
      rc = ngx_http_file_cache_exists(cache, c);
      
      /*生成缓存文件路径:/path/levels/key*/
      ngx_http_file_cache_name(r, cache->path)
       
      /*
      1. 节点不存在。如果load进程已经加载过缓存,则不需要再读取,否则需要尝试读取缓存文件
      2. 节点已存在。如果缓存文件不存在(如cache_lock情况),则不需要
      读取。*/
      if (!test) {
          goto done;
      }
       
      /*缓存文件存在的流程*/
      return ngx_http_file_cache_read(r, c);
       
      /*缓存文件不存在的流程*/
      /*1. 对于未向后端请求(updating)或后端请求超时(lock_age)的节点,返回NGX_DECLINED,表示重新发起请求并缓存。
      2.否则,使能lock_timeout定时器,返回NGX_AGAIN,处理结束,等待节点可用并获取缓存文件。*/
      done:
               return ngx_http_file_cache_lock(r, c);
    
  • ngx_http_file_cache_exists

      /*加锁*/
      ngx_shmtx_lock(&cache->shpool->mutex);
       
      /*根据请求生成r->cache->key值,从红黑树查找相应节点*/
      fcn = c->node;
      if (fcn == NULL) {
      fcn = ngx_http_file_cache_lookup(cache, c->key);
      }
       
      if (fcn) {
      /*代表着cache文件已经存在,可以直接获取,否则返回NGX_AGAIN,
      上层返回NGX_HTTP_CACHE_SCARCE,表示该节点的请求次数未达到min_uses,
      不被cache,需从后端获取。*/
          if (fcn->exists || fcn->uses >= c->min_uses) {
       
              c->exists = fcn->exists;
              if (fcn->body_start) {
                  c->body_start = fcn->body_start;
              }
       
              rc = NGX_OK;
       
              goto done;
          }
          rc = NGX_AGAIN;
       
          goto done;
      }
       
      /*如果不存在缓存,则分配一个ngx_http_file_cache_node_t*/
      fcn = ngx_slab_calloc_locked(cache->shpool,
                                   sizeof(ngx_http_file_cache_node_t));
       
      /*1. r->cache->key的前4个字节,作为红黑树的key。后8个字节 ,作为fcn的key。
          2. 节点加入红黑树。
          3. 根据inactive设置节点超时时间,加入超时队列。
          4. 返回NGX_DECLINED代表节点新创建。*/
    
    • 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_BUSY:请求cache_lock,等待缓存生成,返回。
    NGX_OK:缓存文件获取成功,发送缓存。
    NGX_DECLINED:需要向后端发送请求,可能缓存也可能不缓存。

四、响应体接收

  • ngx_http_upstream_send_response

      valid = r->cache->valid_sec;
      //获取该节点的有效时间,如果valid为0,则不缓存,在查询时如果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;
          }
      }
       
      /*将文件部分内容(ngx_http_file_cache_header_t + ngx_http_file_cache_key + r->cache->keys)写入u->buffer*/
      ngx_http_file_cache_set_header(r, u->buffer.start)
       
      /*如果需要缓存,那么上面所保存的结构体、响应头等内容也将写入文件,而不只是响应包体*/
      if (u->cacheable) {
          p->buf_to_file = ngx_calloc_buf(r->pool);
          p->buf_to_file->start = u->buffer.start;
          p->buf_to_file->pos = u->buffer.start;
      }
    
  • ngx_event_pipe_read_upstream

      /*响应包体通过pipe机制写入临时文件*/
      if (p->cacheable && (p->in || p->buf_to_file)) {
          rc = ngx_event_pipe_write_chain_to_temp_file(p);
          if (rc != NGX_OK) {
              return rc;
          }
      }
    

四、响应体接收结束(ngx_http_upstream_process_request)

  • ngx_http_file_cache_update

      /*将tmp文件拷贝成缓存文件*/
      rc = ngx_ext_rename_file(&tf->file.name, &c->file.name, &ext);
       
      /*更新节点的uniq、exists、updating等信息,更新文件大小(c->node->fs_size)、整体缓存大小(cache->sh->size)等信息*/
    
  • manager进程(ngx_http_file_cache_manager)

      /*
      1.清理未被引用的节点;
      2.刷新被引用的节点;
      3.处理文件数超过manager_files或处理时间超过manager_threshold,则返回。*/
      next = (ngx_msec_t) ngx_http_file_cache_expire(cache) * 1000;
       
      for ( ;; ){
      /*缓存文件大小超过最大值或个数超过watermark,则对老的节点进行强制释放。*/
      }
       
      /*返回一个时间估计值,用于设置下一次manager超时时间。*/
    

你可能感兴趣的:(Nginx)