轉載:最常被遺忘的 Web 性能優化:瀏覽器緩存

https://www.sohu.com/a/153567485_505818php

 

一提起緩存, Web開發者們老是在想數據庫緩存、頁面靜態化、使用 Redis內存緩存。這些方法都有一個共性,就是集中在後臺,目的就是加快數據的讀取,少用比較容易產生瓶頸的部分。css

後臺該優化的都優化到了最佳狀態,卻每每疏忽了一個很是重要的過程,就是數據傳輸。想着如何快速讀取數據,卻忘了如何減小請求數據,或者根本不請求數據。因此,今天咱們就來聊一聊這個常常被咱們遺忘的瀏覽器緩存。html

認識瀏覽器緩存web

當瀏覽器請求一個網站的時候,會加載各類各樣的資源,好比 HTML文檔、圖片、 CSS和 JS等文件。對於一些不常常變的內容,瀏覽器會將他們保存在本地的文件中,下次訪問相同網站的時候,直接加載這些資源,加速訪問。chrome

這些被瀏覽器保存的文件就被稱爲緩存。(不是指 Cookie或者 Localstorage)。數據庫

那麼如何知曉瀏覽器是讀取了緩存仍是直接請求服務器?咱們就使用 Segmentfault網站來作個示例(見下圖)。segmentfault

第一次打開該網站後,若是再次刷新頁面。會發現瀏覽器加載的衆多資源中,有一部分 size有具體數值,然而還有一部分請求,好比圖片、 css和 js等文件並無顯示文件大小,而是顯示了 fromdis cache或者 frommemory cache字樣。這就說明了,該資源直接從內存或者本地硬盤直接讀取,而並無請求服務器。windows

查看緩存後端

知道了瀏覽器從緩存中讀取文件,那麼瀏覽器緩存文件存儲在哪裏?以 chrome爲例,直接在瀏覽器地址欄輸入: chrome://cache/便可打開近期的全部緩存文件連接,固然你能夠直接點擊打開緩存內容。瀏覽器

至於背後的文件,通常存在於: C:UsersyanyingAppDataLocalGoogleChromeUserDataDefaultCache路徑中,其中 yanying是你的 windows用戶名稱。

緩存協商

從上面的圖片能夠看出。一部分請求使用了緩存,而有一部分緩存並無使用緩存。瀏覽器若是想判斷什麼時候該作什麼操做,就必需要有一個斷定標準。這裏就須要用到緩存協商。簡單來講就是 Web瀏覽器和服務器之間協定一個法則,什麼狀況下請求資源,什麼狀況下不請求。

緩存協商方式和 Cookie、 User-Agent同樣,經過瀏覽器 header進行傳輸。

緩存協商方式有3種:

  1. Last-Modified

  2. ETag

  3. Expires

Last-modified 定義

Last-Modified標籤表明是文件的最後修改時間,其格式是標準的 GMT時間。注意:GMT是標準的格林威治時間,咱們國家是 GMT+8時區。因此,你看到的 Last-Modified和咱們的時間有8個小時差距,不過不影響使用。

通常的動態資源沒有所謂的最後修改時間。而靜態文件好比 css文件、圖片等文件能夠經過 stat()系統調用得到文件的最後修改時間。

可是,實際網站運行中, Web服務器(好比 Apache)會自動獲取靜態資源的最後修改時間,同時會自動在 HTTP頭文件中添加 Last-Modified標籤。靜態資源的相應頭文件以下圖所示:

包含了 Last-Modified標籤的資源,在下次的請求中,瀏覽器會帶着該時間。當服務器接收到請求後會覈對該時間後,文件是否被修改,若是修改了就直接返回數據,沒有修改就直接返回 304狀態碼,告知瀏覽器直接使用本地緩存。這樣,一次數據傳輸流量就被免除了,速度稍有加快。

動態資源中使用

動態資源雖然沒有相對意義上的最後修改時間,可是咱們仍是能夠直接經過發送 header頭來手動定義 Last-Modified。這樣,經過動態程序判斷,也能夠達到靜態資源節省數據傳輸流量的做用。

這裏使用 PHP舉個例子:

一、首先建立一個 php文件,發送一個 Last-Modified頭標籤:

  1. <?php

  2. header("Last-Modified:".gmdate("D, d M Y H:i:s")." GMT");

二、使用瀏覽器請求該文件,咱們獲得了以下的服務器返回頭:

  1. HTTP/1.1200OK

  2. Date:Tue,27Jun201715:13:02GMT

  3. Server:Apache/2.4.9(Win32)PHP/5.5.12

  4. X-Powered-By:PHP/5.5.12

  5. Last-Modified:Tue,27Jun201715:13:02GMT

  6. Content-Length:0

  7. Keep-Alive:timeout=5,max=97

  8. Connection:Keep-Alive

  9. Content-Type:text/html

觀察上面服務器返回的頭文件,包含了一個 Last-Modified:Tue,27Jun201715:13:02GMT,這就是上面動態代碼生成的最後修改時間。

三、當咱們再次請求該文件的時候,咱們看下瀏覽器發送給服務器的頭文件。

  1. GET /php/last.php HTTP/1.1

  2. Host:localhost

  3. Connection:keep-alive

  4. Cache-Control:max-age=0

  5. //...這裏省略部分信息

  6. If-Modified-Since:Tue,27Jun201715:13:02GMT

觀察一下最後一行,多了一個 If-Modified-Since標籤,他的時間正是服務器剛剛返回的 Last-Modified的值。這個值就這樣又被返回給了服務器。

四、這樣就很簡單啦。在動態語言端( PHP)能夠直接使用 $_SERVER['HTTP_IF_MODIFIED_SINCE']便可獲取時間值,接着就能夠作一些簡單的對比工做。若是在這個時間以後數據沒有變化則直接返回 304,告訴瀏覽器直接使用緩存,而免去數據傳輸的過程。

並且,最終要的是。這個過程根本無需查找數據庫,因此後臺程序執行時間很是短,從而大大減小用戶等待時間。

這樣咱們就作到了動態資源也能夠實現靜態資源的最後修改時間,從而減小數據傳輸量,達到優化性能要求。

Etag Last-Modified缺點

Last-Modified彷佛已經作到了部分性能優化效果。可是,老是有些狀況下不是很奏效。好比,一個用戶修改了一個文件,後來用戶以爲修改錯誤,因而又修改回去。

上面的過程當中,文件內容並無發生變化。可是,文件在系統中的物理最後修改時間卻發生了變化。這種狀況下,若是瀏覽器再次請求資源。服務器仍是會發送完整數據。從而並未徹底達到咱們預想的效果。

因而在此之上,咱們還能夠添加一個 ETag標籤,用來進一步確認文件是否修改。

瞭解ETag

ETag相似於 Last-Modified,也是一個 header頭標籤。他的值是一串字符串,用於區分各個文件的版本信息,因爲 HTTP並無對該值作任何的格式限制,因此能夠自定義生成。

ETag的值不一樣於 Last-Modified,他並不會在文件被修改時候就發生變化,而是在文件內容發生變化的時候纔會被改變(具體何時改變,徹底有後臺業務邏輯來判斷)。對於靜態資源, Web服務器仍是會幫咱們處理好這個標籤,不用考慮太多。

這裏咱們截取了 Segmentfault的一張圖片的 ETag,以下圖:

下面咱們仍是來討論一下動態資源模擬靜態資源發送 ETag標籤的過程:

一、這裏咱們仍是新建一個 PHP文件,其中代碼是向瀏覽器發送一個包含 ETag的頭文件。

  1. <?php

  2. header("ETag : abcd");

二、使用瀏覽器請求該 PHP文件:

看下服務器返回的 header頭:

  1. HTTP/1.1200OK

  2. Date:Wed,28Jun201701:45:40GMT

  3. Server:Apache/2.4.9(Win64)PHP/5.5.12

  4. X-Powered-By:PHP/5.5.12

  5. ETag:abcd

  6. Content-Length:0

  7. Keep-Alive:timeout=5,max=100

  8. Connection:Keep-Alive

  9. Content-Type:text/html

裏面比正常的返回多了一個 ETag標籤,而且它的值就是咱們剛剛設置的 abcd

三、下面咱們刷新瀏覽器,再次請求改頁面:

注意觀察下瀏覽器請求的頭 header:

  1. GET /etags.php HTTP/1.1

  2. Host:localhost

  3. Connection:keep-alive

  4. Cache-Control:max-age=0

  5. Upgrade-Insecure-Requests:1

  6. User-Agent:Mozilla/5.0(WindowsNT 10.0;Win64;x64)AppleWebKit/537.36(KHTML,like Gecko)Chrome/59.0.3071.86Safari/537.36

  7. Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

  8. Accept-Encoding: gzip, deflate, br

  9. Accept-Language: zh-CN,zh;q=0.8

  10. Cookie: Phpstorm-65418376=dceeb07b-c7af-45d6-b8be-4079e9424244; Hm_lvt_65dfcf8f1948f7203dd3fb620de01083=1497600508; admin_id=1; admin_token=072517cddaa9c106fe4662ea70a1345c

  11. If-None-Match: abcd

仔細看下最後一行,有一個 If-None-Match頭標籤。該標籤的值正是咱們剛剛接收到的服務器返回的 ETag的值,這樣相似於 Last-Modified,咱們在 PHP端可使用 $_SERVER['HTTP_IF_NONE_MATCH']直接獲取咱們剛剛的值。

四、獲取到該值,咱們就能夠直接對比文件現有的 ETag,來決定是直接使用瀏覽器緩存仍是再次發送完整數據。

小結

Etag和 Last-Modified很是類似,都是用來判斷一個參數,從而決定是否啓用緩存。可是 ETag相對於 Last-Modified也有他的優點,他能夠更加準確的判斷文件內容是否被修改,從而在實際操做中實用程度也更高。

有了這兩種優化方式,對於節省流量帶寬已經起到了很是大的做用。可是老是感受仍是有點兒雞肋,畢竟每次瀏覽器仍是要來詢問一下服務器,文件是否被改變。

若是,咱們能夠肯定,一個文件在半年內不會改變,那麼咱們可讓瀏覽器在這半年時間內都不來服務器詢問,而直接使用本地緩存。這裏就須要使用第三種協商方式 Expires.

Expires

Expires這個單詞的意思是過時,在這裏表示的是過時時間。它的使用方式、格式和 Last-Modified同樣,都是使用瀏覽器頭,也都是標準的 GMT時間。

可是它的功能卻徹底不一樣,包含了 Expires頭標籤的文件,就說明瀏覽器對於該文件緩存具備很是大的控制權。例如,一個文件的 Expires值是2020年的1月1日,那麼就表明,在2020年1月1日以前,瀏覽器均可以直接使用該文件的本地緩存文件,而沒必要去服務器再次請求該文件,哪怕服務器文件發生了變化。

因此, Expires是優化中最理想的狀況,由於它根本不會產生請求,因此後端也就無需考慮查詢快慢。

下面咱們看下 segmentfault的靜態文件的 Expires:

對於靜態資源,大多數服務器是會開啓 expires標記功能。若是遇到沒有開啓的,則可使用配置文件開啓。

Apache的 expires支持設置以下:

  1. <IfModulemod_expires.c>

  2. ExpiresActive on

  3. ExpiresByType image/gif "access plus 1 month"

  4. ExpiresByType text/css "now plus 2 day"

  5. ExpiresDefault "now plus 1 day"

  6. </IfModule>

上面的配置中咱們設置 image/gif的格式圖片緩存時間爲1個月,而 css文件緩存時間爲2天,其餘的默認爲1天。

另外,對於經常使用靜態資源。若是不在 web服務器端設置 expires標籤,瀏覽器也能夠智能的標記一個過時時間。好比 gif圖片,瀏覽器會設置他的過時時間爲永不過時。

動態資源中使用Expires

這裏咱們仍是拿 PHP來舉例

一、首先建立一個 PHP文件,用於發送 Expires頭標籤。

這裏咱們把文件過時時間直接設置爲2020年1月1日的0點

  1. <?php

  2. header("Expires:".gmdate("D, d M Y H:i:s",1577808000)." GMT");

二、使用瀏覽器請求該文件,觀察服務器返回的頭文件:

  1. HTTP/1.1200OK

  2. Date:Wed,28Jun201702:24:18GMT

  3. Server:Apache/2.4.9(Win64)PHP/5.5.12

  4. X-Powered-By:PHP/5.5.12

  5. Expires:Tue,31Dec201916:00:00GMT

  6. Content-Length:0

  7. Keep-Alive:timeout=5,max=100

  8. Connection:Keep-Alive

  9. Content-Type:text/html

不出意外,咱們已經在頭文件裏面發現 Expires標籤,而且它的值爲 Tue,31Dec201916:00:00GMT(這裏不是2020年緣由是因爲有8個小時時差)。

三、再次使用瀏覽器訪問改頁面,發現瀏覽器的請求數據的路徑已經變爲了 fromcache(以下圖)。

對於 chrome瀏覽器,從 network中彷佛並不能看出是否使用了緩存。可是,若是打開 chrome://cache/,搜索咱們剛剛的地址,會發現咱們請求的內容也被緩存成功。

請求方式與緩存

瀏覽器有3種請求服務器資源的方式

  1. ctrl+f5:強制刷新

  2. f5:刷新頁面

  3. 瀏覽器地址欄回車,也就是轉到功能

這3種請求方式對於資源使用緩存的影響各不不一樣,下面一一的解釋:

一、ctrl + f5:強制刷新

這種方式是全部加載方式中使用緩存最少的方式。當使用 ctrl+f5訪問一個地址的時候,瀏覽器會強制全部的資源從新加載一次。全部的資源將會被從新緩存。

二、f5:刷新頁面

f5刷新頁面至關於瀏覽器上面的刷新按鈕,是一種比較經常使用的刷新方式。這種方式下瀏覽器會使用部分必要的緩存,針對於 Last-Modified有效,可是 expires標籤就會失去他的做用。

三、地址欄轉到方式

在瀏覽器地址欄輸入即將訪問的地址後,按回車或者瀏覽器轉到功能訪問網頁。這是使用最多的一種狀況,也是使用最少請求服務器的方式。也就說瀏覽器會盡可能使用本地緩存,而避免直接請求服務器數據。注意:Expires標籤也只有在這種狀況下有效。因此,千萬不要使用 f5或者 ctrl+f5還奇怪 expires功能無效。

cache-control 還有一點點小缺陷

瞭解了上面全部的緩存協商方式後,咱們已經能夠高效的優化咱們現有的應用。可是仍是存在一種可能狀況,那就是以前的 Last-Modified和 expires都是使用服務器標準時間來標記。

而做爲最後的判斷者確是瀏覽器。因此,不免會存在用戶電腦時間和服務器時間不一致的狀況。

好比咱們設定一個資源在將來10分鐘內不會過時,而用戶電腦比服務器時間快了1個小時(固然這個太少見)。那麼咱們設置的過時時間對於用戶來說,當即就過時了。那麼咱們的設置至關於白用功了。

因此爲了解決這個可能出現的小缺陷,咱們還能夠設置一個相對於用戶本地時間的緩存過時時間 cache-control。

做用

cache-control和以前的 Last-Modified同樣,都是頭文件裏面的一個標籤。只不過他的值是 max-age=<second>,這裏的 <second>是一個數字,單位爲秒。

假設咱們設置一個值 cahce-control:max-age=3600,那麼就表明改緩存有效期是用戶本地時間加上 3600秒。這樣,緩存的截止時間就和服務器時間沒有太大關係了,從而避免了由於時間誤差帶來的不良影響。

對於靜態文件,若是服務器好比 Apache開啓了 expires功能,那麼也會默認的給頭文件添加一個 cache-control標籤。

PHP設置cache-control

對於動態文件,咱們能夠在程序語言中向瀏覽器直接輸出該標籤。咱們使用 PHP作一個演示:

一、建立一個 PHP文件,向瀏覽器輸出一個包含 cache-control標籤的頭:

  1. <?php

  2. header("Cache-Control:max-age=3600");

二、使用瀏覽器請求該 PHP文件,獲取服務器返回頭 header:

  1. HTTP/1.1200OK

  2. Date:Wed,28Jun201712:33:16GMT

  3. Server:Apache/2.4.9(Win32)PHP/5.5.12

  4. X-Powered-By:PHP/5.5.12

  5. Cache-Control:max-age=3600

  6. Content-Length:0

  7. Keep-Alive:timeout=5,max=98

  8. Connection:Keep-Alive

  9. Content-Type:text/html

觀察上面的信息,能夠發現其中包含 cache-control標籤,其值爲咱們剛剛設置的 max-age=3600,那麼就表明相對於我本地時間 3600秒以後緩存過時。

相關文章
相關標籤/搜索