Nginx模塊fastcgi_cache的幾個注意點 轉

Nginx模塊fastcgi_cache的幾個注意點

 

去年年末,我對nginx的fastcgi_cache進行摸索使用。在個人測試過程當中,發現一些wiki以及網絡上沒被提到的注意點,這裏分享一下。php

 

在web項目中,你們都已經很是熟悉其架構流程了。都說Cache是萬金油,哪裏不舒服抹哪裏。這些流程中,幾乎每一個環節都會進行cache。從瀏覽器到webserver,到cgi程序,到DB數據庫,會進行瀏覽器cache,數據cache,SQL查詢的cache等等。對於fastcgi這裏的cache,不多被使用。去年年末,我對nginx的fastcgi_cache進行摸索使用。在個人測試過程當中,發現一些wiki以及網絡上沒被提到的注意點,這裏分享一下。html

從瀏覽器到數據庫的流程圖nginx

這裏是個人NGinx配置信息git

  1. #增長調試信息  
  2. add_header X-Cache-CFC "$upstream_cache_status - $upstream_response_time";  
  3. fastcgi_temp_path /dev/shm/nginx_tmp;  
  4.    
  5. #cache設置  
  6. fastcgi_cache_path   /dev/shm/nginx_cache  levels=1:2 keys_zone=cfcache:10m inactive=50m;  
  7. fastcgi_cache_key "$request_method://$host$request_uri";  
  8. fastcgi_cache_methods GET HEAD;  
  9. fastcgi_cache   cfcache;  
  10. fastcgi_cache_valid   any 1d;  
  11. fastcgi_cache_min_uses  1;  
  12. fastcgi_cache_use_stale error  timeout invalid_header http_500;  
  13. fastcgi_ignore_client_abort on; 

配置這些參數時,注意每一個參數的做用域,像fastcgi_cache_path參數,只能在http配置項裏配置,而 fastcgi_cache_min_uses這個參數,能夠在http、server、location三個配置項裏配置。這樣更靈活的會每一個域名、每一個匹配的location進行選擇性cache了。具體的參數做用域,參考FASTCGI模塊的官方WIKI。我爲了調試方便,添加了一個『X-Cache-CFC』的http響應頭,$upstream_cache_status 變量表示此請求響應來自cache的狀態,分別爲:github

·MISS 未命中web

·EXPIRED – expired, request was passed to backend Cache已過時數據庫

·UPDATING – expired, stale response was used due to proxy/fastcgi_cache_use_stale updating Cache已過時,(被其餘nginx子進程)更新中ubuntu

·STALE – expired, stale response was used due to proxy/fastcgi_cache_use_stale Cache已過時,響應數據不合法,被污染後端

·HIT 命中cache瀏覽器

 

FASTCGI_CACHE $upstream_cache_status 結果爲miss,一次也沒命中

程序代碼是Discuz!論壇, 隨便開啓測試了幾下,發現/dev/shm/nginx_cache/下沒有任何目錄創建,也沒有文件建立。調試的http header響應頭裏的X-Cache-CFC 結果一直是MISS。從服務器進程上來看,Nginx cache manager process 跟Nginx cache loader process 進程也正常運行:

  1. root      3100     1  0 14:52 ?        00:00:00 nginx: master process /usr/sbin/nginx  
  2. www-data  3101  3100  0 14:52 ?        00:00:00 nginx: worker process  
  3. www-data  3102  3100  0 14:52 ?        00:00:00 nginx: cache manager process  
  4. www-data  3103  3100  0 14:52 ?        00:00:00 nginx: cache loader process 

不知道爲什麼會這樣,爲什麼沒有cache成功,我覺得我配置參數有問題,只好閱讀WIKI。發現fastcgi_ignore_headers 參數下解釋有這麼一段

 

fastcgi_ignore_headers
Syntax: fastcgi_ignore_headers field …
Default:
Context: http
server
location
Reference: fastcgi_ignore_headers

This directive forbids processing of the named headers from the FastCGI-server reply. It is possible to specify headers like 「X-Accel-Redirect」, 「X-Accel-Expires」, 「Expires」 or 「Cache-Control」.
 

 

也就是說這個參數的值,將會被忽略掉,一樣被忽略掉的響應頭好比」X-Accel-Redirect」, 「X-Accel-Expires」, 「Expires」 or 「Cache-Control」,而nginx配置中並無fastcgi_ignore_headers參數的設定,那麼問題會不會出如今 FASTCGI響應結果裏包含了相似」X-Accel-Redirect」, 「X-Accel-Expires」, 「Expires」 or 「Cache-Control」這幾個響應頭呢?用strace抓包,看了下nginx與fpm進程通信的數據

  1. ####爲了確保準確抓處處理該http請求的進程,我把nginx 、fpm都只開啓了一個進程處理。  
  2. //strace -ff -tt -s 1000 -o xxx.log -p PHPFPM-PID  
  3. 14:52:07.837334 write(3, "\1\6\0\1\0\343\5\0X-Powered-By: PHP/5.3.10-1ubuntu3.5\r\nExpires: Thu, 19 Nov 1981 08:52:00 GMT\r\nCache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\nPragma: no-cache\r\nContent-type: text/html\r\n\r\nHello cfc4n1362034327\0\0\0\0\0\1\3\0\1\0\10\0\0\0\0\0\0\0\0\0\0", 256) = 256  
  4.    
  5. //strace -ff -tt -s 1000 -o xxx.log -p Nginx-PID  
  6. 15:05:13.265663 recvfrom(12, "\1\6\0\1\0\343\5\0X-Powered-By: PHP/5.3.10-1ubuntu3.5\r\nExpires: Thu, 19 Nov 1981 08:52:00 GMT\r\nCache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\nPragma: no-cache\r\nContent-type: text/html\r\n\r\nHello cfc4n1362035113\0\0\0\0\0\1\3\0\1\0\10\0\0\0\0\0\0\0\0\0\0", 4023, 0, NULL, NULL) = 256 

從抓取的數據包裏能夠看到,fpm確實返回了包含「Expires」、「Cache-Control」頭的http 響應頭信息。那麼疑問來了:

·nginx的fastcgi_cache沒緩存這條http響應,是由於響應頭裏包含「Expires」、「Cache-Control」的緣由嗎?

·程序裏並無輸出「Expires」、「Cache-Control」 http header的代碼,這是誰輸出的呢?

·既然是fpm響應的時候,就已經有了,那麼是php的core模塊,仍是其餘拓展模塊輸出的?

·「Expires:」時間爲什麼是「Thu, 19 Nov 1981 08:52:00 GMT」?

疑問比較多,一個一個查起,先從Nginx的fastcgi_cache沒緩存這條http響應查起。我根據測試環境nginx版本 1.1.9(ubuntu 12.04默認的),到nginx官方下了對應版本的源碼,搜索了fastcgi參數使用的地方,在httpngx_http_upstream.c找到了。雖然不能很流程的讀懂nginx的代碼,但粗略的瞭解,根據瞭解的狀況加以猜想,再動手測試實驗,也得出告終論,肯定了nginx的 fastcgi_cache的規則。

  1. //ngx_http_upstream.c  
  2. //line 3136  當fastcgi響應包含set-cookie時,不緩存  
  3. static ngx_int_t  
  4. ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h,  
  5.     ngx_uint_t offset)  
  6. {  
  7. #if (NGX_HTTP_CACHE)  
  8.     ngx_http_upstream_t  *u;  
  9.    
  10.     u = r->upstream;  
  11.    
  12.     if (!(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_SET_COOKIE)) {  
  13.         u->cacheable = 0;  
  14.     }  
  15. #endif  
  16.    
  17.     return NGX_OK;  
  18. }  
  19.    
  20. //line 3242 當響應頭包含Expires時,若是過時時間大於當前服務器時間,則nginx_cache會緩存該響應,不然,則不緩存  
  21. static ngx_int_t  
  22. ngx_http_upstream_process_expires(ngx_http_request_t *r, ngx_table_elt_t *h,  
  23.     ngx_uint_t offset)  
  24. {  
  25.     ngx_http_upstream_t  *u;  
  26.    
  27.     u = r->upstream;  
  28.     u->hheaders_in.expires = h;  
  29.    
  30. #if (NGX_HTTP_CACHE)  
  31.     {  
  32.     time_t  expires;  
  33.    
  34.     if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_EXPIRES) {  
  35.         return NGX_OK;  
  36.     }  
  37.    
  38.     if (r->cache == NULL) {  
  39.         return NGX_OK;  
  40.     }  
  41.    
  42.     if (r->cache->valid_sec != 0) {  
  43.         return NGX_OK;  
  44.     }  
  45.    
  46.     expires = ngx_http_parse_time(h->value.data, h->value.len);  
  47.    
  48.     if (expires == NGX_ERROR || expires ngx_time()) {         u->cacheable = 0;  
  49.         return NGX_OK;  
  50.     }  
  51.    
  52.     r->cache->valid_sec = expires;  
  53.     }  
  54. #endif  
  55.    
  56.     return NGX_OK;  
  57. }  
  58.    
  59. //line 3199  當響應頭包含Cache-Control時,#####若是####這裏有若是啊。。。  
  60. //【注意】若是Cache-Control參數值爲no-cache、no-store、private中任意一個時,則不緩存...不緩存...  
  61. //【注意】若是Cache-Control參數值爲max-age時,會被緩存,且nginx設置的cache的過時時間,就是系統當前時間 + mag-age的值  
  62.     if (ngx_strlcasestrn(p, last, (u_char *) "no-cache", 8 - 1) != NULL  
  63.         || ngx_strlcasestrn(p, last, (u_char *) "no-store", 8 - 1) != NULL  
  64.         || ngx_strlcasestrn(p, last, (u_char *) "private", 7 - 1) != NULL)  
  65.     {  
  66.         u->cacheable = 0;  
  67.         return NGX_OK;  
  68.     }  
  69.    
  70.     p = ngx_strlcasestrn(p, last, (u_char *) "max-age=", 8 - 1);  
  71.    
  72.     if (p == NULL) {  
  73.         return NGX_OK;  
  74.     }  
  75.     ...  
  76.     r->cache->valid_sec = ngx_time() + n; 

也就是說,fastcgi響應http請求的結果中,響應頭包括Expires、Cache-Control、Set-Cookie三個,都會可能不被cache,但不僅有這些,別忘了nginx配置中fastcgi_ignore_headers參數設定的部分。以及ngxin的X-ACCEL X- Accel-Redirect、X-Accel-Expires、X-Accel-Charset、X-Accel-Buffering等nginx自定義的響應頭。因爲這幾個不經常使用,我也沒深刻研究。經過對nginx的ngx_http_upstream模塊代碼模糊理解,加猜想,以及寫了腳本測試驗證,能夠獲得結論是正確的。即Nginx fastcgi_cache在緩存後端fastcgi響應時,當響應裏包含「set-cookie」時,不緩存;當響應頭包含Expires時,若是過時時間大於當前服務器時間,則nginx_cache會緩存該響應,不然,則不緩存;當響應頭包含Cache-Control時,若是Cache- Control參數值爲no-cache、no-store、private中任意一個時,則不緩存,若是Cache-Control參數值爲max- age時,會被緩存,且nginx設置的cache的過時時間,就是系統當前時間 + mag-age的值。

nginx fastcgi_cache 響應expired

 

nginx fastcgi_cache hit命中

 

FASTCGI_CACHE $upstream_cache_status 結果爲miss,一次也沒命中。

  1. //逐個測試,測試時,註釋其餘的  
  2. header("Expires: ".gmdate("D, d M Y H:i:s", time()+10000).' GMT');  
  3. header("Expires: ".gmdate("D, d M Y H:i:s", time()-99999).' GMT');  
  4. header("X-Accel-Expires:30");  
  5. header("Cache-Control: no-cache");  
  6. header("Cache-Control: no-store");  
  7. header("Cache-Control: private");  
  8. header("Cache-Control: max-age=10");  
  9. setcookie('cfc4n',"testaaaa");  
  10. echo 'Hello cfc4n',time(); 

到了這裏,疑問1解決了。那麼疑問二、3呢?程序裏並無輸出「Expires」、「Cache-Control」 http header的代碼,這是誰輸出的呢?既然是fpm響應的時候,就已經有了,那麼是php的core模塊,仍是其餘拓展模塊輸出的?我精簡了代碼,只輸出一個「hello world」,發現也確實被緩存了。顯然,php腳本程序中並沒輸出http header 的「Expires」、「Cache-Control」,屢次測試,最終定位到session_start函數,翻閱源碼找到了這些代碼:

  1. //ext/session/session.c  line:1190 左右  
  2. // ...  
  3. CACHE_LIMITER_FUNC(private) /* {{{ */  
  4. {  
  5.     ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");  
  6.     CACHE_LIMITER(private_no_expire)(TSRMLS_C);  
  7. }  
  8. /* }}} */  
  9. //再到這裏3 或者上面幾個 ##默認是nocache  
  10. CACHE_LIMITER_FUNC(nocache) /* {{{ */  
  11. {  
  12.     ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");  
  13.    
  14.     /* For HTTP/1.1 conforming clients and the rest (MSIE 5) */  
  15.     ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");  
  16.    
  17.     /* For HTTP/1.0 conforming clients */  
  18.     ADD_HEADER("Pragma: no-cache");  
  19. }  
  20. /* }}} */  
  21. //這裏2  
  22. static php_session_cache_limiter_t php_session_cache_limiters[] = {  
  23.     CACHE_LIMITER_ENTRY(public)  
  24.     CACHE_LIMITER_ENTRY(private)  
  25.     CACHE_LIMITER_ENTRY(private_no_expire)  
  26.     CACHE_LIMITER_ENTRY(nocache)  
  27.     {0}  
  28. };  
  29.    
  30. static int php_session_cache_limiter(TSRMLS_D) /* {{{ */  
  31. {  
  32.     php_session_cache_limiter_t *lim;  
  33.    
  34.     if (PS(cache_limiter)[0] == '\0') return 0;  
  35.    
  36.     if (SG(headers_sent)) {  
  37.         const char *output_start_filename = php_output_get_start_filename(TSRMLS_C);  
  38.         int output_start_lineno = php_output_get_start_lineno(TSRMLS_C);  
  39.    
  40.         if (output_start_filename) {  
  41.             php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cache limiter - headers already sent (output started at %s:%d)", output_start_filename, output_start_lineno);  
  42.         } else {  
  43.             php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cache limiter - headers already sent");  
  44.         }  
  45.         return -2;  
  46.     }  
  47.    
  48.     for (lim = php_session_cache_limiters; lim->name; lim++) {  
  49.         if (!strcasecmp(lim->name, PS(cache_limiter))) {  
  50.             lim->func(TSRMLS_C);   //這裏1  
  51.             return 0;  
  52.         }  
  53.     }  
  54.    
  55.     return -1;  
  56. }  
  57. // ... 

到了這裏,知道緣由了,是程序調用session_start時,php的session拓展本身輸出的。session.cache_limit 參數來決定輸出包含哪一種Expires的header,默認是nocache,修改php.ini的session.cache_limit參數爲 「none」便可讓session模塊再也不輸出這些http 響應頭。或在調用session_start以前,使用session_cache_limiter函數來指定下該參數值。那爲何要在使用 session時,發Expires、Cache-Control的http response header呢?我猜想了下,須要session時,基本上是用戶跟服務器有交互,那麼,既然有交互,就意味着用戶的每次交互結果也可能不同,就不能 cache這個請求的結果,給返回給這個用戶。同時,每一個用戶的交互結果都是不同的,nginx也就不能把包含特殊Cache-Control的我的響應cache給其餘人提供了。

還有一個無聊的問題「Expires:時間爲什麼是Thu, 19 Nov 1981 08:52:00 GMT」?我翻閱了session.c這段代碼的添加時間,版本,做者信息,在php官方版本庫中找到了此次提交的信息

 

Revision 17092 – (view) (download) (as text) (annotate) – [select for diffs]
Modified Sun Dec 12 14:16:55 1999 UTC (13 years, 2 months ago) by sas
File length: 28327 byte(s)
Diff to previous 16964
Add cache_limiter and cache_expire options. Rename extern_referer_check
to referer_check.

 

對比session.c兩個版本的變動,果真是這塊代碼。做者是sas,也就是Sascha Schumann, http://php.net/credits.php裏能夠看到他的大名。關於這個expires過時時間的問題,有人在stackoverflow也提問過,Why is 「Expires」 1981?,別人說那天是他生日。這是真的麼?若是那天是他生日的話,而他增長session.cache_limiter時是1999年,他才17歲,17歲呀。我17歲時在幹嗎?還不知道電腦長啥樣,正在玩『超級瑪麗』呢。

好奇的不是我一我的,還有個帖子是epoch date — Expires: Thu, 19 Nov 1981 08:52:00也問了。另外兩個地址雖然沒問,也有人提到那天是他生日了。http://boinc.berkeley.edu/dev/forum_thread.php?id=2514https://github.com/codeguy/Slim/issues/157,這些帖子都提到說原帖是http://www.phpbuilder.com/lists/php3-list/199911/3159.php ,我沒法訪問,被跳轉到首頁了。用http://web.archive.org找到了歷史快照,發現上下文關係不大,也不能證實是他生日。 我更是好奇的發了兩封郵件到他的不一樣郵箱裏問他,不過,目前他還沒回復。或許他沒收到、沒看到,或許懶得回了。N年後,「Expires:時間爲什麼是Thu, 19 Nov 1981 08:52:00 GMT」這個日期,會不會又成了一段奇聞佳話了呢?

原文連接:http://www.cnxct.com/several-reminder-in-nginx-fastcgi_cache-and-php-session_cache_limiter/

相關文章
相關標籤/搜索