nginx proxy cache的實現原理

myownstars發表於2015-01-30
除了proxy cache,nginx還可對fastcgi的響應資料進行快取,即fastcgi_cache,兩者的配置和實現原理是一致的;
 
本文以fastcgi_cache為例,描述一下其實現原理,主要參考連結

先回顧一下配置引數

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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章