【系統設計 30 概念】#06 微服務架構與系統防護 — 訊息佇列、API 閘道與冪等性

測驗:微服務架構與系統防護

共 5 題,點選答案後會立即顯示結果

1. 微服務架構與單體架構的核心差異是什麼?

  • A. 微服務架構使用更快的程式語言,所以效能更好
  • B. 微服務將應用拆分成多個獨立服務,可以各自部署和擴展
  • C. 微服務架構不需要網路通訊,所以更簡單
  • D. 微服務架構只能用在大公司,小專案不適用

2. 在 Message Queue 中,當 Consumer 處理完訊息後要做什麼?

def handle_message(ch, method, properties, body): order = json.loads(body) send_email(order[‘user’]) ch.basic_ack(delivery_tag=method.delivery_tag)
  • A. 把訊息轉發給下一個 Consumer
  • B. 刪除整個 Queue 以釋放記憶體
  • C. 發送 ACK 確認,告訴 Queue 訊息已處理完畢
  • D. 回傳結果給 Producer,完成同步呼叫

3. 當 Client 收到 HTTP 429 狀態碼時,代表什麼意思?

  • A. 請求過多,被 Rate Limiting 限流了,需要等待後重試
  • B. 伺服器內部錯誤,服務已經掛掉
  • C. 認證失敗,需要重新登入
  • D. 請求的資源不存在

4. API Gateway 在微服務架構中扮演什麼角色?與 Reverse Proxy 有什麼關鍵差異?

  • A. API Gateway 只負責負載均衡,跟 Reverse Proxy 功能完全一樣
  • B. API Gateway 是資料庫的統一入口,負責管理所有資料存取
  • C. API Gateway 取代了所有微服務,把功能集中在一起處理
  • D. API Gateway 是微服務的統一入口,除了流量轉發外還負責認證、限流、路由等,是 Reverse Proxy 的進化版

5. 一個支付 API 收到兩次相同的扣款請求(帶有相同的 Idempotency Key),正確的行為應該是什麼?

POST /api/charge Headers: Idempotency-Key: “abc-123-xyz” Body: { “amount”: 100, “user”: “alice” }
  • A. 執行兩次扣款,因為收到了兩次請求
  • B. 只扣款一次,第二次直接回傳第一次的處理結果
  • C. 拒絕第二次請求,回傳錯誤訊息要求使用新的 Key
  • D. 將兩次請求合併,扣款 $200

本篇是「系統設計 30 概念」系列的最終篇(第 6 篇),涵蓋概念 #26-#30。
來源影片:「System Design was HARD until I Learned these 30 Concepts」 by Ashish Pratap Singh (YouTube)

這篇在講什麼?

當系統從「一台伺服器搞定一切」成長到「幾十個服務各司其職」,你會遇到三個現實問題:

  1. 怎麼拆? 單體應用要怎麼拆成微服務
  2. 怎麼溝通? 拆開後的服務之間怎麼傳訊息
  3. 怎麼保護? 怎麼防止系統被打爆、被重複操作搞壞

本篇一次搞懂這五個概念: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 題,包含情境題與錯誤診斷題。

1. 你的電商平台在雙 11 期間,訂單建立後需要寄送確認信給用戶。但活動期間訂單量是平時的 50 倍,Email 服務處理速度跟不上。你應該怎麼設計這個流程? 情境題

  • A. 讓 Order Service 同步呼叫 Email Service,排隊等它慢慢處理
  • B. 把 Order Service 和 Email Service 合併成一個服務,減少網路開銷
  • C. 透過 Message Queue 讓 Order Service 把「寄信任務」放入佇列,Email Service 按自己的速度消化
  • D. 對 Email Service 做水平擴展到 50 倍,確保即時處理每一封信

2. 你的微服務架構有 15 個服務,前端 Client 需要呼叫其中 5 個不同服務的 API。目前 Client 直接用各服務的 IP 和 Port 去呼叫,但經常因為服務重新部署後 IP 變了而斷線。你應該怎麼改善? 情境題

  • A. 把所有服務的 IP 寫死在前端 config 檔裡,每次部署時手動更新
  • B. 加一個 API Gateway 作為統一入口,Client 只需連一個位址,由 Gateway 負責路由到正確的服務
  • C. 把 15 個微服務合併成一個單體架構,Client 只需記一個位址
  • D. 讓每個服務都用固定 IP,禁止重新部署時改變位址

3. 你的 API 開放給第三方開發者使用,免費方案每天 1000 次,付費方案每天 10000 次。最近有免費用戶寫了一個壞掉的迴圈,每秒發 500 個請求,導致其他用戶的請求變慢。你應該加入什麼機制? 情境題

  • A. 直接封鎖該用戶的 IP,並通知他修正程式
  • B. 增加伺服器數量來應對多出來的流量
  • C. 把所有 API 改為非同步處理,讓請求排隊
  • D. 加入 Rate Limiting,依 API Key 限制每秒與每天的請求數量,超過上限回傳 429

4. 小明的支付服務出了問題:用戶按一次付款按鈕,偶爾會被扣兩次錢。以下是他的程式碼,問題出在哪裡? 錯誤診斷

@app.post(“/api/charge”) async def charge(request: Request): body = await request.json() # 直接執行扣款 result = await process_payment(body[“amount”], body[“user”]) return {“status”: “success”, “charged”: body[“amount”]}
  • A. 沒有驗證 amount 是否為正數,可能扣到負數
  • B. 沒有實作 Idempotency Key 機制,當網路超時導致 Client 重試時,同一筆付款會被執行兩次
  • C. 沒有使用 Message Queue,應該改為非同步處理扣款
  • D. 應該用 GET 而不是 POST,因為 GET 天生具有冪等性

5. 小華的 Message Queue Consumer 有問題:訊息偶爾會被處理兩次(例如同一封 email 被寄了兩次)。以下是他的程式碼,問題出在哪裡? 錯誤診斷

def handle_message(ch, method, properties, body): order = json.loads(body) send_email(order[‘user’]) # 處理完畢,沒有其他動作 channel.basic_consume( queue=’order_completed’, on_message_callback=handle_message ) channel.start_consuming()
  • A. json.loads 應該改用 json.load,解析方式不對
  • B. 應該用 basic_get 而不是 basic_consume,才能手動控制取訊息的時機
  • C. 處理完訊息後沒有發送 ACK(ch.basic_ack),Queue 會認為訊息未處理成功而重新派送
  • D. start_consuming() 會造成無限迴圈,應該改用定時輪詢

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *