工程 · 4 min read
從面試的一句話,到追出一個 cache 迴圈
同一個網域、同樣的 TTL,HTML 離開 CDN edge 的比例卻是圖片的 17 倍。這個系列從一個 HTTP request 開始,一路追到 load balancer 後面的 cache 迴圈。
我以為 CDN 是一個方塊。追完這個問題,我才知道它是一條鏈。
故事
我原本只懂系統設計面試程度的 CDN:「加一層 CDN,使用者載入就會變快」。
後來我看 CDN 的報表,發現一件怪事。同一個網域、同樣的 TTL,HTML 離開 edge 的比例卻是圖片的 17 倍。追下去,我發現有一整條路線的檔案常常不留在 cache 裡。這條路線依副檔名收 html、css、js,其中大型 HTML 最嚴重。
為了搞懂原因,我從「一個 request 是什麼」開始往下挖。這個系列照我學的順序寫,每一篇都是我在追查途中補上的一塊。
先認識幾個詞
整個系列用同一個比喻:一家連鎖店,有一個中央倉庫和很多分店。
| 名詞 | 白話解釋 | 比喻 |
|---|---|---|
| Request | 瀏覽器向伺服器要一個檔案 | 顧客來店裡點一樣東西 |
| Header | 附在 request 或回應上的小標籤 | 訂單上的備註欄 |
| Origin | 檔案真正存放的伺服器 | 中央倉庫 |
| Cache | 先存一份副本,下次直接拿 | 分店貨架上的存貨 |
| Edge server | 離使用者很近、存著副本的伺服器 | 你家附近的分店 |
| CDN | 一大群 edge server 組成的網路 | 整個連鎖分店系統 |
| Load balancer | origin 的入口,把 request 分給後面不同的機器 | 倉庫門口的分貨員 |
| Proxy | 站在中間,幫忙拿檔案再轉交的程式 | 幫你跑腿去倉庫拿貨的人 |
| Bypass header | 叫 cache「不要用你的副本」的標籤 | 備註寫「請直接去倉庫拿新貨」 |
顧客向附近分店要東西,分店沒貨才回中央倉庫。這個故事的問題是:倉庫門口還有一個隱藏的小貨架,而且它看不懂備註。
路線圖
- CDN 是什麼:CDN 是一群靠近使用者的 edge server。
- 一個 HTTP request:一個 request 就是一次跨網路的函式呼叫。
- Load balancer:一個入口,依規則分給很多後端:兩種 matching 方式,以及藏在 LB 裡的 cache。
- Cache 的新鮮度:fresh、stale 和 304:fresh、stale,以及用 304 refresh。
- CDN 名詞一頁表:hit、miss、offload 等名詞。
- Cache header:誰設定,誰讀取:每個 header 都要有人設定、有人讀取。
- Akamai property:一份 edge 設定:一個 property 就是一份 edge 設定。
- Akamai 工具:purge 和 CLI:purge,以及怎麼確認 purge 生效。
- 一個 request 走過的路:DNS、edge、load balancer、bucket。
- Load balancer 怎麼選後端:同一個網域,兩條路線。
- 兩層 cache:我原本以為的樣子:我以為 bypass header 讓 LB cache 消失。
- 三個地方都在設定 TTL:TTL 從 storage 傳到 client,途中只會被壓低。
- 症狀:HTML、CSS、JS 一直跑回 origin:拆開數字,問題只剩路線。
- Bypass header 只在有規則的地方有效:proxy 路線的 cache 不讀 header。
- Stale-while-revalidate:先回舊檔,背景再刷新:先回舊檔,背景再 refresh。
- Proxy 不接受 HEAD,刷新永遠失敗:一個 405 讓 refresh 永遠失敗。
- 五個零件串成一個迴圈:所有零件接起來。
- 我學到的:cache 不是一個方塊,是一條鏈:我學到的事,以及它帶來的方向。
為什麼會這樣:三個條件交叉
- Bypass header 只在有規則的地方有效:bucket 路線的 LB cache 會讓路,proxy 路線的不會(Bypass header 只在有規則的地方有效)。
- 舊副本照樣往前送:LB cache 過期後先回舊檔,副本帶著很大的
Age到達 Akamai(Stale-while-revalidate:先回舊檔,背景再刷新)。 - Refresh 永遠失敗:LB cache 用 HEAD 做 refresh,proxy 只接受 GET(Proxy 不接受 HEAD,刷新永遠失敗)。
三個條件各自都不嚴重。接在一起,每個大型 HTML 的 request 都跑回 origin(五個零件串成一個迴圈)。
我學到的
我現在會跟著一個 request 走完整條路。每到一層,我都會問三個問題:這裡有沒有 cache?它讀哪些 header?purge 清得到它嗎?(我學到的:cache 不是一個方塊,是一條鏈)
加一層 CDN 會變快。但要知道 request 停在哪一層,你得一層一層走過去。
參考來源:ByteByteGo: How does CDN work, RFC 9111: HTTP Caching, RFC 5861: stale-while-revalidate