Http Cache 是什麼?為什麼需要 Cache 呢?今天就來點 Cache 相關的小學習吧!

HIHI~😍 如果你是第一次來的話,『Chan-Chan-Dev』是一個專門用簡單的圖文與故事講解網路程式技術的網站。
若你也喜歡用這種方式學習的話,歡迎加入 Chan-Chan-Dev Facebook 的粉絲團,在發佈的時候就有比較多機會收到通知喔!😍

我們進入正片囉 😆

在學習網頁開發的過程中,多多少少都會耳聞 快取 或者是 Cache 這個關鍵字,但是他究竟是用來幹嘛的呢?

在進入 Cache 的主題前,我們一樣先來個小故事當作起手式。

這次來說說小明跟料理大師 Chef Master 拜師學藝後自己經營餐館的故事囉!


小明
小明從小就夢想著自己經營著一家餐館,想像著端出熱騰騰的美食到客人面前,客人因爲吃到美食的那刻銷魂的神情,光想到這裏他就感到十分地嚮往。

無奈他目前的工作都跟美食搭不上邊,於是他終於在 30 歲的時候下定決心要成爲特級廚師 😅,所以就 Google 了一下,在陽明山裡找到曾經也是特級廚師的 Chef Master。
Chef-Master 別問為什麼他會爆肌 😆
經過了半年辛苦的密及訓練,Chef Master 終於同意他可以下山開一家屬於自己的餐廳了。

只是他有個但書:

爲了怕小明的餐廳砸了他 Chef Master 的招牌,所以餐廳裡的食譜一律由他自己親自設計。

當小明的店風風光光開幕的那一天,因爲大家都還不知道這家店的存在,所以總共只有 3 個人來消費,但即使如此在第一天結束後,小明就發現他自己已經累了半死了,這究竟是為什麼呢?

第一天開店的時候是這樣子的:

因爲 Chef Master 說食譜要由他親自設計準備,所以當第一個客人點了『蛋炒飯』的時候,小明請客人等等,自己就馬上出發到陽明山跟 Chef Master 要『蛋炒飯』的食譜,等 Chef Master 設計完並且口頭 告訴小明怎麼料理之後,他才又氣喘吁吁地趕回到店裏,依照著印象中上面開始製作『蛋炒飯』料理,等這盤炒飯端上桌到客人面前已經是 30 分鐘之後了。

第二個客人則點了『炒烏龍麵』,於是小明又馬上去陽明山跟 Chef Master 問『炒烏龍麵』的做法後,才又回來準備料理這道『炒烏龍麵』,只是回來的時候發現客人早已不在了。

第三個客人一樣是點了『蛋炒飯』,但是此時的小明已經忘記剛剛『蛋炒飯』的做法了,於是又得跑一趟陽明山去詢問 Chef Master 『蛋炒飯』的做法,然後在一路趕回來。

問題點:每次客人點了什麼料理,都要跑陽明山一次,即使點了料理一樣,也會因爲已經忘記做法一樣得上一次陽明山 😭

保留紙本食譜在食譜本本(Cache)

在第一天的晚上小明驚覺這樣子餐廳根本無法經營下去,於是隔天跟 Chef Master 商量說:「Chef Master,能不能下次你設計好的食譜就寫在紙上,然後讓我帶回去 留存一份,這樣下次遇到一樣的料理我就不用再跑一趟了?」,Chef Master 點頭表示同意。

食譜本本登場!!

因爲調整為上述 保留紙本食譜在店裏 的做法後,第一次客人點了『蛋炒飯』,雖然第一次總是得跑去跟 Chef Master 要『蛋炒飯』的食譜,但是在之後就可以把『蛋炒飯』的食譜放入 食譜本本裡。因此只要之後有任何一個客人再點了『蛋炒飯』的話,小明馬上就可以從 食譜本本 找到『蛋炒飯』的食譜,除了可以 節省每次都要跑陽明山的舟車勞頓 以外, 客人終於也可以快速地吃到『蛋炒飯』了

因爲這本 食譜本本 的功勞,小明店裏出菜的速度有了大幅度的進化,讓他的生意開始越來越好。

從上述小故事我們簡單地瞭解到:

一份資料每次都要去經過長途跋涉的距離大老遠拿回來之後,在之後即使是同一份資料還是得去拿一次,似乎是很沒有效率的做法,就如同故事裏的『口述的料理做法』

聰明的你一定會想說:

身邊留一份備份的資料就好了阿 😆

沒錯,這就是 Cache 的作用,他像故事裏的 食譜本本 一樣,存放了曾經下載過的檔案備份

現在,讓我們現在把鏡頭交給棚內主播 🎥


HTTP 因爲是 Client 與 Server 的架構,所以每次 Client 端需要什麼資料都必須發送一個 Request 給 Server 端,並且由 Server 那邊回覆一個 Response 把資料傳遞回來。

請求與回覆示意圖

雖然上圖看起來好像很近,但是他實際的地理位置的距離可能是這個樣子的:

實際使用者與伺服器之間的地理位置距離

因此想像一下每次使用者需要從我們的伺服器取得資料都要經過這個距離的傳輸,而瀏覽器卻要等這些資料回來才能顯示在網頁上,可能會讓使用者使用的體驗不佳。

如果使用者按了瀏覽器上的『重新整理』按鈕,同樣的傳輸流程就需要在跑一次。

這些檔案之前已經下載在自己的電腦裡過一次了,就還需要 重新下載 所有的檔案。

對於使用手機有限網路流量的使用者極度的不友善,因爲他只要重新整理幾次我們的網站,他的網路流量扣打就馬上被吃光光了 😭

為什麼需要 Cache?

講了這麼久 Cache 到底有什麼好處呢?

  • 減少延遲與網路傳輸(讓非網路吃到飽的使用者節省網路流量的費用)😊
  • 減少網站顯示前的等待時間(如果已經瀏覽過這個網頁,可以大幅加速網頁內容的呈現)🚀
  • 讓網站變得更即時(因爲下載過的檔案可以直接從 Cache 裏面拿)⭐️
  • 減緩了伺服器的需要給每個 Client 回應的負擔(因爲減少了再去找伺服器要資料,所以伺服器也可以輕鬆一點囉)😎

什麼是 Cache?

依據 wiki 的定義如下:

Web快取(或HTTP快取)是用於臨時儲存(快取)Web文件(如HTML頁面和圖像),以減少伺服器延遲的一種資訊科技。Web快取系統會儲存下通過這套系統的文件的副本;如果滿足某些條件,則可以由快取滿足後續請求。[1] Web快取系統既可以指裝置,也可以指電腦程式。

我們簡單地來看一下有沒有 Cache 的 ReqeustResponse 的差異吧。

沒有使用 Cache

沒有使用 Cache

使用 Cache

使用 Cache

Cache-Control

我們可以 ReqeustResponse 的 header 找到 Cache-Control 的設定,Cache-ControlHTTP/1.1 之後才有的標頭,專門用來控制 Cache 相關的機制,他可以有幾種不同的寫法,就來簡單地看一下吧~

完全不使用 Cache 使用 Cache
但是每次都要重新檢查一次
私人 Cache 公開 Cache
Cache-Control: no-store Cache-Control: no-cache Cache-Control: private Cache-Control: public

有了 Cache 真的是好棒棒 !但是故事真的就這樣子幸福快樂了嗎?讓我們看下去吧~


Chef Master 更新料理做法

隨著店裏生意越來越好,Chef Master 在陽明山下都聽到他得意門生的美名,於是他親自下山來祝賀他的得意門生,到店裏後馬上叫他上一盤『蛋炒飯』來測試一下他的能力。

等熱騰騰冒着香濃鍋氣的『蛋炒飯』上桌後,Chef Master 馬上嚐了一口,本來慈眉善目的神情突然立刻大變,讓侍立在旁的小明嚇到馬上跪下來。

Chef Master 眼睛睜開了!?

「你怎麼會用這麼鹹,『蛋炒飯』的做法我早就改成用 半湯匙 的醬油了,你這應該還是一湯匙的量吧?」Chef Master 嚴厲地問著。

小明跪著拿出 食譜本本 上『蛋炒飯』的食譜,上頭的確記錄著是用一湯匙的醬油。

問題點:若食譜已經收錄到 食譜本本 後,Chef Master 對於菜色的設計有任何的更動,小明店內的 食譜本本 無法被即時更新 😫

於是小明跟 Chef Master 討論了一個調整做法,就是在食譜上標示這張食譜的 『最大壽命 (max-age)』

最大壽命的意思就是從這張食譜下山開始,過了多久之後這張食譜就會失效了。若食譜失效的話小明又要上陽明山拿取新版本的食譜,避免上述的問題再次發生。

Cache 有效期限 (max-age)

上述的故事情境也是會上演在

當伺服器已經更新了某些檔案,但是瀏覽器的 Cache 卻依然還是舊版的內容

因此我們在 Cache-Control 可以用 max-age 的參數設定這個 Cache 的 最大有效期限,單位爲
若不常更換的檔案,也許我們可以設定 1年 的有效期限: 60(秒) * 60(分) * 24(小時) * 365(天) = 31536000(秒)

1
Cache-Control: max-age=31536000

例如 Google 搜尋頁面的 Logo,也許他們預期不會一直頻繁地更換,所以設定了 max-age=31536000

Google Logo Cache max-age

MDN 上面關於 max-age 的流程已經圖解的很棒了,所以稍微調整一下 For 目前的 Case 直接使用囉。

Image From MDN

Expires

Expires 是 HTTP/1.0 的標頭,跟 max-age 不一樣的是,他裏面的值是日期的內容。

他是用來判斷在這個日期之後的內容應該被判定爲 stale 過期狀態,例如:

1
Expires: Wed, 21 Oct 2015 07:28:00 GMT

但有一點要注意的是,若 Cache-Control 已經有定義了 max-age 的話,那 Expires 的值就會被忽略掉囉,反之,就會以他作爲這個內容是否過期的判斷。

Etag / If-None-Match

食譜上有了 max-age 有效期限雖然很棒,但是如果當時後 Chef Master 在 max-age 寫了 432000 秒(5天),到了 5 天之後發現過期了,小明依然要跑一趟陽明山。到了山上問了 Chef Master 後才發現『蛋炒飯』的料理方法,繼上次改過之後就沒有在動過了,Chef Master 爲了確保小明的 食譜本本 是最新的,於是還是叫小明拿着 一模一樣 的『蛋炒飯』食譜回到店裏。

問題點:等食譜失效後,跑了一趟老遠才發現,食譜依然沒有更改過,卻又要帶一份一模一樣的食譜回來

於是小明提出了一個新的解法:

當每次他從山上拿食譜回來的時候,Chef Master 會再多給他這個食譜的 版本號碼 ,例如 W/v1 。下次如果發現這個食譜過期的話,小明就順手地帶上這組 W/v1的版本號碼,跟山上的 Chef Master 核對食譜的版本號碼是否一樣,如果一樣的話,就不用在帶一份一樣的食譜下山了,這不就可以 解決帶一份一模一樣的食譜回來 的問題了嗎? 😍

但是等食譜過期後,上一次陽明山確認版本是免不了的過程,但跟 Chef Master 確認版本的號碼沒有更動之後,就可以不用再帶一樣的東西回來囉 😆

Etag

Etag 就像是上述講的這份資料的 版號,可以在伺服器回覆的 Response 裡頭找到,例如:MDN 的首頁

E-Tag

If-None-Match

當在第一次的回覆(Response)帶有 Etag 的時候,下次若已經超過了 max-age 的期限的話,瀏覽器就會帶著 Etag 的值 W/"cb80fdf89ab2bcac3be8a431229f0ef8",並且放在 if-none-match 的欄位裡,跟著請求一起送出。

If-None-Match

若伺服器發現這個版號沒有更改,就會回覆 304 (Not Modified) 的回覆,所以就不需要在帶同樣一份的資料回來,也大大地減少了檔案的傳輸量喔 😍

Cache Eviction

因為客人點菜的料理五花八門,這本 食譜本本 也越來越厚,都快要從一樓疊到二樓這麼高了。

因爲每頁食譜有了 max-age 有效期限 ,所以小明都會花點時間檢查哪些食譜沒有過期,如果沒有過期就會標記爲 fresh ,若過期的話就會標記爲 stale

對於那些過期的食譜,小明也不會馬上丟棄掉,還記得每張食譜都有版本號碼了嗎?

因此小明一樣可以帶著這個食譜的 版本號碼 去山上問 Chef Master 是否有更新,若沒有更新的話就不需要帶一樣的食譜下山,只是下山之後要記得更新那張食譜的 age資訊,讓他標記爲 fresh 的狀態即可。

版本化檔名

就這樣經過上述的流程調整,小明的店經過好長一段時間的成長期。直到一天 Chef Master 發生了一個意外。

2 天前當小明上山來跟 Chef Master 要『鮭魚炒飯』的食譜的時候,Chef Master 似乎是因爲昨天沒睡好,不小心把『鮭魚』寫成『章魚』。這是何等天大的錯誤,怎麼會發生在 Chef Master 身上呢?

於是 Chef Master 在發現後,立馬更改了食譜上的內容,只是那張食譜上的 max-age 有效期限 寫著是 3 天之後,也就是明天!也就是說小明明天才會上山來確認『鮭魚炒飯』的食譜是否有更新。OMG!OMG!OMG! Chef Master 像熱鍋上的螞蟻地想要 修正這個錯誤 ,一想到他多年來辛苦建立的名聲,只因為一字之差就要化為灰燼,他就無比地焦慮。

Chef Master 完全體爆發?

問題點:若想要在 有效期限內 更新 食譜的話,之前的做法似乎都無法達到這個效果。

因爲在有效期限內 小明就會直接拿店內裡的 食譜本本直接來用了,而不會再上山一次問 Chef Master 是否有更新 😅

等到隔日小明上山之後,Chef Master 才趕緊跟他說修正這個錯誤,也好險在那之前沒有人點了『鮭魚炒飯』,所以 Chef Master 的招牌被 hold 住了!

於是他們一起想了這個問題的解決方案,經過了 3 天 3 夜終於想到解法了!

就是將小明店裏面的菜單上的招牌由 Chef Master 遠端操控顯示。例如本來小明的菜單上寫著『鮭魚炒飯』,後來因爲 Chef Master 有更改這道食譜後,他就直接透過遠端操控的方式,將招牌上『鮭魚炒飯』改成『鮭魚炒飯●改』。

因此客人看到『鮭魚炒飯●改』的時候就會跟小明點菜,小明一發現在 食譜本本 裏面沒有這道『鮭魚炒飯●改』的食譜的時候,他就知道必須上山一趟取得最新的『鮭魚炒飯●改』的食譜了。

如果之後又有更新『鮭魚炒飯●改』的時候怎麼辦呢?

沒錯,就是把菜單上改成『鮭魚炒飯●改v2』、『鮭魚炒飯●改v3』的做法。

這樣做法連帶有一個好處,就是可以把 max-age 有效期限 拉得很長,例如 31536000 (一年),所以大部分的時間食譜都沒有更新的話,就可以不用上山拿新的版本的食譜。

若有更新的話,會在菜名直接更新後面的內容,例如 ●改v1●改v2●改v3之類的,小明就知道要更新了。

原本的那些舊的食譜怎麼辦呢?

還記得每張食譜上有 max-age 有效期限 嗎? 一等到過期小明被小明發現了,又發現上面的版本號碼不一樣的時候,就會被小明丟掉啦~

好的,我們現在把鏡頭交給棚內! 🎥


上述的故事情境比較容易發生在 css 或者是 js 的檔案之中。

若今天好不容易修正了一個 bug,卻因爲 max-age 有效期限 尚未過期而無法更新,只能看著 bug 掛在線上無能爲力,身爲一個開發者應該會跟 Chef Master 一樣著急的吧?

於是我們可以在每次的 cssjs 的檔名上做點手腳,幫他後面加上一個像是 版號 或者一串類似簽名(Fingerprint) 的亂碼。

因此當 html 內引用的檔名從一開始的 app.v1.js 變成 app.v2.js 之後,瀏覽器無法在 Cache 找到 app.v2.js 的資料,所以就會在發一次請求到伺服器取得 app.v2.js 的資料,確保使用的 js 檔爲最新的版本。

因爲 MDN 也有很棒的圖解,所以我也順便貼上來囉!

Image From MDN

這個方法雖然很不錯,但每次都要自己手動處理上述版本卻是有他的麻煩之處,因此 Webpack 提供了自動化流程的做法,因爲跟這篇的主題較不相干,所以就留給有需要的大大囉!

Cache 的種類

其實剛剛上述講的所有情境都是假設 Cache 是放在個人的瀏覽器端的情況下,也就是private cache 的情境,但是 Cache 真的只能放在瀏覽器嗎?

MDN 的解釋,Cache 分成兩大類:

  • private cache 私人快取
  • shared cache 共享快取

private cache 顧名思義就是不會與他人共享的,因此會存放在每個使用者的瀏覽器裡; shared cache 則是可以大多使用者可以共享的資料,又可以分成 gateway cachesCDNproxy caches 等等不同階段的 Cache。

結論

上述 Cache 的內容概念大多都是參考 MDN HTTP Caching 的內容,除了故事跟用來測試筆刷的塗鴉不是 🤣

Cache 從一開始的想法是基於 『同樣的一份檔案不需要重新傳輸一次』 的思考下出發。

當達成這個效果的時候也發現了衍伸的問題:新鮮度 是否過期的問題。因此有了 max-ageetagif-none-match 等等的配套做法。

Cache 也有分很多不同的類型,而上述大多討論的是瀏覽器端的 Cache 內容。但 Cache 不僅止於此,例如伺服器也有記憶體 Cache 之類的,不過那又是另外一個故事了。

Cache 的部分就差不多聊到這裡囉,簡單地介紹了 Cache 是什麼與使用情境,希望用故事的方法能夠幫助大家更簡單地理解概念。😀


最後,如果覺得喜歡這種圖文介紹的文章的話,歡迎加入 Chan-Chan-Dev Facebook 的粉絲團,在發佈的時候就有比較多機會收到通知喔!😍

最後也感謝你的閱讀,下次再見囉 😍

參考文章