HTTP緩存策略
http協議是什么?
HTTP協議(超文本傳輸協議),簡單來說就是一種網絡傳輸協議, 瀏覽器請求服務器獲取內容就是基于http協議或者https協議。 使得計算機可以在瀏覽器和服務器之間傳輸文字、圖片、二進制、音頻、視頻等資源。
既然http負責傳輸資源,那么緩存是必不可少的
http緩存策略主要分為兩種方式:
- 強緩存 (瀏覽器端執行)
- 協商緩存(服務器端執行)
優先級: 強緩存 > 協商緩存
所謂強緩存就是第一次請請求服務器的時候獲取的兩個字段【Expires】、【Cache-Control】.
強緩存【Expires】、【Cache-Control】
Expires
【Expires】:該字段是HTTP1.0版本提出的,是瀏覽器訪問服務器時,由服務器在ResponseHeader字段中設置該資源過期時間:
Expires:Mon, 29 Jun 2020 11:10:23 GMT
復制代碼
上面表示該請求在 29 Jun 2020 11:10:23 前使用緩沖資源,過后再次請求則請會請求服務器獲取新的資源。 所以每次請求前瀏覽器都會判斷Expires字段來決定使用緩存資源還是重新請求服務器。
但是Expires的時間是在服務器的ResponseHeader中生成的,所以時間是相對于服務器時間的,一旦服務器時間跟瀏覽器本地時間不一致則會出現問題,所以Expores并不是一個很好的緩存方法,所以在HTTP1.1提出了【Cache-Control】字段。
Cache-Control
【Cache-Control】:該字段是HTTP1.1提出的,該字段的值是 過期時長(類似一種倒計時的功能),這樣即使 服務器和瀏覽器日期時間不一致也不會導致Expires的問題,到了時間自動過期。
Cache-Control:max-age=6000
復制代碼
上面代碼代表的是該請求的資源在6000秒后過期, 6000秒前使用緩存資源。
注意事項:
- 當Expires和Cache-Control同時存在時,優先使用HTTP1.1的Cache-Control
- 當強緩存的【Expires】、【Cache-Control】都不命中時,則進入協商緩存。
協商緩存
協商緩存原理:瀏覽器第一次請求服務器時,服務器會判斷Request Headers是否帶有緩存標識,若不存在緩存標識,則在Response Headers添加緩存標識,并且返回新的資源。
緩存標識分為兩種:【Last-Modified】、【ETag】
Last-Modified
這個字段表示最后修改時間,是指請求的資源的最后修改時間,在瀏覽器第二次訪問服務器時,會在Request Headers的If-Modified-Since中帶上該字段的值(值來自服務器),服務器接到請求后,會對If-Modified-Since的值與該請求資源的最后修改時間進行對比,若請求的資源最后修改時間大于If-Modified-Since的值,則返回新的資源,并重新設置Last-Modified字段的值。
以上就是協商緩存:【Last-Modified】的整個執行過程。
ETag
ETag是對請求資源的內容進行MD5算法,生成一個唯一的標識(hash值)。只要資源文件有所改動,改值就會發生改變。 過程:
瀏覽器第一次請求服務器的時候,服務器會判斷Request Headers中的【If-None-Match】是否包含值,若沒有該字段則返回新資源,并在Response Headers增加ETag字段,ETag值為請求對應資源的內容生成的hash值。
瀏覽器第二次請求時,會在Request Headers上添加【If-None-Match】字段,值為服務器返回的ETag的值,服務器接受到請求后,會與請求資源的MD5算法生成的hash值做對比,若相同則返回304告知瀏覽器使用緩存的資源,若不相同則返回全新的資源給瀏覽器,并且把新的資源hash值通過Response Headers的ETag字段返回給瀏覽器,瀏覽器在下次請求時帶上。
ETag 和 Last-Modified 對比
- 性能上,Last-Modified > ETag,因為Last-Modified記錄的是資源最后修改時間,而ETag則是記錄MD5算法生成的文件內容的hash值。
- 精度上,ETag > Last-Modified,因為ETage是根據內容生成的hash值,對內容極其敏感,而Last-Modified只是記錄資源最后一次修改時間。
協商緩存 總結
- Last-Modified: 在服務器生成,存在Response Headers的【Last-Modified】中,瀏覽器通過RequestHeaders中的【If-Modified-Since】字段把Last-Modified的值帶給服務器。
- ETag : 在服務器中生成,存在Response Headers的【ETag】字段給帶瀏覽器,瀏覽器通過Request Headers中的【If-None-Match】字段把【ETag】的值帶給服務器。
HTTP隊頭阻塞
眾所周知,服務器和客戶端是經過三次握手創建TCP通道進行交流的,最后通過四次回收告別的。
所以一次TCP通道的創建是需要消耗一定資源和時間的。
那么在HTTP0.9之前,每發送一次請求就必須創建一次TCP通道,但是一個網站往往都需要發送幾十個請求,那么就需要創建幾十個TCP通道,那樣豈不是很消耗資源?有沒有什么方法可以解決呢?
有的! 在HTPP1.0開始增加了Connection: Keep-Alive字段,可以讓TCP鏈接持續打開。這樣就可以節省了一個請求創建一次TCP通道的性能消耗。
在HTTP1.1引入了持久連接 和 管道機制
持久連接
持久連接:即不用聲明Connection: keep-alive字段,TCP連接默認不關閉,并且可以被多個請求復用。長連接的連接時長可以通過請求頭中的 keep-alive 來設置。
當客戶端請求中含有Connection: Keep-Alive首部,服務器響應中也有Connection: Keep-Alive首部時,雙方才會成功建立持久連接。
在服務器返回【Connection: Keep-Alive】字段時,還可以追加【Keep-Alive: max=5, timeout=120】字段

Connection: Keep-Alive
Keep-Alive: max=5, timeout=120
復制代碼
上面個例子說明,服務器最多還會為另外5個事務保持TCP連接的打開狀態,或者將打開狀態保持到連接空閑了2分鐘之后。
管道機制
HTTP1.1 允許在持久連接上可選擇使用請求管道。這是相對于keep-alive連接的又一性能優化。在相應到達之前,可以將多條請求放入隊列,當第一條請求發往服務器的時候,第二第三條請求也可以開始發送了,在高延時網絡條件下,這樣做可以降低網絡的環回時間,提高性能。
持久連接 + 管道機制 引發 HTTP隊頭阻塞
前面提到HTTP管道化要求服務端必須按照請求發送的順序返回響應,那如果一個響應返回延遲了,那么其后續的響應都會被延遲,直到隊頭的響應送達。
HTTP隊頭阻塞 的解決方法
利用HTTP2的多路復用解決:
對于HTTP1.1中管道化導致的請求/響應級別的隊頭阻塞,可以使用HTTP2的多路復用解決。
HTTP2不使用管道化的方式,而是引入了幀、消息和數據流等概念,每個請求/響應被稱為消息,每個消息都被拆分成若干個幀進行傳輸,每個幀都分配一個序號。每個幀在傳輸是屬于一個數據流,而一個連接上可以存在多個流,各個幀在流和連接上獨立傳輸,到達之后再組裝成消息,這樣就避免了請求/響應阻塞。
當然,即使使用HTTP2,如果HTTP2底層使用的是TCP協議,仍可能出現TCP隊頭阻塞。
并發連接
我們知道對于一個域名而言,是允許分配多個長連接的,那么可以理解成增加了任務隊列,也就是說不會導致一個任務阻塞了該任務隊列的其他任務,在RFC規范中規定客戶端最多并發2個連接,不過實際情況就是要比這個還要多,舉個例子,Chrome中是6個。
域名分片
顧名思義,我們可以在一個域名下分出多個二級域名出來,而它們最終指向的還是同一個服務器,這樣子的話就可以并發處理的任務隊列更多,也更好的解決了隊頭阻塞的問題。 舉個例子,比如TianTian.com,可以分出很多二級域名,比如Day1.TianTian.com,Day2.TianTian.com, Day3.TianTian.com, 這樣子就可以有效解決隊頭阻塞問題。
HTTP2
- 二進制分幀 這是一次徹底的二進制協議,頭信息和數據體都是二進制,并且統稱為"幀":頭信息幀和數據幀。
- 頭部壓縮 HTTP 1.1版本會出現 「User-Agent、Cookie、Accept、Server、Range」 等字段可能會占用幾百甚至幾千字節,而 Body 卻經常只有幾十字節,所以導致頭部偏重。HTTP 2.0 使用 HPACK 算法進行壓縮。
- 多路復用 復用TCP連接,在一個連接里,客戶端和瀏覽器都可以同時發送多個請求或回應,且不用按順序一一對應,這樣子解決了隊頭阻塞的問題。
- 服務器推送 允許服務器未經請求,主動向客戶端發送資源,即服務器推送。
- 請求優先級 可以設置數據幀的優先級,讓服務端先處理重要資源,優化用戶體驗。