題目與適用情境
某訂單服務經壓測可在 p99 延遲 250 ms 下持續處理每秒 3,000 個請求。促銷期間,抵達流量升到每秒 8,000 個請求。庫存相依服務變慢,處理中的請求數與佇列等待時間同時上升,用戶端開始重試,而自動擴展的新執行個體要三分鐘才能就緒。關鍵下單、互動式庫存查詢與內部批次對帳共用這個服務。
請設計一套過載保護策略,讓處理程序維持回應能力,並盡量保住有價值的工作。回答需要涵蓋偵測訊號、並行與佇列界線、准入控制、請求優先順序、優雅降級、重試、復原與驗證。題中數字是面試假設,不代表某個真實正式環境。
這題歸類為後端,因為核心決策是單一服務如何保護自己的 CPU、記憶體、執行緒、連線與下游呼叫。分散式限流器負責依租戶或時間區間執行流量政策;本題處理的是合法流量仍超過服務目前容量的情況。逾時、重試與斷路器保護某一次相依服務呼叫,過載控制則決定服務現在還負擔得起哪些新工作。
面試官考察重點
好的回答會用本地工作量辨識飽和,不會只看請求速率。當一次請求變成原本的五倍昂貴,或相依服務變慢而讓連線占用更久時,固定的每秒請求門檻就會失效。處理中工作量、佇列等待時間、可用執行緒或連線、CPU、記憶體壓力與剩餘 deadline,才能顯示真正快要耗盡的資源。
第二個訊號是系統是否有明確界線。無界佇列只會把超額需求變成記憶體成長與過期請求。候選人應在瓶頸處限制並行數,維持小佇列或不排隊,並在昂貴的解析或下游呼叫前以低成本拒絕。目標是完成更多有用工作,不是接受所有請求,也不是追求更高的原始嘗試數。
面試官也會看策略是否理解業務。關鍵下單可以取得保留額度;庫存查詢可以在契約允許時使用短期快取;批次對帳可以暫停。優先順序內仍要確保租戶公平,避免大型客戶用完所有保留名額。最後還要有復原迴路:抑制重試放大,把擴展視為較慢的容量反應,逐步恢復准入,並在事故前持續演練降級路徑。
回答前需要釐清的問題
- 哪一種資源最先飽和? CPU 飽和適合本地並行或成本上限;慢相依服務需要獨立的連線/並行艙壁;記憶體成長或佇列等待過久需要更短佇列與更早拒絕。瓶頸不同,准入訊號也不同。
- 哪些操作必須保住,哪些可以降級? 本題假設下單關鍵、庫存允許稍微過期、批次對帳可暫停。如果法規或業務要求每個操作都讀取最新庫存,快取備援就不成立,應明確失敗。
- 端對端 deadline 是多少? 排隊同樣消耗 deadline。服務應丟棄已經不可能準時完成的工作,並將取消訊號傳到下游。
- 請求成本是否相近? 只有成本相近時,一個請求一個權杖才合理。昂貴端點可能需要加權許可或獨立資源池,避免便宜的關鍵工作被批次工作卡住。
- 用戶端能否安全重試? 被拒絕的讀取請求可以稍後再試;寫入訂單需要冪等鍵與結果查詢。服務必須區分「稍後重試」與「不要重試」,各層也要共享重試預算。
- 是否能轉移流量? 只有目標區域確實有經驗證的剩餘容量時,跨區承接才有幫助。盲目轉移過載會製造第二個故障。
30 秒回答架構
「我會圍繞瓶頸建立一個小型迴路。先用壓測決定單一執行個體的並行與佇列上限,再觀察本地處理中工作、佇列等待時間、CPU、記憶體、連線池與剩餘 deadline。飽和升高時,在昂貴處理前拒絕新工作,為下單保留容量,在同一優先層級內確保公平;庫存查詢採用經核准的快取降級,批次工作暫停。過載拒絕回傳明確的暫時無法服務訊號,用戶端只對冪等操作使用退避、隨機抖動與共享重試預算。自動擴展能補容量,但來不及成為第一道防線。復原時使用遲滯與逐步放量,再透過過載壓測驗證記憶體有界、有效吞吐、關鍵請求成功率、公平性、重試放大與復原過程。」
分步深入解答
先建立經過量測的容量界線。每秒 3,000 個請求只對壓測時的請求組合、相依服務延遲、執行個體數與 250 ms p99 目標有效。記錄這個界線對應的單一執行個體處理中工作量、CPU、記憶體、工作執行緒使用率與下游連線占用。請求速率是輸入,准入決策應跟隨最接近失效的資源。例如庫存變慢後,相同抵達速率會產生更多處理中呼叫,只看速率的控制器會反應太晚。
針對稀缺資源設定彼此獨立的並行限制。訂單處理器有整體上限,庫存呼叫與批次工作再使用更小的艙壁。配置昂貴資源前先取得許可,並在成功、失敗、逾時或取消時釋放。請求成本差異明顯時,使用加權許可或獨立端點資源池。依壓測得到的靜態硬上限是安全起點;自適應上限可能提高使用率,但必須有穩定回饋、護欄與快速回復設定的方法。
佇列必須明確且有界。短佇列可以吸收已知突發,但長度應由 deadline 餘裕決定,不能依剩餘記憶體決定。佇列已滿,或預估等待後沒有足夠時間執行時,直接拒絕。無界佇列不會創造容量,只會拉高尾端延遲、占用記憶體,並讓用戶端重試仍在等待的請求。既要監控深度,也要監控最舊工作的等待時間,因為少量昂貴工作同樣可能已經過期。
准入控制要提早、低成本且前後一致。閘道執行租戶契約配額與粗粒度流量上限;每個服務執行個體依本地飽和度保護自己擁有的資源。下游微服務已經過載時,上游應在完成註定被丟棄的工作前拒絕。請求關鍵等級需要沿呼叫鏈傳遞,避免同一張訂單在前段獲准,卻在已消耗大量工作後被後段隨機丟棄。
以明確的容量保留實作優先順序:
| 類別 | 過載動作 | 原因 |
|---|---|---|
| 下單 | 保留並行額度;達到自身上限後仍要拒絕 | 保護關鍵路徑,同時不給予無限容量 |
| 庫存查詢 | 優先回傳具有明確資料新鮮度契約的短期快取;否則拒絕 | 在不捏造答案的前提下降低下游工作量 |
| 批次對帳 | 暫停拉取,之後從持久化進度恢復 | 工作重要,但不需要互動式 deadline |
沒有公平性的優先順序會讓小租戶挨餓。在同一類別內套用租戶配額或公平排程,並為復原與控制流量保留最低份額。不要設計數十個優先層級;事故期間,維運人員必須能預測某類請求是否會被准入。
達到上限後,要在資料庫或相依服務呼叫前回傳。HTTP 503 表示暫時過載,可以附上 Retry-After;它不代表允許所有用戶端同時重試。用戶端需要有上限的指數退避、隨機抖動、deadline 與重試預算。只在一個合適層級重試,不讓每一層都試。建立訂單要重複使用冪等鍵;逾時且結果不明時先查詢原結果。呼叫端 deadline 已過的請求應取消下游工作,避免完成沒有人需要的回應。
優雅降級藉由減少工作量止損。若產品契約允許,庫存查詢可以略過可選的資料擴充,或使用具有最大存留時間的快取。批次消費者可以停止拉取訊息。把過期庫存標成最新庫存屬於不誠實的備援;必須使用最新資料時,應回傳明確的不可用結果。平時要讓一小部分流量持續經過降級路徑,因為從未使用的緊急路徑很可能在事故時失效。
自動擴展、跨區承接與增加容量仍有價值,但應位於准入控制之後。依原始請求數擴展可能在便宜流量到來時增加太多執行個體,卻在昂貴請求下反應過慢;指標應包含並行數、佇列等待時間或資源飽和度。新執行個體應預熱連線後再承接完整流量。容錯移轉前必須驗證目標容量。這些機制都不能取代本地界線。
復原門檻應低於進入過載的門檻。處理中工作、佇列等待時間與相依服務健康度持續低於退出線一段時間後,再分階段提高准入量。正常延遲穩定前繼續保留優先份額。這種遲滯與漸進放量可以避免系統在正常與過載間反覆切換,也防止只恢復一部分的相依服務再次被壓垮。
驗證不能停在標稱容量測試。依正式環境的請求成本組合重播流量,在庫存變慢且自動擴展延遲三分鐘的同時,將抵達速率從每秒 3,000 提高到 8,000 個請求。再加入同步用戶端重試、一個濫用租戶、過期 deadline 與相依服務復原。斷言佇列與記憶體有界、執行緒與連線穩定、拒絕成本夠低、下單取得承諾份額、租戶公平、降級誠實、重試放大受控,並且能逐步復原。要分別統計完成的有用操作、接受請求數與下游嘗試數。
高品質示範回答
「每秒 3,000 個請求是某種請求組合下的壓測結果,我會先確認界線處究竟是哪一種資源耗盡。庫存變慢期間,處理中呼叫數與連線占用比請求速率更能反映風險。我會為服務設定經過壓測的單一執行個體並行上限,為庫存呼叫再設一個更小的艙壁,並只保留一個仍能滿足請求 deadline 的短佇列。觸及任一界線後,在昂貴工作前拒絕。
流量分成下單、庫存查詢與對帳。下單有保留容量,但仍有硬上限;庫存只有在 API 明確資料新鮮度契約時才能使用短期快取;對帳暫停並從持久化進度恢復。每一類內部都做租戶公平,不能讓一個客戶拿走全部額度。我也會把關鍵等級傳給下游,避免請求到呼叫鏈末端才被隨機丟棄。
暫時過載回傳可辨識的 503;能夠估計時附上 Retry-After。用戶端仍要使用有上限的退避、隨機抖動、deadline 與共享重試預算。只讓一個層級重試,訂單寫入重複使用冪等鍵。呼叫端已逾時的工作要往下游取消。
因為新執行個體要三分鐘,自動擴展是較慢的容量迴路。我會依飽和訊號擴展並預熱新執行個體,本地准入則負責保住現有執行個體。復原時使用較低的退出門檻與分階段放量。
最後用每秒 8,000 個請求、慢庫存、延遲擴展、重試、混合請求成本與噪音租戶做過載測試。預期看到記憶體與佇列有界、有效吞吐穩定、下單份額與公平性成立、拒絕成本低、沒有重試風暴,並在庫存復原後受控回到正常狀態。」
常見錯誤
- 不斷提高佇列上限直到錯誤消失 → 接受的請求等待更久、占用記憶體、過期並引發重試,卻沒有增加執行容量 → 依 deadline 餘裕限制佇列並提早拒絕。
- 只依每秒請求數偵測過載 → 請求成本與相依服務延遲會改變,相同速率可能安全也可能致命 → 觀察本地處理中工作、佇列等待時間、資源飽和與相依資源池。
- 讓自動擴展成為第一道防線 → 三分鐘延遲足以讓佇列與重試破壞現有執行個體 → 先維持本地准入界線,再擴展以恢復餘裕。
- 給關鍵流量無限優先權 → 它仍會耗盡相同資源並讓復原工作挨餓 → 保留容量,但同時保留硬上限與公平性。
- 每個微服務都隨機卸載 → 請求在後段被拒絕前已經消耗上游工作,端對端有效成功率下降 → 傳遞關鍵等級,並在知道瓶頸後提早拒絕。
- 回傳 503 後讓所有用戶端重試 → 同步重試會放大超額流量 → 使用隨機抖動、deadline、單層重試與重試預算。
- 回傳未標示的過期資料 → 表面可用,卻破壞庫存語意 → 公開資料新鮮度契約,或明確失敗。
- 復原後立刻接回全部流量 → 剛復原的相依服務再次過載 → 使用遲滯、有界探測與逐步放量。
- 只統計接受流量 → 高接受率可能掩蓋逾時、浪費工作與重試 → 統計有效完成、拒絕成本、deadline 浪費與嘗試放大。
追問與應對
追問 1:為什麼不能只用分散式限流器解決?
分散式限流器適合執行契約配額、防止濫用與請求進入服務前的流量整形。它無法單獨感知庫存延遲已提高每個合法請求的成本。保留閘道限流,再由資源擁有者使用本地並行與佇列保護。如果請求成本穩定且服務只有一個瓶頸,保守的速率限制可能是更簡單且足夠的方案。
追問 2:並行上限如何決定?
先用正式環境的請求組合壓測,找到仍符合延遲與資源目標且保有餘裕的最高並行數,再在相依服務變慢時重複測試。穩態下並行、吞吐與耗時的關係可以用來檢查合理性,但無法保證突發流量或混合成本下的容量。先以僅觀察模式上線,再灰度拒絕,最後強制執行;程式、執行個體規格或相依服務變更後重新評估。
追問 3:如果 90% 的請求都宣稱自己關鍵呢?
此時標籤已無法幫助准入。關鍵等級應來自業務操作,由可信任的入口設定,而且每一類都有上限,只保留經量測的份額。關鍵類內部繼續做租戶公平或使用者穩定優先順序,避免只偏好最吵的用戶端。如果真正關鍵的需求也超過實體容量,仍會有關鍵工作失敗,契約必須說明取捨。
追問 4:自適應並行會不會更好?
它能比靜態上限更快跟隨服務耗時變化,但有雜訊的延遲與滯後回饋可能造成振盪或錯誤卸載。先從壓測後的靜態界線開始。只有同時具備最小/最大限制、平滑訊號、遲滯、穩定備援值,以及涵蓋快速惡化與緩慢復原的重播測試時,再導入自適應控制。
追問 5:深層呼叫鏈應該在哪一層卸載?
資源擁有者必須保留本地最後一道保護。它一旦發出過載訊號,上游應更早停止工作,並沿呼叫鏈維持相同關鍵等級。只在邊緣卸載無法精確知道下游狀態,只在葉節點卸載會浪費上游工作。實務方案是粗粒度邊緣政策、本地保護,以及上游可處理的過載訊號三者結合。
追問 6:哪些正式環境指標能證明策略有效?
依操作與優先層級統計有效完成量與延遲;准入、排隊、降級與拒絕數;最舊佇列等待時間;處理中工作;CPU、記憶體、工作執行緒與連線飽和度;deadline 過期工作;租戶公平性;每個邏輯請求的實體嘗試數;擴展就緒延遲;以及過載模式持續時間。關鍵份額失守、長期過載、拒絕成本接近正常處理成本,或復原放量反覆退回時都應發出警示。