http://www.tuicool.com/articles/EFfeu2
HTTP性能的問題與方案
一個最終用戶訪問一個網頁,從瀏覽器發出請求,到接受請求,時間大體上消耗在了以下幾個部分:
-
建立tcp/ip握手連接。
-
瀏覽器向服務器傳送請求數據。
-
服務器處理數據。
-
服務器返回數據。
如果用戶請求的資源很少改變,像js,css,圖片之類的靜態文件,如果每次用戶的請求都需要占用服務器資源去處理,再如果 一個用戶和服務器位於太平洋兩岸,那么,時間就被浪費在了網絡傳輸和服務器處理步驟上了。在這種情況下,應該使用cache。
像上圖一樣,在離用戶最近的地方,增加一個緩存服務器,將不常修改的靜態文件緩存起來,用戶的請求就可以直接由緩存服務器來處理,而不需要再勞煩網站服務器了。
一般緩存服務器,代理服務器和逆向代理服務器在一起,即,一個Apache Httpd上,同時開啟了緩存功能與代理功能。只是這台服務器從不同的功能角度,被稱為不同的名字,有時叫緩存服務器,有時叫代理服務器。
Http緩存機制
http緩存是遵循http協議實現的。控制緩存行為的字段均在http header中。這些字段分別在幾個方面控制着緩存:
-
有效期機制
-
重驗證機制
-
共享機制
有效期機制
控制緩存有效期的有下面幾個header,我必須將它們列出來。
頭名 | 作用域 | 描述 |
Response | 原始服務器產生響應的時間,GMT格式。必須由服務器產生,代理不允許修改此值。 | |
Response | 每一級緩存發出cache時,跟Date頭部相比,cache已經被使用的時間,以秒為單位。 1.1中Age必須出現在cache中。應有各級代理產生。 |
|
Expires | Response | 緩存過期時間,GMT格式。可由代理修改。 |
Cache-Control: max-age=xx | Request和Response | 在response中聲明此緩存保持新鮮的生命時間。 在request中發送max-age=0,效果則同no-cache。 |
Cache-Control: no-cache:這個很容易讓人產生誤解,使人誤以為是響應不被緩存。實際上Cache-Control: no-cache是會被緩存的,只不過每次在向客戶端(瀏覽器)提供響應數據時,緩存都要向服務器評估緩存響應的有效性。
Cache-Control: no-store:這個才是響應不被緩存的意思。
Pragma: no-cache:跟Cache-Control: no-cache相同,Pragma: no-cache兼容http 1.0 ,Cache-Control: no-cache是http 1.1提供的。因此,Pragma: no-cache可以應用到http 1.0 和http 1.1,而Cache-Control: no-cache只能應用於http 1.1.
計算一個緩存是否有效,只需要計算緩存的使用期(cache age)和新鮮生存期(freshness lifetime)。緩存有效的判別式為:
isValid = cache_age < freshness_lifetime;
新鮮生存期比較好計算,應該為Cache-Control:max-age的值。如果沒有Cache-Control:max-age的話,則使用Expires,如果都沒有,則使用試探時算法(略)。計算式如下:
if (cache-control_max_age_set) fressness_lifetime = Cache-Control:max-age; else if (Expires_set) freshness_lifetime = Expires - Date; else 試探算法。
為何Expires比max-age給替代了?因為Expires有可能是各級代理服務器寫入的,但Date是由原始服務器寫入的,各個服務器的時鍾可能存在差異,其准確度無法得到保證。
緩存使用期則是比較難計算的。 它不能簡單的用當前時間去減原始服務器寫入的Date ,原因是各個服務器的時鍾差異,有的可能差幾天。所以,它的計算依靠每一級代理服務器返回緩存時所帶的age頭部。對於離服務器最近的一級代理服務器,age頭部的算法應該為:
age = (response_arrive_time - request_sent_time) + (now - response_arrived_time) - 修正值; //此計算並不精確,但誤差在秒級。
那下一級代理的age算法應該為:
age = age_of_cache_returen_from_last_proxy + cache_time_saved_in_this_proxy;
遞歸下去,可以計算出每一級代理的age頭部。
重驗證機制
下面列出了幾個重要的頭部,還有一些沒有列在其中。
頭名 | 作用域 | 描述 |
Cache-Control: no-catch | Request和response | 請求必須對緩存進行重驗證。有些服務器略有出路,把no-cache與no-store混淆。 |
Pragma: no-catch | Request和response | 請求必須對緩存進行重驗證。為了向http1.0兼容。 |
Cache-Control: must-revalidate | response | 強制約束此cache在過期后,必須通過重新驗證以后才可使用。 |
Last-Modified | Response | 返回的內容上次被修改的日期的long值,精確到秒。 |
If-Modified-Since | Request | 請求時帶上過期緩存的Last-Modified值。 |
Response | 響應實體的標簽。 | |
If-None-Match | Request | 請求時帶上過期緩存的標簽。 |
以上的幾個頭部,組成了cache的重驗證機制。前3個頭部是對cache強制認證的約束,后面4個則是實體認證時所需要檢測的屬性。
需要注意no-cache和must-revalidate的區別。no-cache表明緩存在被返回之前,必須向服務器沖驗證是否被修改過。有時候,代理允許使用過期的cache,比如,服務器死機了,代理的cache雖然已經過期,但還是可以被使用。must-revalidate並不是要求cache一定要重驗證,未過期的cache不會因它的存在而去服務器重新驗證。must-revalidate是為了禁止這種行為。它要求過期的緩存一定要重驗證通過后才能使用,如果服務器死機了,那無法進行重驗證,過期的cache也就不能再被使用,用戶收到504 gateway timout。 must-revalidate對有效期內的cache並無作用。
共享機制
緩存應該存在什么地方,則由下面的三個頭部組成。
頭名 | 作用域 | 描述 |
Cache-Control: public | Response | 共享緩存。緩存可被代理服務器存儲。 |
Cache-Control: private | Response | 私有緩存,緩存不能被代理存儲,可由瀏覽器存儲。 |
Cache-Control: no-store | Request和Response | 不可存儲緩存。 |
Cache的流程圖
因為整個過程中存在很多的條件節點,時序圖並不好表示出因果關系,所以還是用流程圖更好一些。 我想了很久,才想出如何用一種更直觀的方法來畫出cache工作的流程圖。
黃色線代表request的流程,藍色線代表response的流程,紅色線代表原始服務器處理流程。
Apache Httpd搭建Cache Server
軟件需要
-
Tomcat 8
-
Apache Httpd 2.2
服務器開發
我制作一個圖片展示頁面。頁面上展示4張圖片,每張圖片的響應都包含不同的cache header。這四張圖片如下:
圖片名 | public.jpg | private.jpg | noStore.jpg | noCache.jpg |
http header | Cache-Control: public, max-age=600 Last-Modified:xxxxxx Etag:xxxxx |
Cache-Control: private, max-age=600 Last-Modified:xxxxxx Etag:xxxxx |
Cache-Control: no-store |
Cache-Control: no-cache Last-Modified:xxxxxx Etag:xxxxx |
為此,我可以專門寫一個filter,來管理這些header。Last-Modified和ETag頭會由tomcat的default servlet自動添加。我就不自己去處理了。此filter在doFilter方法上加了synchronized修飾,為了讓請求按順序執行,這樣我們就可以看清楚每個請求和返回的header都有什么。我把header都打印出來了。
@WebFilter(urlPatterns={"*.jpg"}) public class CacheFilter implements Filter { private int count = 0; public void init(FilterConfig filterConfig) throws ServletException {} public void destroy() {} public synchronized void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; String uri = req.getRequestURI(); System.out.println("["+count+++"]Accessing "+uri); printRequestHeaders(req); HttpServletResponse resp = (HttpServletResponse) response; if (uri.endsWith("public.jpg")) { resp.addHeader("Cache-Control", "max-age=600,public"); } if (uri.endsWith("private.jpg")) { resp.addHeader("Cache-Control", "max-age=600,private"); } if (uri.endsWith("noStore.jpg")) { resp.addHeader("Cache-Control", "no-store"); } if (uri.endsWith("noCache.jpg")) { resp.addHeader("Cache-Control", "no-cache"); } chain.doFilter(request, response); printResponseHeaders(resp); } private void printRequestHeaders(HttpServletRequest request) { System.out.println("Request headers:"); Enumeration<String> names = request.getHeaderNames(); while (names.hasMoreElements()) { String name = names.nextElement(); Enumeration<String> values = request.getHeaders(name); while (values.hasMoreElements()) { System.out.println(name + " : " + values.nextElement()); } } System.out.println(); } private void printResponseHeaders(HttpServletResponse response) { System.out.println("Response headers:"); Collection<String> names = response.getHeaderNames(); for (String name : names) { Collection<String> values = response.getHeaders(name); for (String value : values) { System.out.println(name + " : " + value); } } System.out.println("**********************************************"); } }
創建一個web app, 名叫cache。將4張圖片復制到webapp/img/目錄下。創建上面的filter。
最后在index.html上面展示:
<html> <body> <div> <img src="/cache/img/public.jpg"/> </div> <div> <img src="/cache/img/private.jpg"/> </div> <div> <img src="/cache/img/noStore.jpg"/> </div> <div> <img src="/cache/img/noCache.jpg"/> </div> </body> </html>
打包,並部署到tomcat服務器。
緩存服務器開發
自從Apache 2.2開始,cache_module已經成為非常穩定模塊。它完全遵循HTTP協議中的cache規定。所以我們可以使用cache_module來做實現Apache Httpd的緩存管理。
cache_module是由兩個實現模塊mem_cache_module和disk_cache_module所支持的。其中mem_cache_module是緩存於內存中,disk_cache_module則存於硬盤上。各種詳細的命令可以參照Apache手冊。本實驗,只使用cache_module和disk_cache_module來管理HTTP緩存。只涉及到了部分命令。
修改Apache下的httpd.cnf
LoadModule cache_module modules/mod_cache.so LoadModule disk_cache_module modules/mod_disk_cache.so LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
然后修改Apache下的httpd-vhosts.conf。
NameVirtualHost *:80 <VirtualHost *:80> ServerAdmin joey ServerName www.test.com ErrorLog "logs/cache.log" CustomLog "logs/accessCache.log" common # 開啟debug LogLevel debug ProxyRequests Off ProxyPreserveHost On ProxyPass / ajp://localhost:9009/ ProxyPassReverse / ajp://localhost:9009/ # 如果header中沒有max-age或者expires,則添加默認過期時間,默認1小時. CacheDefaultExpire 3600 CacheRoot D:/share/tmp CacheEnable disk /cache CacheDirLevels 5 CacheDirLength 3 UseCanonicalName On </VirtualHost>
在apache2.2中,cache的log還不完善。如果使用2.4版本,可以開啟關於cache命中決策的log。2.2中,只能通過在ErrorLog中搜索mod_cache.c關鍵字,查看cache的處理日志。
檢測結果
使用檢測工具為Fiddler2, 一個可以捕獲所有瀏覽器對外交流的軟件。還可以使用Firebug, chrome debug tool。
在執行下面步驟之前,必須確保明白一件重要的事情,下面所有的步驟都是在瀏覽器地址欄輸入地址后,按enter鍵產生的。 不可以使用刷新鍵。
-
(ie)用ie首次訪問 http://www.test.com/cache
在代理服務器端查看cache.log,或cache文件夾,可以發現代理服務器對4張圖片的處理:
Name public.jpg private.jpg noStore.jpg noCache.jpg Action request to tomcat
status 200request to tomcat
status 200request to tomcat
status 200request to tomcat
status 200Handle cache cached
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: privatenot cached
reason: no-storecached
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT瀏覽器端對4張圖片的處理:
Name public.jpg private.jpg noStore.jpg noCache.jpg Response status 200 200 200 200 Handle cache cached
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTcached
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: no-storecached
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT -
(ie,10分鍾內)在瀏覽器緩存和代理緩存均未過期,chrome訪問http://www.test.com/cache
代理服務器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Action n/a request to tomcat
status 200revalidate to tomcat
status 304Handle cache n/a n/a not cached
reason: no-storeReplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT瀏覽器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Response status use cache directly use cache directly 200 304 Handle cache n/a n/a not cached
reason: no-storeReplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT -
(chrome,10分鍾內)在代理緩存新鮮期內,換一個瀏覽器,chrome訪問http://www.test.com/cache
代理服務器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Action cache is fresh.
use cache.request to tomcat
status 200request to tomcat
status 200revalidate to tomcat
status 304Handle cache not cached
reason: privatenot cached
reason: no-storeReplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT瀏覽器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Response status 200 200 200 200 Handle cache cached
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTcached
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: no-storecached
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT -
(chrome,10分鍾后)在10分鍾后,代理的緩存和瀏覽器的緩存都過期了。chrome訪問 http://www.test.com/cache
代理服務器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Action revalidate to tomcat
status 304revalidate to tomcat
status 304request to tomcat
status 200revalidate to tomcat
status 304Handle cache Replace cache header
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: privatenot cached
reason: no-storeReplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT瀏覽器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Response status 304 304 200 304 Handle cache replace cache header
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTreplace cache header
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: no-storereplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT -
(ie,在第4步后,立刻)這時候,ie的緩存已經過期,但proxy端的緩存是新的。
代理服務器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Action cache is fresh,
use cacherevalidate to tomcat
status 304request to tomcat
status 200revalidate to tomcat
status 304Handle cache not cached
reason: privatenot cached
reason: no-storeReplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMT瀏覽器端:
Name public.jpg private.jpg noStore.jpg noCache.jpg Response status 304 304 200 304 Handle cache replace cache header
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTreplace cache header
max-age=600
Etag: XXXXX
Last-Modified: XXXX GMTnot cached
reason: no-storereplace cache header
max-age=0
Etag: XXXXX
Last-Modified: XXXX GMTapache 緩存配置
http://kinglord2010.iteye.com/blog/1442868 -
硬盤緩存:mod_disk_cache,依賴 mod_cache 模塊
內存緩存:mod_mem_cache,依賴 mod_cache 模塊
文件緩存:mod_file_cache 搭配 mod_mem_cache 模塊使用
1、硬盤緩存:配置例子:
- <IfModule mod_disk_cache.c>
- CacheDefaultExpire 86400
- CacheEnable disk /
- CacheRoot /tmp/apacheCache
- CacheDirLevels 5
- CacheDirLength 5
- CacheMaxFileSize 1048576
- CacheMinFileSize 10
- </IfModule>
CacheDefaultExpire: 設定緩存過期的時間(秒),默認是1小時,只有當緩存的文檔沒有設置過期時間或最后修改時間時這個指令才會生效
CacheEnable:啟用緩存,第1個參數是緩存類型,第2個參數是緩存路徑,指的是 url 路徑,這里是緩存所有的東西,直接寫上“/”即可,如“/docs”則只緩存 /docs 下的所有文件
CacheRoot:緩存文件所在的目錄,運行 Apache 的用戶(如daemon 或 nobody)要能對其進行讀寫,如果不清楚的話可以直接設置成 777,請手動建立該目錄並設置好訪問權限
CacheDirLevels:緩存目錄的深度,默認是3,這里設置為5
CacheDirLength:緩存目錄名的字符長度,默認是4,這里設置為5
CacheMaxFileSize 和 CacheMinFileSize :緩存文件的最大值和最小值(byte),當超過這個范圍時將不再緩存,這里設置為 1M 和 10bytes
2、內存緩存:- <IfModule mod_mem_cache.c>
- CacheEnable mem /
- MCacheMaxObjectCount 20000
- MCacheMaxObjectSize 1048576
- MCacheMaxStreamingBuffer 65536
- MCacheMinObjectSize 10
- MCacheRemovalAlgorithm GDSF
- MCacheSize 131072
- </IfModule>
CacheEnable:啟用緩存,使用基於內存的方式存儲
MCacheMaxObjectCount:在內存中最多能存儲緩存對象的個數,默認是1009,這里設置為20000
MCacheMaxObjectSize:單個緩存對象最大為 1M,默認是10000bytes
MCacheMaxStreamingBuffer:在緩沖區最多能夠放置多少的將要被緩存對象的尺寸,這里設置為 65536,該值通常小於100000或 MCacheMaxObjectSize 設置的值
MCacheMinObjectSize:單個緩存對象最小為10bytes,默認為1bytes
MCacheRemovalAlgorithm:清除緩存所使用的算法,默認是 GDSF,還有一個是LRU,可以查一下 Apache 的官方文檔,上面有些介紹
MCacheSize:緩存數據最多能使用的內存,單位是 kb,默認是100kb,這里設置為128M3、 文件緩存:
#緩存內容
MMapFile /var/www/html/index.html /var/www/html/articles/index.html
#只緩存文件的句柄
CacheFile /var/www/html/index.html /var/www/html/articles/index.html
信息參考:http://httpd.apache.org/docs/2.2/caching.html