nginx proxy 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所涉及的資料結構
1 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_path和cache目錄位於同一檔案系統;
由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;
...
3 ngx_http_file_cache_node_t
儲存每個快取檔案在記憶體中的描述資訊。
這些資訊需要儲存於共享記憶體中,以便多個 worker 程式共享。所以,為了提高利用率,此結構體多個欄位使用了位域 (Bit field),同時,快取 key 中用作查詢樹鍵值( ngx_rbtree_key_t ) 的部分位元組不再重複儲存:
4 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]
5 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_handler即ngx_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_lookup從ngx_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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- nginx proxy cache的配置實現Nginx
- nginx之proxy、cache、upstream模組學習Nginx
- 【Go】Golang實現gRPC的Proxy的原理GolangRPC
- LRU cache原理及go實現Go
- Nginx rewrite 規則 與 proxy_pass 實現Nginx
- LRU Cache的原理和python的實現Python
- Nginx 實現高併發的原理分析Nginx
- Nginx實現原理master和workerNginxAST
- 從零手寫實現 nginx-35-proxy_pass netty 如何實現?NginxNetty
- Linux下玩轉nginx系列(六)---nginx實現cache(快取)服務LinuxNginx快取
- docker – nginx – proxy_pass + proxy_redirectDockerNginx
- nginx proxy_pass 和 proxy_redirectNginx
- http proxy原理HTTP
- Nginx實現高速併發處理的原理詳解Nginx
- APISIX proxy-cache 外掛用法API
- nginx的pass_proxy遇到的坑Nginx
- Proxy實現vue MVVM實踐VueMVVM
- 從零手寫實現 nginx-33-http_proxy 代理驗證測試NginxHTTP
- go proxy 實現反向代理Go
- nginx的反向代理proxy_pass指令Nginx
- Nginx配置fastcgi cacheNginxAST
- Nginx 配置 fastcgi cacheNginxAST
- 從 Nginx 遷移到 Envoy ProxyNginx
- 使用 Proxy 實現簡單的 MVVM 模型MVVM模型
- 使用 proxy 實現的 mobx - dob 介紹
- Guava Cache 原理分析與最佳實踐Guava
- Buffer Cache 原理
- [Vue響應式原理]從Object.defineProperty到proxy實現觀察者機制的探索VueObject
- Nginx proxy manager反向代理docker hubNginxDocker
- nginx配置proxy_set_headerNginxHeader
- Buffer cache的執行原理
- liferay的cache的實現疑問
- Nginx的proxy_pass簡單使用記錄Nginx
- Nginx中proxy_pass的斜槓(/)問題Nginx
- 利用Proxy.newProxyInstance實現AOP
- iOS SDWebImgae Cache原理iOSWeb
- Oracle Buffer Cache原理Oracle
- 【RAC原理】Cache Fusion