# 前端效能優化 Day17 X 初探快取 & HTTP Caching > 本章節重點 : Server 跟瀏覽器之間的 Cache 機制 => HTTP Caching ### 什麼是 Cache?  - 提供一個暫存可能使用到資料的額外儲存空間 - Cache 是避免 client 和 sever 提出請求的一種方式 - Response 的再利用性(Reusability): 瀏覽器可以在一段時間內重複使用已經快取的回應,而不需要再次向伺服器發送請求 ### 適合被快取的資料 1. 很常被使用到 2. 資料不常變動 ### 為什麼需要 Cache 節省流量,節省時間,減少資源的損耗 ### Cache Hit vs Cache Miss - Cache Hit : 指的就是當發出請求時在快取就找到想要的資源 - Cache Miss : 指的就是快取中找不到想要的資源,必須再回去跟 origin server 拿資料 - 效能優化的角度 : 提高 Cache Hit Ratio,降低 Cache Miss Ratio ### 快取的種類 - 起源: 應用於 OS 方面的機制,透過快取,CPU 可以不必一直到 main memory 去拿資料,後來這個概念被運用到了 OS 層以外的地方。 - 分類: 以 Web 開發領域來說,依照作用的層級 1. Client Cache : HTTP Caching, Service Worker Caching 2. Server Cache(Application Cache): Redis 3. Networking Cache : CDN caching ### Public 與 Private 的快取 - 這個 response 能不能被不同 users 共享  - 公有快取 : 指快取伺服器上存的回覆能給好幾個不同的請求者服務。 - 私有快取 : 相對只會服務一個請求者,這台電腦的使用者可以使用快取的資源。 ### HTTP Caching  - HTTP Cache 是避免瀏覽器向伺服器發送不必要請求的一道防線 - 要啟用 HTTP Cache 需要伺服器端與瀏覽器端事先經過協商,瀏覽器與伺服器透過在 HTTP Request 與 Response 的 header 帶入一些資訊來協商快取的機制 **Expires** - HTTP 1.0 舊版本的方式,伺服器在 response header 中可以加入 Expires 字段 ``` Expires: Wed, 21 Oct 2015 07:28:00 GM ``` - 機制 : 取決於瀏覽器「現在的時間」是否有超過 Expires 中指定的過期時間 - 結果 : - 沒有超過 =>瀏覽器「不會發送任何 Request」直接從的 Cache 拿資料 - 超過 => 發起 network request  「Status code 200 (from disk cache)」,代表這個 Request 其實沒有發出去,Response 是直接從 disk cache 裡面拿的。 - 問題 : 根據使用者電腦本身的時間來決定過期與否 => Cache Miss **Cache-Control 與 max-age** - 為解決 Expires 的問題 - HTTP 1.1 時推出了 Cache-Control 這個 header ``` # 某個 http response header Cache-Control: max-age=60 ``` - 機制 : 使用者在收到這個 response 的 60 秒內,如果再對相同資源發出請求,就會得到快取的版本,如果超過 60 秒後才對這個資源發出請求,則會發出一個新的 network request。 - 範例: 觀察 Google Logo 檔案的 Response header  - max-age 設定成 31536000 秒(365 天) - 在一年之內造訪這個網站,都不會對 Google logo 這張圖片送出 Request,而是會直接使用快取 : Status code 200 (from memory cache) **Expires跟max-age優先性?** 根據RFC2616的定義: > If a response includes both an Expires header and a max-age directive, the max-age directive overrides the Expires header, even if the Expires header is more restrictive max-age會蓋過Expires。因此現在的快取儘管兩個都會放,但其實真正會用到的是max-age。 :::info tips : 1. Expires 和 Cache-Control 這兩個 Header 都是在關注一個 Response 的「新鮮度(freshness)」 2. Expires 給定絕對時間,Cache-Control 通常會搭配 max-age 給定相對時間 ::: **過期了,然後呢?** 「過期了不代表不能用」 **Last-Modified & If-Modified-Since** - HTTP 1.0 就被提出的比較舊的解決方案 - Last-Modified : 出現在伺服器回應給瀏覽器的 response header 裡,告訴瀏覽器這個檔案上次更改是什麼時候。 - If-Modified-Since : 出現在瀏覽器發出請求的 resquest header 裡,用來跟伺服器確認檔案在某個時間點後是不是有經過更改。 ``` Last-Modified: 2017-01-01 13:00:00 // 檔案的最後更新時間 Cache-Control: max-age=31536000 // 過期時間 ``` - 機制: - 半年後重新請求這張圖片 => 不會發送任何 Request,而是直接從瀏覽器那邊獲得資料。 - 一年之後再請求一次這張圖片 => 瀏覽器和 Server 確認檔案從2017-01-01 13:00:00以後有沒有更新」,會發送出下面這樣的 Request: ``` GET /logo.png If-Modified-Since: 2017-01-01 13:00:00 ``` 1. 檔案更新 => 瀏覽器會收到一份新的檔案。 2. 檔案未更新 => Server 回一個Status code : 304 (Not Modified),代表可以繼續沿用快取的這份檔案。  - 問題 : 根據檔案的編輯時間來做判斷的 **Etag & If-None-Match** - 為解決 Last-Modified & If-Modified-Since 的問題 - 機制 : 依據檔案的內容有沒有更動來決定是否要重新抓取檔案 - 流程 : - server 在 response header 會帶入 etag 讓瀏覽器存起來 ``` Cache-Control: max-age=86400 ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" ``` - 等到快取時效過後,使用者又請求了相同資源,瀏覽器就會在 request header 中帶入: ``` If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4" ``` - 比對瀏覽器帶過來的 Etag 與最新的檔案是否相符 - 相同 => 代表檔案內容沒有變,瀏覽器可以沿用相同的快取 - 不同 => 代表瀏覽器快取中已是舊的版本,需要重新抓取一次 - (補充) 生成 etag 的原理 - 對於靜態文件(例如 HTML、CSS、JavaScript、圖像等),通常會使用文件的 size 加 mtime (修改時間)。 - 對於動態生成的內容,例如字符串或緩衝區(Buffer),通常會使用字串或者 Buffer 的長度加上透過 sha1 演算法產生的 hash 字串的前27位。 **(補充) 避免 Mid-Air Collisions** - 目標:使用 ETag 避免多人同時編輯的情況 - 流程:當從 view mode 進到 edit mode 時,可以先紀錄當前頁面的 Etag 值 ``` ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" ``` 等到修改完成時透過 If-Match 這個 header 檢查 Etag 是否一樣 ``` If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d5" ``` ETag 值不一致,表示發生共編 => 伺服器回應 412 狀態碼,代表前置條件失敗(Precondition Failed),告知使用者需要重新編輯 :::info tips:  - 選擇適合的 cache policy 是非常重要 - 快取沒過期 : 直接拿不會發出網路請求 - 使用 Etag 或是 If-Modified-Since 的機制 : 會發出網路請求,只是網路封包大小通常會比真的跟伺服器抓取資料還要小許多 ::: ### 不要快取怎麼辦? 含有一些機密資料的頁面 **Cache-Control: no-store** 代表不要任何快取 **Cache-Control: no-cahce** 代表此 response 必須經過驗證才能使用 > 不要任何快取是 no-store 不是 no-cahce! **Cache-Control: private** 代表此 response 只可以被瀏覽器儲存起來 **Cache-Control: public** 代表此 response 可以被任何快取軟體儲存起來,例如 reverse proxy 的快取、瀏覽器的快取 ### no-cahce 的應用 - 首頁的快取策略 - 目標:「把頁面快取起來,但只要首頁一變動,就能夠立刻看到新的頁面」 **[解法1]** `Cache-Control: max-age=0` (代表這個 Response 0 秒之後就會過期) 再搭配 `Etag` 來使用 : 第一個 Response: ``` Cache-Control: max-age=0 Etag: 1234 ``` 重新整理,瀏覽器發出 Request: ``` If-None-Match: 1234 ``` - 檔案沒有變動,無需發出 Request,Server 回傳:304 Modified,沿用舊檔案 - 檔案有變動,回傳新的檔案並更新 Etag **[解法2]** `Cache-Control: no-cache` : - 無論檔案有沒有變動,每次都會發送 Request 去確認 > max-age=0 允許快取在重新驗證後使用舊的回應,而 no-cache 則要求快取必須重新驗證回應,確保每次都使用最新的數據。 **Cache-Control: no-store** v.s. **Cache-Control: no-cahce** - 案例 : A 網站是使用 Cache-Control: no-store,B 網站是使用 Cache-Control: no-cache - 假設 index.html 有 100 kb - 假設封包大小 1kb - 結果: - 造訪了十次 A,累積的流量就是 1000kb - 造訪了十次 B,但 B 第十次才更新,累積流量就是 9 (前九次 Server 只會回傳 Status code 304) + 100 = 109 kb > 達成的目標一致 : 只要網站更新,使用者就能立即看到結果,但是 B 的流量遠低於 A,因為有善用快取策略。 :::info tips: - max-age=0 跟 no-cache 的差異,SHOULD-revalidate 和 MUST-revalidate - no-store 跟 no-cache 的差異,永遠不用快取跟永遠檢查快取 ::: ### Cache Busting **no-cache 無論如何,都會發出 Request。有沒有可能,連 Request 都不發呢?** - 目標:檔案不更新,瀏覽器就不會發 Request,直接沿用快取。檔案一更新,瀏覽器就要立即抓取新的檔案 - 機制: Cache Busting - 為檔案建立一個獨立識別檔名,只要檔名變了,瀏覽器就認為這是一個新的檔案,需要重新跟伺服器抓取(類似把 Etag 機制實作在 HTML file裡面) - webpack 這些 bundler 都可以做到在打包時加一串 hash 字串到檔名裡,並同時自動更新 HTML 裡面引入的檔名 ```html= <!DOCTYPE html> <html> <head> <link rel='stylesheet' href='./css/public/style-dasd2134das.css'></link> <script src='./public/js/ajskdj1213.js'></script> </head> <body> <div id="container"> <!-- 交給 SPA 去 render --> </div> </body> </html> ``` - 以下方修改 CSS 後重新打包的 HTML 為例 : 只有 CSS 檔名中的 hash 有改變 ```html= <!DOCTYPE html> <html> <head> <link rel='stylesheet' href='./css/public/style-eawe124as.css'></link> <script src='./public/js/ajskdj1213.js'></script> </head> <body> <div id="container"> <!-- 交給 SPA 去 render --> </div> </body> </html> ``` - 原理: 針對不同的檔案採用不同的快取策略,並且直接用「更換 CSS 或 JavaScript 檔案」的方式強制瀏覽器重新下載 - 策略: 對於 CSS 檔案:快取策略為 max-age=31536000 對於 index.html 檔案:快取策略為 no-cache 所以每一次訪問這個頁面,都會去看 index.html 是否更新,因為引入的 CSS 檔名變了,重新發送 Request ### Memory Cache vs Disk Cache  - from disk cache : 表示這些資源來自瀏覽器的快取,並沒有再發出 request,因此載入速度會比第一次來的快。 - from memory cahce : 可以看作是 Chrome 特別實作的一種快取,它把資源存在 memory (RAM) 裡面,所以在效能上會比 disk cache 還要快,缺點是關閉瀏覽器時資料就會被清空。 - 目前沒有明確的定義瀏覽器怎麼決定哪些資源要放到 memory cache 裡,只知道透過 resource hint 例如 preload, prefetch 載入的資源比較有機會被放到 memory cache 中。 **memory cahce v.s. disk cache** - 效能與準確性 : memory cahce > disk cache 1. 不受 Cache-Control 設定影響:即使資源的 Cache-Control 指令要求不快取,memory cache 也可能會將其存儲起來以提高後續的訪問速度 2. 識別特徵不同: 傳統的 HTTP 快取通常根據 URL 或檔案名稱來識別和存儲資源,但 memory cache 會額外考慮資源的 Content-Type 和 CORS 等其他特徵來識別和區分資源 **既然速度那麼快,就把全部資源都先存到 memory cache 就好啦?** 這是不可能的,記憶體的容量相比硬碟小非常多 **(補充) cache 的優先順序(指會先到哪種 cache 找有沒有資料)** `memory cache -> service worker cache -> disk cache (browser cache)` ### 結語 - Expires 跟 max-age 是在負責看這個快取是不是「新鮮」 - Last-Modified, If-Modified-Since, Etag, If-None-Match 是負責詢問這個快取能不能「繼續使用」 - no-cache 與 no-store 代表到底要不要使用快取,以及應該如何使用 - 所有透過瀏覽器發送的 HTTP 請求,都會先經過瀏覽器的快取,在這裡會先檢查是否有有效的快取內容可作為回應,如果有的話就直接讀取快取的內容,以減少網路的延遲和傳輸造成的成本 ### 參考文章 https://ithelp.ithome.com.tw/articles/10276125
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up