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

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

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

認識瀏覽器緩存

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

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

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

圖片描述

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

查看緩存

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

至於背後的文件,通常存在於:C:\Users\yanying\AppData\Local\Google\Chrome\User Data\Default\Cache路徑中,其中yanying是你的windows用戶名稱。windows

緩存協商

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

緩存協商方式和CookieUser-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頭標籤:

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

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

HTTP/1.1 200 OK
Date: Tue, 27 Jun 2017 15:13:02 GMT
Server: Apache/2.4.9 (Win32) PHP/5.5.12
X-Powered-By: PHP/5.5.12
Last-Modified: Tue, 27 Jun 2017 15:13:02 GMT
Content-Length: 0
Keep-Alive: timeout=5, max=97
Connection: Keep-Alive
Content-Type: text/html

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

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

GET /php/last.php HTTP/1.1
Host: localhost
Connection: keep-alive
Cache-Control: max-age=0
//...這裏省略部分信息
If-Modified-Since: Tue, 27 Jun 2017 15:13:02 GMT

觀察一下最後一行,多了一個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的頭文件。

<?php
header("ETag : abcd");

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

看下服務器返回的header頭:

HTTP/1.1 200 OK
Date: Wed, 28 Jun 2017 01:45:40 GMT
Server: Apache/2.4.9 (Win64) PHP/5.5.12
X-Powered-By: PHP/5.5.12
ETag: abcd
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

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

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

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

GET /etags.php HTTP/1.1
Host: localhost
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8
Cookie: Phpstorm-65418376=dceeb07b-c7af-45d6-b8be-4079e9424244; Hm_lvt_65dfcf8f1948f7203dd3fb620de01083=1497600508; admin_id=1; admin_token=072517cddaa9c106fe4662ea70a1345c
If-None-Match: abcd

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

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

小結

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

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

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

Expires

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

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

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

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

圖片描述

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

Apacheexpires支持設置以下:

<IfModule mod_expires.c>
    ExpiresActive on
    ExpiresByType image/gif "access plus 1 month"
    ExpiresByType text/css "now plus 2 day"
    ExpiresDefault "now plus 1 day"
</IfModule>

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

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

動態資源中使用Expires

這裏咱們仍是拿PHP來舉例

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

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

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

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

HTTP/1.1 200 OK
Date: Wed, 28 Jun 2017 02:24:18 GMT
Server: Apache/2.4.9 (Win64) PHP/5.5.12
X-Powered-By: PHP/5.5.12
Expires: Tue, 31 Dec 2019 16:00:00 GMT
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

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

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

圖片描述

對於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-Modifiedexpires都是使用服務器標準時間來標記。

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

好比咱們設定一個資源在將來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標籤的頭:

<?php
header("Cache-Control:max-age=3600");

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

HTTP/1.1 200 OK
Date: Wed, 28 Jun 2017 12:33:16 GMT
Server: Apache/2.4.9 (Win32) PHP/5.5.12
X-Powered-By: PHP/5.5.12
Cache-Control: max-age=3600
Content-Length: 0
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Content-Type: text/html

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

嚴穎,2017.6.28

我的主頁:segmentfault

推薦一個咱們團隊本身開發的針對開發者的網址導航:筆點導航 - 用心作最簡潔的網址導航

能夠自定義網址
能夠自定義分類
分類能夠標記顏色
自定義皮膚
自定義搜索
網址拖拽排序
自定義插件小模塊

圖片描述

相關文章
相關標籤/搜索