iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Build on Google AI

從 Vibe Coding 到 Production:用 Google AI 打造上線守門員系列 第 14

Day 14|健康檢查、容量與降級:讓服務撐過流量與依賴故障

  • 分享至 

  • xImage
  •  

Day 13,我們替外部呼叫加入 Timeout、Backoff 與 Idempotency。

這些控制可以處理短暫故障,但不能創造不存在的容量。如果付款服務持續停止回應,或瞬間流量超過系統上限,不斷 Retry 只會增加負載。

系統這時需要回答三個問題:

  1. 目前 Instance 還能不能接收新流量?
  2. 哪些功能可以繼續提供?
  3. 超過容量的請求應該在哪裡被拒絕?

今天會建立一個本機 Demo,實測三種機制:

  • Liveness 與 Readiness Health Check
  • Circuit Breaker 與 Graceful Degradation
  • Concurrency Limit 與 Load Shedding

服務活著,不等於服務可以接流量

Health Check 不能只回答「Process 是否存在」。

一個 Node.js Process 可能仍在執行,但 Database Connection 已經中斷。它也可能卡在大量請求中,無法在合理時間內完成新工作。

常見 Health Check 可以分成三類:

類型 回答的問題 常見用途
Startup 應用程式是否完成啟動? 避免啟動期間被誤判為故障
Liveness Process 是否仍能執行基本工作? 判斷是否需要重新啟動
Readiness Instance 是否適合接收新流量? 決定 Load Balancer 是否繼續送流量

這三種檢查的處理方式不同。

Liveness 失敗通常會觸發重新啟動。Readiness 失敗則應先停止新流量,讓 Instance 保留時間恢復或完成既有請求。

如果 Liveness 檢查所有外部依賴,Database 短暫故障可能讓所有 Instance 同時重新啟動。這會增加啟動流量,也可能把原本的 Dependency 故障放大成整個服務故障。

建立 Day 14 的本機情境

今天的程式放在:

demo-app/capacity-demo/scenario.js

進入 Demo:

cd /media/mickey/777/ithome/demo-app

執行:

npm run capacity:demo

腳本會模擬:

  1. Database 正常,但 Payment Service 故障。
  2. Database 故障。
  3. 四次 Checkout 請求。
  4. 五個同時進入的工作。

所有服務與資料都只存在於本機記憶體。

Liveness 應該保持簡單

Demo 的 Liveness 回應:

/live  200 {"status":"alive"}

它只證明 Process 能接收請求並產生回應。

Liveness Endpoint 應符合以下條件:

  • 計算成本低。
  • 不執行完整業務流程。
  • 不等待多個外部服務。
  • 不回傳 Secret、Stack Trace 或內部拓樸。
  • 在 Event Loop 或核心執行元件失效時能夠失敗。

如果 Endpoint 每次都查詢 Database、Payment、Email 與 Gemini,它本身可能變成額外負載,也會讓單一 Dependency 控制整個服務是否重新啟動。

Readiness 要反映是否能服務

Payment 故障時,Demo 回傳:

/ready 200 {
  "status":"degraded",
  "checks":{
    "database":"up",
    "payment":"down"
  }
}

Database 故障時則回傳:

/ready database-down 503 {
  "status":"not_ready",
  "checks":{
    "database":"down"
  }
}

為什麼 Payment 故障時仍回傳 200?

因為這個 Demo 還能提供 Catalog。Payment 只影響 Checkout,Database 則是目前所有核心請求都需要的 Dependency。

這不是通用規則。

如果產品只有付款功能,Payment 就是核心依賴,Readiness 應回傳失敗。如果不同功能部署在不同服務,也可以只把 Checkout Service 移出流量。

團隊要先定義「Ready」代表哪些使用者流程可以運作,不能把所有 Dependency 放進同一份固定清單。

Health Check 不應暴露太多細節

內部 Readiness Endpoint 可以提供元件狀態,協助平台決定是否送流量。

公開 Endpoint 則不應列出:

  • Database Host
  • 內部 Service Name
  • Credential 狀態
  • Stack Trace
  • 套件版本
  • 私有 IP

攻擊者不需要知道每一個內部依賴。Health Check 只要提供平台與值班人員需要的資訊。

Health Check 的網路存取範圍也應受到限制。Google Cloud Load Balancer 會由特定 Prober 發出檢查,Firewall 必須允許正確來源與 Port,但不需要向所有網路公開管理資訊。

不是所有功能都要一起失敗

Payment 故障時,Demo 仍回傳 Catalog:

/catalog 200 {
  "products":3,
  "source":"cache",
  "stale":true
}

資料來自 Cache,而且可能不是最新版本。Response 明確標記 stale: true

這就是 Graceful Degradation。系統降低功能或資料品質,但仍保留部分使用者價值。

常見降級方式包含:

  • 推薦服務故障時,改顯示熱門商品。
  • 即時資料不可用時,顯示最近一次快取並標記更新時間。
  • Gemini 暫時不可用時,先執行固定規則掃描。
  • 圖片處理過載時,延後產生縮圖。
  • 搜尋服務過載時,縮小搜尋範圍。

降級不能欺騙使用者。

付款沒有完成時,系統不能顯示「付款成功」。資料不是最新版本時,也不應假裝內容仍是即時結果。

使用 Circuit Breaker 停止攻擊故障依賴

前兩次 Checkout 仍嘗試呼叫 Payment:

/checkout request=1 status=503 code=PAYMENT_DEPENDENCY_FAILURE
/checkout request=2 status=503 code=PAYMENT_DEPENDENCY_FAILURE

連續失敗達到門檻後,Circuit Breaker 開啟:

/checkout request=3 status=503 code=CHECKOUT_TEMPORARILY_UNAVAILABLE
/checkout request=4 status=503 code=CHECKOUT_TEMPORARILY_UNAVAILABLE
Payment dependency calls: 2
Circuit state:            OPEN

總共有四次 Checkout,卻只呼叫 Payment 兩次。

Circuit Breaker 開啟後,後續請求快速失敗。這可以減少:

  • 無效網路連線
  • 等待 Timeout 的請求
  • Dependency 的額外負載
  • 自己服務中的 In-flight Requests

完整 Circuit Breaker 通常有三種狀態:

  1. **Closed:**正常呼叫 Dependency。
  2. **Open:**暫時停止呼叫,直接回傳降級或錯誤。
  3. **Half-open:**等待一段時間後,允許少量測試請求確認 Dependency 是否恢復。

Circuit Breaker 需要 Metric 與 Alert。否則系統可能長時間停在 Open,團隊卻不知道 Checkout 已經關閉。

容量不只等於 QPS

每秒 100 個簡單 Health Check,可能比每秒 5 個大型 AI Review 更便宜。

Google SRE 指出,不同請求的資源成本可能差異很大。只用 Queries Per Second 估算容量,容易忽略:

  • CPU Time
  • Memory
  • Database Connection
  • Queue Length
  • 外部 API Quota
  • Token 數量
  • 同時執行的模型請求

對 Production Readiness Agent 來說,一個只包含 20 行規則的 Review,與掃描大型 Repository 的成本不同。系統需要根據實際資源建立限制。

沒有併發限制時會發生什麼?

Demo 同時啟動五個工作,每個工作執行 30 毫秒。

沒有上限時:

Unbounded: completed=5 peak_in_flight=5

五個工作全部同時執行,Peak In-flight 是 5。

這次實驗規模很小,所以全部完成。正式環境如果同時進入 5,000 個大型 Review,Memory、CPU、Gemini Quota 或 Socket 可能先耗盡。

使用 Concurrency Limit 與 Load Shedding

安全版本最多同時處理兩個工作:

Limited: accepted=2 rejected=3 peak_in_flight=2

超過上限的三個請求快速收到:

{
  "status": 503,
  "code": "OVER_CAPACITY",
  "retryAfterSeconds": 1
}

這個結果表面上比較差,因為三個請求遭到拒絕。

但服務保住了兩個可完成的工作,也把 Peak In-flight 固定在 2。如果系統接受所有請求後一起耗盡資源,最後可能五個都失敗。

Load Shedding 的目標不是讓每個請求成功,而是讓系統在超載期間繼續完成可控制的工作量。

排隊不一定比拒絕更好

另一種做法是把三個請求放進 Queue。

Queue 適合可以延後完成的工作,例如:

  • 背景報表
  • Email
  • 圖片處理
  • Repository Security Review

但 Queue 必須設定:

  • 最大長度
  • 單項工作 Deadline
  • 重試上限
  • Dead-letter Queue
  • 排隊時間 Metric
  • 公平性與租戶配額

如果 Queue 沒有上限,系統只是把 Memory Exhaustion 延後發生。

互動式 Checkout 已經超過使用者 Deadline 時,繼續排隊也沒有價值。系統應快速失敗,讓使用者稍後再試。

降級模式也需要定期測試

降級功能平常很少執行,因此最容易在真正事故時壞掉。

Google SRE 建議定期讓少量服務進入接近過載的狀態,以確認 Graceful Degradation 仍能運作。

團隊至少要測試:

  1. Payment 故障時,Catalog 是否仍能讀取。
  2. Circuit Open 時,Checkout 是否快速回應。
  3. Cache 過期時,畫面是否標記資料時間。
  4. 超過容量時,服務是否拒絕新工作。
  5. 依賴恢復後,Circuit 是否能回到 Closed。
  6. 降級模式是否產生 Metric 與 Alert。

沒有演練過的 Fallback,只是一項尚未驗證的假設。

Google AI Agent 如何降級?

Production Readiness Agent 會依賴 Gemini。Gemini 暫時不可用時,不應讓整個產品只剩空白畫面。

可以設計三層結果:

  1. **完整模式:**執行 Gemini Recon、Finding 與驗證。
  2. **降級模式:**只執行固定規則、Secret Scanning 與 Dependency Audit。
  3. **排隊模式:**保存 Review 工作,等待模型服務恢復後處理。

系統要清楚標記結果來源:

{
  "reviewMode": "static-only",
  "geminiStatus": "unavailable",
  "complete": false
}

固定規則掃描不能假裝成完整 AI Review。使用者要知道哪些檢查尚未完成。

大型 Repository 還需要限制:

  • 同時執行的 Review 數量
  • 每個 Review 的最大檔案數
  • Gemini Token Budget
  • 單一使用者或組織配額
  • Queue Length 與最長等待時間

Production Readiness Agent 要檢查什麼?

Agent 應檢查:

  1. 是否區分 Startup、Liveness 與 Readiness。
  2. Health Check 是否依賴過多外部服務。
  3. Readiness 失敗是否能停止新流量。
  4. 部分依賴故障時是否有清楚的降級行為。
  5. Circuit Breaker 是否具備門檻、恢復與監控。
  6. Queue、Thread、Connection 與併發是否有上限。
  7. 超過容量時是否快速拒絕低優先級工作。
  8. Load Shedding 是否回傳穩定錯誤與 Retry 指示。
  9. 降級模式是否定期測試。
  10. Metric 是否能觀察 Saturation、Rejected Requests 與 Circuit State。

一項可操作的 Finding 可以寫成:

問題:AI Review 沒有併發上限
證據:每個 Request 都立即建立新的 Gemini Review 工作
觸發條件:大量使用者同時提交 Repository
影響:Memory 與 Gemini Quota 耗盡,所有 Review 延遲或失敗
驗證:送出五個同時請求,Peak In-flight 增加到 5
修正:限制同時工作數,設定有上限 Queue,超量時回傳 OVER_CAPACITY

Agent 不能只看到 Promise.all() 就判定服務一定會過載。它還要確認輸入數量是否受控、每項工作成本,以及上層是否已經限制併發。

今天的結論

Health Check 告訴平台 Instance 是否能繼續運作與接收流量。Graceful Degradation 在 Dependency 故障時保留部分功能。Load Shedding 則在超過容量時保護仍可完成的工作。

今天的實驗得到四個結果:

  • Process 活著時,Liveness 回傳 200。
  • Database 故障時,Readiness 回傳 503。
  • Payment 故障時,Catalog 使用 Cache 繼續服務,Checkout 快速失敗。
  • Concurrency Limit 把 Peak In-flight 從 5 限制為 2。

明天,我們會處理備份、資料庫遷移、復原與回滾。服務即使能撐過流量,錯誤部署仍可能破壞資料。

參考資料


上一篇
Day 13|錯誤處理、Timeout 與 Retry:避免小故障變成大事故
下一篇
Day 15|備份不是有做就好:資料庫遷移、復原與回滾演練
系列文
從 Vibe Coding 到 Production:用 Google AI 打造上線守門員16
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言