測驗:快取、CDN 與流量管理 — 代理伺服器與負載均衡
共 5 題,點選答案後會立即顯示結果
1. 快取的核心概念是什麼?
2. 正向代理(Forward Proxy)和反向代理(Reverse Proxy)最大的差異是什麼?
3. CDN 最適合用來放哪一類內容?
4. 如果你的後端有三台伺服器,硬體規格各不相同(一台 16 核、一台 8 核、一台 4 核),最適合使用哪種負載均衡演算法?
5. 「快取雪崩(Cache Stampede)」是什麼情況?以下哪個做法可以幫助避免?
你的應用跑起來了,資料庫也連上了。但隨著使用者變多,每個請求都去資料庫查一次,伺服器開始喘不過氣。這篇教你系統設計中最常見的四個加速元件:快取、CDN、代理伺服器、負載均衡器。
這篇文章要解決什麼問題?
如果你有以下困惑,這篇文章適合你:
- 聽過 Redis、CDN 這些名詞,但不確定它們在系統裡扮演什麼角色
- 分不清正向代理和反向代理到底差在哪裡
- 知道負載均衡器可以「分散流量」,但不清楚它是怎麼決定把請求送去哪台伺服器的
- 想跟 AI 討論架構時,能說出「我這邊應該加一層快取」而不只是「我的網站好慢」
前置知識
本篇假設你已經了解:
- 客戶端與伺服器之間如何透過 HTTP 溝通(見【系統設計白話文】#03)
- API 是什麼、請求和回應的基本概念
第一章:快取 — 空間換時間的藝術
一句話說明
快取就是「把常用的東西放在更近、更快的地方」。
你去圖書館查資料,每次都要走到書架、找書、翻頁。如果你把最常查的那幾頁影印放在桌上,下次直接看桌上的影本,就快多了。這就是快取。
快取的核心邏輯
第一次請求(快取未命中 Cache Miss):
客戶端 → 伺服器 → 資料庫(慢)→ 存一份到快取 → 回傳給客戶端
第二次請求(快取命中 Cache Hit):
客戶端 → 快取(快!)→ 直接回傳
翻譯:第一次還是得去原始來源拿資料,但拿完之後存一份副本。下次有人問同樣的問題,直接給副本,不用再跑一趟。
快取的四個層級
系統中不只一個地方可以放快取,從離使用者最近到最遠:
| 層級 | 在哪裡 | 放什麼 | 白話解釋 |
|---|---|---|---|
| 瀏覽器快取 | 使用者的電腦 | 圖片、CSS、JS 檔案 | 「這張圖昨天下載過了,不用再下載」 |
| CDN 快取 | 全球各地的節點 | 靜態資源、熱門頁面 | 「台灣使用者從台灣節點拿,不用跑去美國」 |
| 應用層快取 | 伺服器的記憶體(Redis) | API 回應、計算結果 | 「這個排行榜 10 秒前才算過,直接給上次的結果」 |
| 資料庫查詢快取 | 資料庫本身 | 常見查詢的結果 | 「這個 SQL 剛跑過,結果還在記憶體裡」 |
最小範例:Redis 快取怎麼用
import redis
cache = redis.Redis()
def get_user_profile(user_id):
# 先查快取
cached = cache.get(f"user:{user_id}")
if cached:
return cached # 快取命中,直接回傳
# 快取沒有,去資料庫查
profile = db.query(f"SELECT * FROM users WHERE id = {user_id}")
# 查到後存入快取,設定 300 秒後過期
cache.set(f"user:{user_id}", profile, ex=300)
return profile
Code language: PHP (php)逐行翻譯:
cache.get(...)— 去 Redis 問「你有沒有這筆資料?」if cached: return cached— 有的話直接回傳,省掉資料庫查詢db.query(...)— 沒有的話,去資料庫拿cache.set(..., ex=300)— 拿到後存進 Redis,300 秒後自動刪除
快取失效策略:資料過期了怎麼辦?
快取最大的挑戰不是「怎麼存」,而是「什麼時候該丟掉」。資料庫裡的資料更新了,快取裡還是舊的,使用者就會看到過期資訊。
| 策略 | 白話解釋 | 適合場景 |
|---|---|---|
| TTL(Time To Live) | 設定保存期限,時間到自動刪除 | 排行榜、熱門文章(幾分鐘更新一次沒關係) |
| LRU(Least Recently Used) | 空間滿了,先丟掉最久沒人用的 | 記憶體有限時的通用策略 |
| Write-through | 寫資料庫的同時也更新快取 | 資料一致性很重要(如帳戶餘額) |
| Write-back | 先寫快取,之後再批次寫回資料庫 | 寫入頻繁但可以容忍短暫不一致(如按讚數) |
快速決策:
Q1: 資料多久不更新,使用者可以接受?
├─ 幾秒到幾分鐘 → TTL(設個合理的過期時間)
└─ 一秒都不行 → 繼續 Q2
Q2: 寫入頻率高嗎?
├─ 高(每秒幾百次以上)→ Write-back(先寫快取,批次同步)
└─ 不高 → Write-through(同時寫兩邊,保證一致)
第二章:CDN — 全球部署的快取
一句話說明
CDN 就是在全球各地放副本,讓使用者從最近的節點取資料。
CDN 解決什麼問題?
假設你的伺服器在美國。一個台灣使用者要看你網站上的圖片:
沒有 CDN:
台灣使用者 →→→→→(太平洋)→→→→→ 美國伺服器 →→→→→(太平洋)→→→→→ 台灣使用者
延遲:200ms+
有 CDN:
台灣使用者 →→ 台灣 CDN 節點(已經有副本)→→ 台灣使用者
延遲:20ms
效果:延遲從 200ms 降到 20ms,而且美國伺服器的負擔也變小了。
CDN 的運作方式
1. 第一個台灣使用者請求圖片
→ 台灣 CDN 節點沒有(Miss)
→ 去美國原始伺服器拿
→ 存一份在台灣節點
→ 回傳給使用者
2. 第二個台灣使用者請求同一張圖片
→ 台灣 CDN 節點有了(Hit)
→ 直接回傳,不用去美國
CDN 適合放什麼?
| 適合 | 不適合 |
|---|---|
| 圖片、影片、CSS、JavaScript | 使用者個人資料(每人不同) |
| 不常變動的頁面 | 即時交易資料(必須最新) |
| 公開的靜態內容 | 需要驗證權限的內容 |
重點:CDN 本質上就是「離使用者很近的快取層」,適合放「很多人都會要、而且不常變」的內容。
第三章:代理伺服器 — 請求的中間人
一句話說明
代理伺服器就是「幫你轉交請求的中間人」。
代理分兩種方向:正向代理(站在客戶端這邊)和反向代理(站在伺服器這邊)。名字很像,但做的事情完全不同。
正向代理(Forward Proxy)
正向代理
客戶端 A ──┐ ┌──→ 伺服器 X
客戶端 B ──┼──→ [Proxy] ──→ 伺服器 Y
客戶端 C ──┘ └──→ 伺服器 Z
Code language: JavaScript (javascript)白話翻譯:正向代理站在「客戶端這邊」,代表客戶端去跟伺服器溝通。伺服器不知道真正的客戶端是誰,只看到代理。
用途:
| 功能 | 白話解釋 | 實際例子 |
|---|---|---|
| 匿名存取 | 伺服器看不到你的真實 IP | VPN 服務 |
| 內容過濾 | 公司封鎖員工存取某些網站 | 企業防火牆 |
| 快取加速 | 多人存取同一網站,代理快取一份 | 學校網路代理 |
反向代理(Reverse Proxy)
反向代理
客戶端 A ──┐ ┌──→ 伺服器 1
客戶端 B ──┼──→ [Reverse Proxy] ──→ 伺服器 2
客戶端 C ──┘ └──→ 伺服器 3
Code language: JavaScript (javascript)白話翻譯:反向代理站在「伺服器這邊」,代表伺服器來接客戶端的請求。客戶端不知道背後有幾台伺服器,只看到代理。
用途:
| 功能 | 白話解釋 | 實際例子 |
|---|---|---|
| 負載均衡 | 把請求分散給多台伺服器 | Nginx 分流 |
| SSL 終止 | 統一處理 HTTPS 加密 | 後面的伺服器只需處理 HTTP |
| 安全防護 | 隱藏真實伺服器的 IP 和數量 | 防 DDoS 攻擊 |
| 快取 | 快取常見回應 | 靜態資源加速 |
正向 vs 反向:一張表搞懂
| 正向代理 | 反向代理 | |
|---|---|---|
| 站在誰那邊 | 客戶端 | 伺服器 |
| 誰不知道對方 | 伺服器不知道真正的客戶端 | 客戶端不知道真正的伺服器 |
| 誰設定的 | 客戶端(使用者)設定 | 伺服器管理員設定 |
| 核心目的 | 保護客戶端的隱私 | 保護伺服器的安全與效能 |
| 常見例子 | VPN、企業防火牆 | Nginx、Cloudflare |
記憶口訣:
- 正向代理 = 代理「正」在發請求的人(客戶端)
- 反向代理 = 代理請求要去的「反」方向(伺服器)
第四章:負載均衡器 — 流量的交通警察
一句話說明
負載均衡器就是「決定每個請求要送去哪台伺服器」的交通指揮。
當你的應用有多台伺服器時,需要有人來分配流量。這個「分配者」就是負載均衡器(Load Balancer)。
為什麼需要負載均衡?
沒有負載均衡器:
所有請求 →→→ 只有一台伺服器(撐不住就掛了)
有負載均衡器:
所有請求 →→→ [負載均衡器] ──→ 伺服器 1(處理 33%)
──→ 伺服器 2(處理 33%)
──→ 伺服器 3(處理 33%)
Code language: CSS (css)常見的負載均衡演算法
負載均衡器用什麼方式決定「這個請求送去哪台」?以下是四種最常見的演算法:
1. Round Robin(輪流分配)
請求 1 → 伺服器 A
請求 2 → 伺服器 B
請求 3 → 伺服器 C
請求 4 → 伺服器 A(輪回來了)
請求 5 → 伺服器 B
...
白話:排隊輪流,像發撲克牌一樣一人一張。
優點:簡單,不需要追蹤任何狀態。 缺點:不管伺服器忙不忙,照樣發。可能有一台已經很忙了還在收到新請求。
2. Least Connections(最少連線數)
伺服器 A:目前 5 個連線
伺服器 B:目前 2 個連線 ← 新請求送這裡
伺服器 C:目前 8 個連線
白話:看哪台最閒就送去哪台。
優點:能自動適應,忙的伺服器會少分到。 缺點:需要持續追蹤每台伺服器的連線數,稍微增加複雜度。
3. IP Hash(根據來源 IP 分配)
使用者 IP 192.168.1.1 → hash → 固定送到伺服器 A
使用者 IP 192.168.1.2 → hash → 固定送到伺服器 C
使用者 IP 192.168.1.3 → hash → 固定送到伺服器 B
Code language: CSS (css)白話:同一個使用者永遠送去同一台伺服器。
優點:同一使用者的 session 資料不用在伺服器之間同步。 缺點:某些 IP 請求特別多時會造成分配不均。
4. 加權分配(Weighted)
伺服器 A(16 核心):權重 5 → 分到 50% 流量
伺服器 B(8 核心):權重 3 → 分到 30% 流量
伺服器 C(4 核心):權重 2 → 分到 20% 流量
白話:能力強的多分一點,能力弱的少分一點。
優點:適合伺服器硬體規格不同的情況。 缺點:權重需要手動設定和調整。
演算法速查表
| 演算法 | 適合場景 | 不適合場景 |
|---|---|---|
| Round Robin | 所有伺服器規格相同、請求處理時間差不多 | 請求有輕有重、伺服器規格不同 |
| Least Connections | 請求處理時間差異大(如有些查詢很慢) | 需要 session 固定在同一台 |
| IP Hash | 需要 session 粘性(sticky session) | 使用者分布很不均勻 |
| 加權分配 | 伺服器硬體規格不同 | 所有伺服器規格一樣(沒必要加權) |
第五章:實戰組合 — Nginx 同時當反向代理和負載均衡器
在真實世界中,這些元件經常組合使用。最經典的例子就是 Nginx:它同時扮演反向代理和負載均衡器。
最小範例:Nginx 設定
# nginx.conf
http {
# 定義後端伺服器群組
upstream backend_servers {
server 192.168.1.10:8000 weight=3; # 高規格伺服器,多分一點
server 192.168.1.11:8000 weight=2;
server 192.168.1.12:8000 weight=1; # 低規格伺服器,少分一點
}
server {
listen 80;
# 靜態檔案 — Nginx 自己處理(不用麻煩後端)
location /static/ {
root /var/www;
expires 7d; # 瀏覽器快取 7 天
}
# API 請求 — 轉發給後端伺服器
location /api/ {
proxy_pass http://backend_servers; # 負載均衡到後端群組
}
}
}
Code language: PHP (php)逐行翻譯:
upstream backend_servers— 定義一群後端伺服器,Nginx 會把請求分散給它們weight=3— 這台伺服器的權重是 3,會收到較多請求listen 80— Nginx 在 port 80 接收所有請求location /static/— 如果請求的是靜態檔案,Nginx 自己回傳(當快取用)expires 7d— 告訴瀏覽器「這個檔案 7 天內不用重新下載」location /api/— 如果請求的是 API,轉發給後端伺服器群組proxy_pass— 這就是「反向代理」的動作:把請求代為轉發
完整的請求路徑
把本篇所有概念串起來,一個真實世界的請求可能經過這些層:
使用者(台灣)
│
▼
瀏覽器快取 → 有快取?→ 直接顯示(不發請求)
│ 沒有
▼
CDN 節點(台灣)→ 有快取?→ 直接回傳
│ 沒有
▼
反向代理 / 負載均衡器(Nginx)
│
├─ 靜態資源 → Nginx 直接回傳
│
└─ API 請求 → 選一台後端伺服器
│
▼
應用層快取(Redis)→ 有快取?→ 直接回傳
│ 沒有
▼
資料庫查詢 → 拿到結果 → 存入 Redis → 回傳
重點:每一層都在試圖「攔截」請求,越早攔截到,回應越快、後面的元件壓力越小。
常見坑與解法
坑 1:快取了不該快取的東西
症狀:使用者 A 看到使用者 B 的個人資料。
為什麼會發生: 把包含個人化內容的 API 回應放進了公共快取。快取的 key 只用了 URL,沒有區分不同使用者。
怎麼避免:
- 個人化內容不要放入公共快取(CDN、共享 Redis)
- 如果要快取,key 裡面要包含使用者 ID
- HTTP 回應標頭用
Cache-Control: private告訴 CDN 不要快取
坑 2:快取雪崩(Cache Stampede)
症狀:快取過期的瞬間,系統突然變得很慢或掛掉。
為什麼會發生: 大量快取同時過期,所有請求同時湧向資料庫。
怎麼避免:
- 不要讓所有快取的 TTL 都設一樣的時間,加上隨機偏移
- 設定「快取預熱」:在快取即將過期前就主動更新
- 使用互斥鎖:同一時間只允許一個請求去更新快取,其他請求等待
坑 3:搞混正向代理和反向代理
症狀:討論架構時用詞混淆,導致團隊溝通不順。
怎麼記:
- 你(客戶端)找人幫你跑腿 = 正向代理(VPN)
- 商店(伺服器)找人幫它接客 = 反向代理(Nginx)
Vibe Coder 檢查點
當你在跟 AI 討論系統架構時,確認這些事:
- [ ] 有沒有快取層? — 如果同樣的查詢會被反覆執行,問 AI:「這個 API 需不需要加 Redis 快取?」
- [ ] 靜態資源有沒有走 CDN? — 圖片、CSS、JS 檔案應該透過 CDN 分發,而不是每次都從你的伺服器取
- [ ] 有沒有反向代理? — 如果有多台伺服器,前面應該有 Nginx 或類似工具來分配流量
- [ ] 負載均衡演算法合理嗎? — 如果伺服器規格不同,用加權分配;如果需要 session 粘性,用 IP Hash
- [ ] 快取失效策略? — 確認有設定 TTL,並且不是所有快取都在同一時間過期
跟 AI 討論架構時怎麼說
入門版:
「我的網站很慢,怎麼辦?」
進階版(讀完本篇後):
「我的 API 回應時間有 500ms,其中 400ms 花在資料庫查詢。這個查詢結果 5 分鐘內不會變。我想加一層 Redis 快取,TTL 設 5 分鐘,用 write-through 策略。你覺得合理嗎?」
「我們有三台 API 伺服器,一台 16 核、兩台 8 核。現在用 Nginx 做負載均衡,想改成加權 Round Robin。幫我寫 Nginx 設定,16 核的權重設 2,8 核的設 1。」
小結
本篇介紹了四個讓系統更快、更穩的核心元件:
| 元件 | 一句話 | 核心思路 |
|---|---|---|
| 快取 | 常用的資料放在更快的地方 | 空間換時間 |
| CDN | 在全球各地放副本 | 把資料搬到使用者旁邊 |
| 代理伺服器 | 請求的中間人 | 正向保護客戶端,反向保護伺服器 |
| 負載均衡器 | 流量的交通警察 | 把請求分散給多台伺服器 |
記住這個原則:系統設計的優化就是在請求路徑上層層攔截。每一層快取、每一個代理,都在試圖讓請求「盡早得到回應」,不要一路跑到最後面的資料庫。
下一篇我們會進入資料庫的世界,討論 SQL vs NoSQL、分片、複製等讓資料層也能擴展的技術。
進階測驗:快取、CDN 與流量管理 — 代理伺服器與負載均衡
共 5 題,包含情境題與錯誤診斷題。