測驗:微服務架構與系統防護
共 5 題,點選答案後會立即顯示結果
1. 微服務架構與單體架構的核心差異是什麼?
2. 在 Message Queue 中,當 Consumer 處理完訊息後要做什麼?
3. 當 Client 收到 HTTP 429 狀態碼時,代表什麼意思?
4. API Gateway 在微服務架構中扮演什麼角色?與 Reverse Proxy 有什麼關鍵差異?
5. 一個支付 API 收到兩次相同的扣款請求(帶有相同的 Idempotency Key),正確的行為應該是什麼?
本篇是「系統設計 30 概念」系列的最終篇(第 6 篇),涵蓋概念 #26-#30。
來源影片:「System Design was HARD until I Learned these 30 Concepts」 by Ashish Pratap Singh (YouTube)
這篇在講什麼?
當系統從「一台伺服器搞定一切」成長到「幾十個服務各司其職」,你會遇到三個現實問題:
- 怎麼拆? 單體應用要怎麼拆成微服務
- 怎麼溝通? 拆開後的服務之間怎麼傳訊息
- 怎麼保護? 怎麼防止系統被打爆、被重複操作搞壞
本篇一次搞懂這五個概念:Microservices、Message Queue、Rate Limiting、API Gateway、Idempotency。
概念 26:Microservices(微服務架構)
一句話說明
把一個大程式拆成多個小程式,各自獨立運行。
最小範例:單體 vs 微服務
# 單體架構 — 一個程式包辦所有事
my-shop-app/
├── user.py # 用戶管理
├── order.py # 訂單處理
├── payment.py # 支付功能
└── notification.py # 通知推播
→ 全部打包成一個程式,部署在一台伺服器
Code language: PHP (php)# 微服務架構 — 每個功能是獨立的程式
user-service/ → 獨立部署、獨立資料庫
order-service/ → 獨立部署、獨立資料庫
payment-service/ → 獨立部署、獨立資料庫
notification-service/ → 獨立部署、獨立資料庫
→ 各自打包,可以分別部署在不同伺服器
Code language: PHP (php)服務之間怎麼溝通?
用戶下單的流程:
[Client] → [Order Service]
│
├──呼叫──→ [User Service] (REST API:確認用戶存在)
│
├──呼叫──→ [Payment Service] (REST API:處理扣款)
│
└──發訊息→ [Notification Service] (Message Queue:寄確認信)
Code language: CSS (css)翻譯:Order Service 需要其他服務幫忙時,用 API 直接呼叫(同步),或把訊息丟進 Queue 讓對方慢慢處理(非同步)。
微服務的取捨
| 面向 | 單體架構 | 微服務架構 |
|---|---|---|
| 部署 | 改一行就要重新部署整個程式 | 只需部署被修改的那個服務 |
| 擴展 | 整個程式一起擴展 | 忙的服務單獨擴展 |
| 開發 | 團隊踩來踩去 | 每組負責自己的服務 |
| 複雜度 | 程式內部呼叫,簡單直接 | 網路呼叫,需處理超時、斷線 |
| 除錯 | 看一份 log 就好 | 要追蹤跨服務的請求鏈路 |
Vibe Coder 怎麼看?
當你在 AI 生成的程式碼中看到以下特徵,表示它在設計微服務:
# 特徵 1:每個服務有自己的入口
# user_service/main.py
app = FastAPI()
@app.get("/users/{id}")
def get_user(id: int):
...
# 特徵 2:呼叫其他服務用 HTTP
import httpx
async def verify_payment(order_id: str):
response = await httpx.get(f"http://payment-service:8001/verify/{order_id}")
return response.json()
# ↑ 注意:這裡不是 import 另一個模組,而是「發 HTTP 請求到另一台機器」
Code language: PHP (php)必看懂:服務之間用 HTTP 請求溝通,不是 import 另一個檔案。
知道就好:gRPC、Protocol Buffers 等進階通訊方式。
概念 27:Message Queue(訊息佇列)
一句話說明
服務之間的「信箱」— 寄信的人丟進去就走,收信的人有空再看。
最小範例
[Order Service] → 放入訊息 → [ Queue ] → 取出處理 → [Email Service]
(Producer) │ (Message) │ (Consumer)
│ │
「訂單 #123 完成」 「寄確認信給用戶」
Code language: CSS (css)翻譯:Order Service 把「訂單完成」這件事放進 Queue,然後繼續做自己的事。Email Service 有空就從 Queue 拿出來處理,去寄信。兩邊完全不需要等對方。
為什麼不直接呼叫?
# 直接呼叫(同步)— 有問題
[Order Service] ──呼叫──→ [Email Service]
│
如果 Email Service 掛了?
Order Service 也卡住了!
# 透過 Queue(非同步)— 更穩
[Order Service] → [Queue] → [Email Service]
│
如果 Email Service 掛了?
訊息留在 Queue 裡,等 Email Service 恢復再處理。
Order Service 完全不受影響!
Code language: CSS (css)常見使用場景
場景 1:背景任務
用戶上傳圖片 → Queue → 圖片壓縮服務(慢慢處理,用戶不用等)
場景 2:流量削峰
雙 11 瞬間湧入 10 萬筆訂單 → Queue 排隊 → 後端慢慢消化
場景 3:事件驅動
用戶註冊 → Queue → 同時觸發:寄歡迎信 + 初始化設定 + 發優惠券
程式碼中的樣子
# Producer(放訊息)
import pika # RabbitMQ 的 Python 套件
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='order_completed')
channel.basic_publish(
exchange='',
routing_key='order_completed', # 指定放進哪個 Queue
body='{"order_id": "123", "user": "alice"}' # 訊息內容
)
# ↑ 放完就走,不等回應
Code language: PHP (php)# Consumer(收訊息)
def handle_message(ch, method, properties, body):
order = json.loads(body) # 解析訊息
send_email(order['user']) # 處理:寄信
ch.basic_ack(delivery_tag=method.delivery_tag) # 告訴 Queue:我處理完了
# ↑ ACK 很重要 — 沒有 ACK,Queue 會認為訊息沒處理成功,會重新派送
channel.basic_consume(queue='order_completed', on_message_callback=handle_message)
channel.start_consuming() # 持續監聽,有訊息就處理
Code language: PHP (php)代表工具比較
| 工具 | 特色 | 適合場景 |
|---|---|---|
| RabbitMQ | 傳統訊息佇列,功能完整 | 任務分派、工作佇列 |
| Apache Kafka | 高吞吐量,訊息可重複讀取 | 事件串流、日誌收集 |
| AWS SQS | 雲端託管,免維護 | 快速上線、不想自己架 |
必看懂:Producer / Consumer / Queue 三者的關係,以及 ACK 機制。
知道就好:Dead Letter Queue、Message Ordering、Partitioning。
概念 28:Rate Limiting(速率限制)
一句話說明
限制每個人在一段時間內能發多少請求,防止系統被打爆。
最小範例
用戶 Alice:1 秒內發了 100 個請求
前 10 個 → 200 OK(正常回應)
第 11 個起 → 429 Too Many Requests(被限流了)
HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
"error": "Rate limit exceeded. Try again in 30 seconds."
}
Code language: JavaScript (javascript)翻譯:系統設定每秒最多 10 個請求。超過的直接擋掉,回傳 429 告訴 client「你太快了,等一下再來」。
常見限流策略
策略 1:Token Bucket(令牌桶)
想像一個桶子,每秒自動放入 10 個令牌。
每個請求需要消耗 1 個令牌。
桶子空了 → 請求被拒絕。
桶子最多存 20 個令牌 → 允許短暫爆發流量。
[每秒補充 10 個]
↓
┌──────────┐
│ 🪙🪙🪙🪙🪙 │ ← 桶子(最多 20 個)
│ 🪙🪙🪙🪙🪙 │
└────┬─────┘
↓
[請求消耗令牌]
Code language: CSS (css)策略 2:Sliding Window(滑動窗口)
以「過去 60 秒」為窗口,計算請求數量。
窗口隨時間滑動,不是固定的「每分鐘重置」。
時間軸:
|----60秒窗口----|
← 滑動 ←
超過上限 → 拒絕
程式碼中的樣子
# 使用 FastAPI + slowapi 實現速率限制
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address) # 用 client IP 識別身分
@app.get("/api/search")
@limiter.limit("10/minute") # 每分鐘最多 10 次
async def search(request: Request, q: str):
return {"results": do_search(q)}
# ↑ 超過 10 次就自動回傳 429
Code language: PHP (php)為什麼需要?
沒有 Rate Limiting:
- 惡意用戶發送百萬請求 → 伺服器掛掉(DDoS 攻擊)
- 某個壞掉的程式無限重試 → 吃光所有資源
- 免費用戶和付費用戶搶同一份資源
有 Rate Limiting:
- 每個 IP 每秒最多 100 次 → 防 DDoS
- 每個 API Key 每天最多 1000 次 → 按方案分級
- 特定 endpoint 每分鐘 10 次 → 保護昂貴操作
必看懂:429 狀態碼代表被限流、Retry-After 告訴你等多久。
知道就好:Token Bucket 和 Sliding Window 的具體實作差異。
概念 29:API Gateway(API 閘道)
一句話說明
微服務架構的「大門口」— 所有請求都先經過這裡。
最小範例
沒有 API Gateway:
Client 需要知道每個服務的位址,自己去呼叫
[Client] → http://user-service:8001/users
[Client] → http://order-service:8002/orders
[Client] → http://payment-service:8003/pay
→ 如果有 20 個服務,Client 要記 20 個位址?
Code language: JavaScript (javascript)有 API Gateway:
Client 只需知道一個位址
[Client] → https://api.myshop.com/users ─→ [User Service]
[Client] → https://api.myshop.com/orders ─→ [Order Service]
[Client] → https://api.myshop.com/pay ─→ [Payment Service]
→ 只要記一個 api.myshop.com,Gateway 幫你轉發
Code language: JavaScript (javascript)API Gateway 做了哪些事?
請求的完整旅程:
[Client] → [API Gateway]
│
├─ 1. 認證:JWT Token 有效嗎?
├─ 2. 限流:這個用戶超過上限了嗎?
├─ 3. 日誌:記錄這筆請求
├─ 4. 路由:決定轉發給哪個服務
├─ 5. 轉換:把外部格式轉成內部格式
│
└─→ [對應的微服務]
Code language: CSS (css)翻譯:API Gateway 就像大樓的門衛 — 檢查證件(認證)、控制人流(限流)、登記訪客(日誌)、指引方向(路由)。
跟 Reverse Proxy 有什麼不同?
Reverse Proxy(反向代理):
- 基本的流量轉發
- 負載均衡
- SSL 終止
→ 通用工具,不管你的架構是什麼
API Gateway:
- 上述所有功能,加上:
- API 認證 / 授權
- 速率限制(Rate Limiting)
- 請求/回應轉換
- API 版本管理
- 開發者入口網站
→ 專為微服務 API 設計
Code language: JavaScript (javascript)一句話區分:Reverse Proxy 是「交通警察」,API Gateway 是「海關」。
Nginx 設定檔中的樣子
# nginx.conf — 簡化版 API Gateway 設定
server {
listen 443 ssl;
server_name api.myshop.com;
# 路由到 User Service
location /users {
proxy_pass http://user-service:8001;
}
# 路由到 Order Service
location /orders {
proxy_pass http://order-service:8002;
}
# 限流設定
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# ↑ 每個 IP 每秒最多 10 個請求
}
Code language: PHP (php)代表工具
| 工具 | 特色 |
|---|---|
| Kong | 開源、外掛豐富、可自架 |
| AWS API Gateway | 雲端託管、跟 AWS 生態系整合 |
| Nginx | 輕量、當 API Gateway 入門選擇 |
| Traefik | 容器友好、自動偵測服務 |
必看懂:API Gateway 是統一入口,負責認證 + 限流 + 路由。
知道就好:Service Mesh、Sidecar Pattern 等更進階的服務間通訊管理。
概念 30:Idempotency(冪等性)
一句話說明
同一個操作做一次和做十次,結果一模一樣。
最小範例:為什麼需要?
現實場景:用戶按了「付款」按鈕
1. Client 發送扣款請求 → 網路超時,沒收到回應
2. Client 不確定有沒有成功,自動重試
3. Server 收到兩次扣款請求
沒有冪等性:扣了兩次錢!用戶被收 $200 而不是 $100
有冪等性:第二次請求被辨識為重複,只扣一次 $100
怎麼做到的?Idempotency Key
第一次請求:
POST /api/charge
Headers:
Idempotency-Key: "abc-123-xyz" ← 唯一的請求 ID
Body:
{ "amount": 100, "user": "alice" }
Server 處理 → 扣款 $100 → 記錄 "abc-123-xyz" 已處理
第二次請求(重試):
POST /api/charge
Headers:
Idempotency-Key: "abc-123-xyz" ← 同一個 ID
Body:
{ "amount": 100, "user": "alice" }
Server 發現 "abc-123-xyz" 已經處理過 → 直接回傳上次的結果,不再扣款
Code language: JavaScript (javascript)翻譯:每個請求帶一個「身分證號碼」。Server 處理前先查「這個號碼做過了嗎?」做過就直接回傳舊結果。
HTTP 方法的天生冪等性
天生冪等(做幾次結果都一樣):
GET /users/123 → 永遠回傳同一個用戶(只是查詢)
PUT /users/123 → 永遠把用戶設為指定的值(整個覆蓋)
DELETE /users/123 → 刪一次是刪除,刪第二次還是「已刪除」
不是天生冪等(需要額外處理):
POST /orders → 每次呼叫可能建立新訂單
PATCH /users/123/balance → 「餘額 +100」執行兩次就 +200 了
程式碼中的樣子
# 實作冪等性的簡化範例
@app.post("/api/charge")
async def charge(request: Request):
idempotency_key = request.headers.get("Idempotency-Key")
# 1. 檢查這個 key 是否已經處理過
existing = await db.get(f"idempotency:{idempotency_key}")
if existing:
return existing # 直接回傳上次的結果
# ↑ 關鍵:不再執行扣款,而是回傳之前的結果
# 2. 第一次處理:執行扣款
result = await process_payment(request.body)
# 3. 儲存結果,標記這個 key 已處理
await db.set(f"idempotency:{idempotency_key}", result, expire=86400)
# ↑ 保留 24 小時,之後自動清除
return result
Code language: PHP (php)Vibe Coder 檢查點
看到支付、扣款、建立資源等操作時確認:
- [ ] 有沒有 Idempotency Key 機制?重複請求會不會造成重複操作?
- [ ] POST 請求有沒有防重複提交的保護?
- [ ] Client 端重試時,是否帶著相同的 Idempotency Key?
必看懂:Idempotency Key 的概念 — 用唯一 ID 防止重複操作。
知道就好:Exactly-once delivery、分散式交易的具體實作。
五個概念的關聯
一個完整的微服務系統長這樣:
[Client App]
│
▼
[API Gateway] ← 概念 29:統一入口、認證、限流
│
├── Rate Limiting ← 概念 28:擋掉過多請求
│
▼
[User Service] ←─ REST API ─→ [Order Service] ← 概念 26:微服務拆分
│
▼
[Message Queue] ← 概念 27:非同步通訊
│
┌─────┴─────┐
▼ ▼
[Email Service] [Payment Service]
│
Idempotency Key ← 概念 30:防重複扣款
Code language: CSS (css)翻譯:用戶的請求先經過 API Gateway(認證 + 限流),被路由到對應的微服務。服務之間用 API 或 Message Queue 溝通。涉及金錢的操作用 Idempotency Key 防止重複。
Vibe Coder 總檢查點
讀完本篇,當你在讀 AI 生成的系統設計或後端程式碼時:
看到微服務相關程式碼時:
- [ ] 各服務之間是用 HTTP 呼叫還是 Message Queue?(同步 vs 非同步)
- [ ] 如果某個服務掛了,其他服務會不會連帶受影響?
看到 Message Queue 相關程式碼時:
- [ ] Consumer 有沒有 ACK 機制?處理失敗的訊息會怎樣?
- [ ] 訊息格式是什麼?(JSON?Protobuf?)
看到 API 相關設定時:
- [ ] 有沒有 Rate Limiting?限制是多少?
- [ ] 有沒有 API Gateway?還是 Client 直接打後端服務?
看到寫入操作時:
- [ ] POST/PATCH 請求有沒有冪等性保護?
- [ ] 支付、轉帳類操作有沒有 Idempotency Key?
系列回顧:30 個概念一覽
恭喜你讀完了「系統設計 30 概念」系列全部 6 篇。回顧一下這趟旅程:
| 篇數 | 主題 | 涵蓋概念 |
|---|---|---|
| #01 | 網路與通訊協定 | IP、DNS、TCP/UDP、HTTP/HTTPS(概念 1-5) |
| #02 | API 設計與資料格式 | REST、GraphQL、gRPC、JSON/XML/Protobuf、WebSocket(概念 6-10) |
| #03 | 系統擴展與效能優化 | 垂直/水平擴展、負載均衡、CDN、Proxy、快取(概念 11-15) |
| #04 | 資料庫選型與儲存策略 | SQL/NoSQL、ACID、Replication、Sharding、CAP 定理(概念 16-20) |
| #05 | 分散式系統基礎 | 一致性雜湊、WebSocket/Webhook、Heartbeat、Bloom Filter、Checksum(概念 21-25) |
| #06 | 微服務架構與系統防護 | Microservices、Message Queue、Rate Limiting、API Gateway、Idempotency(概念 26-30) |
這 30 個概念不需要全部背起來。重要的是:當你在閱讀系統設計文件或 AI 生成的架構圖時,遇到這些詞彙不會一頭霧水,知道去哪裡複習。
系統設計不是考試,是一套解決問題的思維工具。當你需要設計一個新系統,或是讀懂現有系統的架構時,這 30 個概念就是你的工具箱。
進階測驗:微服務架構與系統防護
共 5 題,包含情境題與錯誤診斷題。