測驗:分散式系統與內容傳遞 — CAP 定理、CDN 與即時通訊
共 5 題,點選答案後會立即顯示結果
1. 在 CAP 定理中,為什麼 P(分區容錯)被認為是「必選」的?
2. 為什麼大檔案(如圖片、影片)不適合直接存進資料庫,而應該使用 Blob Storage?
3. CDN 第一次收到某個用戶的請求時,節點上還沒有快取。此時 CDN 會怎麼做?
4. WebSocket 和 Webhook 最根本的差異是什麼?
5. 一個社群媒體平台正在設計「按讚」功能。根據 CAP 定理,這個功能在分散式系統中應該偏向哪種設計?為什麼?
**系列**:系統設計 30 概念(第 5 篇,共 6 篇)
**難度**:L2-進階
**前置知識**:第 1 篇(HTTP、Latency)、第 3 篇(水平擴展)、第 4 篇(Replication 一致性)
**來源**:System Design was HARD until I Learned these 30 Concepts — Ashish Pratap Singh
這篇在講什麼
前幾篇我們學了資料庫怎麼擴展、怎麼複製。但當系統分散在多台機器上,新問題來了:
- 資料一致跟系統可用,你只能選一個?(CAP 定理)
- 大檔案放哪裡?怎麼讓全球用戶快速取得?(Blob Storage + CDN)
- 怎麼做到「即時」通訊,不用一直問 server 有沒有新消息?(WebSocket vs Webhook)
本篇涵蓋系統設計 30 概念的第 21-25 個。
21. CAP Theorem(CAP 定理)
一句話說明
分散式系統不可能同時滿足一致性、可用性、分區容錯 — 三選二,而且分區容錯是必選的。
三個字母分別是什麼
| 字母 | 名稱 | 白話翻譯 |
|---|---|---|
| C | Consistency(一致性) | 不管你問哪台 server,拿到的資料都一樣 |
| A | Availability(可用性) | 不管什麼時候問,都能拿到回應(不會 503) |
| P | Partition Tolerance(分區容錯) | 就算某些 server 之間斷線了,系統還能繼續運作 |
為什麼三個不能同時滿足
想像你有兩台 server(Server A 和 Server B),它們之間的網路斷了(這就是「分區」):
用戶 ──→ Server A ──✕──→ Server B
有新資料 還是舊資料
這時候你有兩個選擇:
選擇 1:保一致性(CP) — Server A 說「我跟 Server B 同步不了,先不回應你,等網路恢復」
- 結果:資料一致,但用戶暫時用不了(犧牲可用性)
- 適合:銀行轉帳 — 寧可讓你等,也不能帳戶金額不一致
選擇 2:保可用性(AP) — Server A 說「我先用手上的資料回你,之後再跟 Server B 同步」
- 結果:用戶能用,但可能看到舊資料(犧牲一致性)
- 適合:社群媒體按讚數 — 晚幾秒更新沒關係,重要的是能刷動態
為什麼 P 是必選的
在真實世界中,網路一定會出問題 — 海底電纜被鯊魚咬、機房跳電、路由器當掉。所以 P(分區容錯)不是選項,是前提。真正的選擇是:
**CP 還是 AP?一致性優先,還是可用性優先?**
Vibe Coder 檢查點
看到系統設計討論時確認:
- 這個功能對資料一致性的要求有多高?(金流 vs 按讚數)
- 當部分系統掛了,應該拒絕服務還是回傳可能過時的資料?
- AI 幫你設計的系統如果用了多台 server,有考慮資料同步的問題嗎?
22. Blob Storage(物件儲存)
一句話說明
專門存大檔案的雲端儲存 — 圖片、影片、PDF 往這裡丟。
為什麼需要 Blob Storage
資料庫(MySQL、PostgreSQL)適合存結構化資料:用戶名稱、訂單編號、交易記錄。但如果你要存:
- 一張 5MB 的商品圖片
- 一段 200MB 的教學影片
- 一份 50MB 的 PDF 報告
硬塞進資料庫會讓它慢到爆炸。Blob Storage 就是專門幹這件事的服務。
它怎麼運作
你的應用程式
│
├─ 結構化資料 ──→ 資料庫(PostgreSQL)
│ 用戶名稱、訂單... ↑ 存 URL 指向 Blob
│ │
└─ 大檔案 ──────→ Blob Storage(S3)
圖片、影片... 回傳一個公開 URL
白話翻譯:資料庫裡存的是「這張圖片的網址」,圖片本身放在 Blob Storage。
代表服務
| 服務 | 雲端平台 |
|---|---|
| S3(Simple Storage Service) | AWS |
| Cloud Storage | Google Cloud |
| Blob Storage | Azure |
AI 幫你寫的程式碼裡可能出現
import boto3
s3 = boto3.client('s3')
# 上傳檔案到 S3
s3.upload_file('photo.jpg', 'my-bucket', 'images/photo.jpg')
# ↑ 本地檔案 ↑ 儲存桶名稱 ↑ 存在哪個路徑
# 產生一個臨時下載連結(1 小時後過期)
url = s3.generate_presigned_url('get_object',
Params={'Bucket': 'my-bucket', 'Key': 'images/photo.jpg'},
ExpiresIn=3600)
# ↑ 這個 URL 可以直接給用戶下載,不需要經過你的 server
Code language: PHP (php)翻譯:把本地的 photo.jpg 上傳到雲端的 my-bucket 儲存桶,然後產生一個暫時的下載連結。
Vibe Coder 檢查點
看到 AI 的程式碼處理檔案上傳時確認:
- 大檔案是存到 Blob Storage(S3 等),不是塞進資料庫?
- 有沒有用 presigned URL?(不要讓所有檔案都公開可讀)
- 儲存桶的權限設定合理嗎?(AI 有時會設成全公開)
23. CDN(Content Delivery Network,內容傳遞網路)
一句話說明
在全球各地放快取伺服器,讓用戶從最近的節點取內容,不用跨洋拿資料。
為什麼需要 CDN
假設你的 server 在美國,台灣的用戶要看一張圖片:
沒有 CDN:
台灣用戶 ──→ 跨太平洋 ──→ 美國 Server ──→ 跨太平洋回來
延遲:200ms+
有 CDN:
台灣用戶 ──→ 台灣 CDN 節點(已快取)
延遲:20ms
CDN 就是把你的靜態檔案(圖片、CSS、JS、影片)複製到全球各地的伺服器。用戶請求時,自動導到最近的節點。
它的工作流程
第一次請求:
用戶 ──→ CDN 節點(沒有快取)──→ 向原始 Server 拿 ──→ 快取起來 ──→ 回給用戶
第二次請求(同一地區的用戶):
用戶 ──→ CDN 節點(有快取了)──→ 直接回給用戶 ✓
CDN 適合什麼 vs 不適合什麼
| 適合放 CDN | 不適合放 CDN |
|---|---|
| 圖片、影片 | 個人化內容(你的購物車) |
| CSS、JavaScript 檔案 | 即時變動的資料(股價) |
| 字型檔案 | 需要驗證身份的 API 回應 |
| 靜態 HTML 頁面 | 資料庫查詢結果 |
代表服務
| 服務 | 特色 |
|---|---|
| Cloudflare | 免費方案很大方,也提供安全防護 |
| AWS CloudFront | 跟 S3 無縫整合 |
| Akamai | 老牌 CDN,企業級 |
Blob Storage + CDN 的黃金組合
在實務中,這兩個常常一起用:
上傳流程:
用戶上傳圖片 ──→ 你的 Server ──→ 存到 S3(Blob Storage)
讀取流程:
用戶瀏覽圖片 ──→ CDN 節點 ──→(如果沒快取)──→ 從 S3 拿
↓
直接回給用戶(超快)
S3 負責存檔,CDN 負責快速分發。
Vibe Coder 檢查點
看到 AI 的系統架構時確認:
- 靜態資源(圖片、JS、CSS)有沒有走 CDN?
- CDN 的快取時間設多久?(太長會看到舊版,太短效果不好)
- 更新檔案時有沒有做 cache busting?(例如檔名加 hash:
app.3f7a.js)
24. WebSockets
一句話說明
client 和 server 之間的持續雙向通訊通道 — 兩邊都能隨時發訊息。
跟一般 HTTP 有什麼不同
一般 HTTP(像寄信):
Client: 「有新消息嗎?」 ──→ Server: 「沒有」
Client: 「有新消息嗎?」 ──→ Server: 「沒有」
Client: 「有新消息嗎?」 ──→ Server: 「有!給你」
↑ 要一直問(polling),很浪費
WebSocket(像打電話):
Client ←──建立連線──→ Server
(保持通話中)
Server: 「有新消息了!」 ──→ Client ← server 主動推
Client: 「收到,我要回覆」 ──→ Server ← client 也能隨時說
最小範例
前端建立 WebSocket 連線:
// 建立 WebSocket 連線(ws:// 是 WebSocket 協定)
const socket = new WebSocket('ws://example.com/chat');
// 連線成功時
socket.onopen = () => {
socket.send('你好!'); // 發訊息給 server
};
// 收到 server 的訊息時
socket.onmessage = (event) => {
console.log('收到:', event.data);
};
// 連線斷開時
socket.onclose = () => {
console.log('斷線了');
};
Code language: JavaScript (javascript)翻譯:連上 server 後,雙方隨時可以互傳訊息,不用每次都重新建立連線。
後端(用 Python FastAPI 為例):
from fastapi import WebSocket
@app.websocket("/chat") # 這不是一般的 HTTP 路由,是 WebSocket 路由
async def chat(ws: WebSocket):
await ws.accept() # 接受連線
while True: # 持續監聽(不會結束)
msg = await ws.receive_text() # 等用戶說話
await ws.send_text(f"你說了:{msg}") # 回覆
Code language: PHP (php)翻譯:server 接受連線後,進入一個無限迴圈,一直等著收訊息、回訊息。
什麼時候用 WebSocket
| 場景 | 為什麼需要 |
|---|---|
| 即時聊天(LINE、Slack) | 訊息要即時送達 |
| 多人遊戲 | 玩家動作要即時同步 |
| 股票行情 | 價格一直在變,要即時更新 |
| 協作編輯(Google Docs) | 每個人的打字都要即時顯示 |
代價
WebSocket 不是免費的午餐:
- 每個連線都占用 server 資源(記憶體、檔案描述符)
- 1 萬個用戶同時在線 = 1 萬條持續連線
- 需要處理斷線重連的邏輯
Vibe Coder 檢查點
看到 AI 用 WebSocket 時確認:
- 真的需要即時雙向通訊嗎?如果只是偶爾拿一次資料,用一般 HTTP 就好
- 有沒有處理斷線重連?(網路不穩時 WebSocket 會斷)
ws://是不加密的,正式環境要用wss://(就像http對https)
25. Webhooks
一句話說明
「事情做完了叫我」 — server 主動通知另一個 server,不用一直輪詢。
跟 WebSocket 有什麼不同
很多人搞混這兩個,其實它們解決的問題完全不同:
| WebSocket | Webhook | |
|---|---|---|
| 方向 | Client 和 Server 雙向 | Server 對 Server 單向 |
| 連線 | 持續保持連線 | 沒有持續連線,事件觸發時發一個 HTTP 請求 |
| 適合 | 即時互動(聊天、遊戲) | 事件通知(付款成功、程式碼推送) |
| 白話 | 打電話(一直通著) | 快遞到了打電話通知你(叫一次就掛) |
實際場景:Stripe 付款通知
沒有 Webhook(你一直問):
你的 Server: 「付款成功了嗎?」 ──→ Stripe: 「還沒」
你的 Server: 「付款成功了嗎?」 ──→ Stripe: 「還沒」
你的 Server: 「付款成功了嗎?」 ──→ Stripe: 「成功了!」
↑ 每秒問一次,浪費資源(polling)
有 Webhook(Stripe 通知你):
你的 Server: (做自己的事...)
Stripe: 「付款成功了!」 ──→ POST https://你的server.com/webhook/stripe
↑ 事情發生時才通知,高效
Code language: JavaScript (javascript)最小範例
你需要做的事 — 提供一個接收通知的 API:
from fastapi import Request
@app.post("/webhook/stripe") # Stripe 會打這個網址
async def stripe_webhook(request: Request):
payload = await request.json() # 拿到 Stripe 送來的資料
event_type = payload['type'] # 看是什麼事件
if event_type == 'payment_intent.succeeded':
order_id = payload['data']['object']['metadata']['order_id']
await update_order_status(order_id, 'paid') # 更新訂單狀態
return {"status": "ok"} # 回覆 Stripe:我收到了
Code language: PHP (php)翻譯:你的 server 提供一個網址,Stripe 付款成功後會自動發一個 POST 請求到這個網址,你接收到後更新訂單狀態。
常見的 Webhook 使用場景
| 服務 | 什麼時候觸發 | 通知什麼 |
|---|---|---|
| Stripe | 付款成功/失敗 | 交易結果 |
| GitHub | 有人 push 程式碼 | commit 資訊 |
| Slack | 有人在頻道發訊息 | 訊息內容 |
| Shopify | 有新訂單 | 訂單資料 |
Webhook 的安全注意事項
任何人都可以偽造一個 POST 請求打到你的 webhook URL,所以必須驗證來源:
import hmac
import hashlib
def verify_stripe_signature(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# ↑ 用 Stripe 給你的 secret 驗證這個請求真的來自 Stripe
Code language: PHP (php)翻譯:用一個只有你和 Stripe 知道的密鑰,驗算收到的資料有沒有被竄改。
Vibe Coder 檢查點
看到 AI 寫了 webhook 端點時確認:
- 有沒有驗證請求來源?(不驗證的話任何人都能偽造通知)
- 有沒有回覆正確的 HTTP 狀態碼?(通常回 200,否則對方會重試)
- 有沒有處理「重複通知」的情況?(網路問題可能導致同一事件通知兩次)
五個概念的關係整理
這五個概念在一個真實系統中可能同時出現:
用戶上傳圖片
│
├─ 圖片存到 Blob Storage(S3) ← #22
│
├─ 透過 CDN 分發給全球用戶 ← #23
│
├─ 多台 Server 之間的資料同步 ← #21 CAP 定理
│ 要選 CP 還是 AP?
│
├─ 即時通知好友「上傳了新照片」 ← #24 WebSocket
│ 透過持續連線推送
│
└─ 觸發第三方圖片辨識服務 ← #25 Webhook
辨識完成後回調通知你的 server
Code language: CSS (css)本篇總結
| 概念 | 一句話記憶 | 關鍵詞 |
|---|---|---|
| CAP 定理 | 分散式系統三選二,P 必選 | 一致性 vs 可用性 |
| Blob Storage | 大檔案倉庫,資料庫存 URL 就好 | S3、儲存桶 |
| CDN | 全球快取,靜態資源從最近的節點拿 | Cloudflare、延遲 |
| WebSocket | 持續雙向通話,即時互傳訊息 | ws://、即時聊天 |
| Webhook | 事情做完打電話通知,不用一直問 | POST 回調、事件驅動 |
必看懂 vs 知道就好
必看懂(面試和實務都常見):
- CAP 定理的三選二邏輯 — 會被問
- CDN 的運作原理 — 每個 Web 應用都在用
- WebSocket 和 Webhook 的差異 — 很容易搞混
知道就好(遇到再查):
- S3 的 ACL 權限細節
- CDN 的 cache invalidation 策略
- WebSocket 的心跳機制和自動重連實作
下一篇預告
第 6 篇(完結篇)會涵蓋概念 26-30:微服務架構、訊息佇列與常見的架構模式。當系統變大、團隊變多,怎麼把一個巨大的應用拆成可獨立部署的小服務?
進階測驗:分散式系統與內容傳遞 — CAP 定理、CDN 與即時通訊
共 5 題,包含情境題與錯誤診斷題。