題目與範圍
為後端服務設計 Worker Pool:接收任務、限制同時執行數量,並在關閉時不靜默遺失已接收任務。說明佇列容量、接納或拒絕、取消、重試歸屬、結果關聯、指標和優雅關閉。
假設任務彼此獨立,可能呼叫下游 API。這裡討論行程內元件;若任務必須跨行程故障復原,應在更外層設計持久化佇列。
面試官考察什麼
面試官會看你是否給出明確的並發上限、有界記憶體模型和過載策略,也會追問取消如何到達 Worker、重試是否放大流量,以及關閉時如何區分排隊、執行中、完成和拒絕的任務。
作答前需要確認的問題
- 行程崩潰時允許遺失任務嗎?不允許時先放入持久化訊息系統。
- 任務是否冪等,是否可以安全重試?
- 下游限流、平均耗時和尾延遲目標是什麼?
- 佇列滿時是短暫阻塞、回傳
429,還是優先拒絕低優先級任務? - 取消只針對未開始任務,還是任務可以協作式中斷 I/O?
30 秒回答框架
「我會用固定數量的 Worker 和有限佇列實作有界提交。容量耗盡時回傳明確的過載結果;context 或取消訊號讓排隊任務退出,也讓執行中的任務協作式停止。每個任務有 ID 和終態,重試次數受限並由單一層負責。指標包括佇列深度、等待時間、活躍 Worker、拒絕、耗時和取消。關閉先停止接納,再按策略處理排隊任務,在截止時間內排空已接收任務,並回報未完成任務。」
分步深入
第一步:定義狀態機
使用 submitted → queued → running → succeeded|failed|cancelled。佇列滿時回傳 rejected,不要讓呼叫方無限等待。若呼叫方需要行程崩潰後復原,狀態必須持久化到行程外。
第二步:同時限制 Worker 與佇列
選擇 W 個 Worker 和容量為 Q 的佇列;記憶體上限約為 Q 個任務載荷加 Worker 堆疊。不要為每個請求建立執行緒或 goroutine。Python 的 ThreadPoolExecutor 提供 max_workers,但提交量無界時仍需要應用層的有界接納策略。
第三步:選擇接納和背壓
可以非阻塞提交、限時等待或按優先級拒絕。回傳穩定的過載碼,只有確認安全時才給 Retry-After。滿佇列上無限等待可能卡住請求鏈路;無界佇列則會把過載變成延遲和記憶體增長。
submit(job, deadline):
if stopping or deadline expired: return REJECTED
if queue.try_push(job): return ACCEPTED(job.id)
if policy == WAIT and wait_until(deadline) and queue.try_push(job):
return ACCEPTED(job.id)
return OVERLOADED第四步:實作協作式取消
把取消 context 傳給任務。尚未開始的任務可以移除;執行中的任務要在安全點檢查取消,並把訊號傳給下游客戶端。執行緒通常不能被安全強殺,因此要定義不可中斷 I/O 的「已取消」語義。
第五步:讓重試只有一個歸屬層
選擇由 Pool、任務處理器或持久化佇列中的一層調度重試。限制次數,使用帶抖動的指數退避,並區分可重試和永久錯誤。否則多層逾時會對同一依賴製造重試風暴。
第六步:關聯結果和錯誤
回傳任務 ID 或 future,不要共享可變結果槽。記錄原始錯誤、嘗試次數和終態。若採用輪詢,要說明結果保留時間和授權;若採用等待,要說明呼叫方斷線後任務怎麼處理。
第七步:設計優雅關閉
關閉時先停止接納,再按策略處理排隊任務,讓執行中的任務在截止時間前排空。Go 的流水線指南透過 done 訊號傳播取消;Worker Pool 也應遵循同樣原則。逾時後要回報未完成任務以便重放,只有契約明確時才能標記為放棄。
第八步:觀測真正的瓶頸
記錄佇列深度和年齡、活躍 Worker、利用率、接納/拒絕/取消數量、執行耗時、重試次數和下游錯誤。告警應關注持續佇列年齡與拒絕率,而不只看 CPU。W 應依據下游連線池、外部限流或 CPU 容量設定。
權衡與邊界
權衡一:固定 Worker 還是動態擴縮
固定數量容易預測並發並保護依賴。動態擴縮可以提高吞吐,但必須有全局硬上限,並統計每個副本的並發,否則各實例獨立擴張會壓垮下游。
權衡二:佇列滿時拒絕還是等待
拒絕能快速回饋並保護延遲;等待可以吸收短峰值,但必須有截止時間,不能長期佔用稀缺請求執行緒。
權衡三:行程內還是持久化佇列
行程內 Pool 延遲低、實作簡單。持久化 Broker 增加復原、重放能力和運維成本。若任務要跨部署、崩潰或多實例路由存活,應選持久化方案。
失敗演練與演進計畫
演練一:下游故障
讓任務都慢速失敗,觀察佇列年齡、拒絕、逾時和有界重試,確認依賴不健康時並發不會繼續膨脹。
演練二:大規模取消
提交 10,000 個任務,執行前取消一半,並在排空期間停止服務。確認排隊取消任務不會執行,已接收的執行中任務最終都有可回報狀態。
演練三:副本滾動發布
滾動發布期間同時執行兩個版本,確認每個實例遵守本地上限;需要系統級上限時,再驗證持久化佇列或全局限流器。
常見錯誤與追問
錯誤一:無界緩衝
無界佇列只是在隱藏過載,最終會表現為記憶體或延遲崩潰。容量和拒絕策略必須可觀測。
錯誤二:重複重試
HTTP 客戶端和任務處理器同時重試會相乘。指定單一重試歸屬,並傳遞嘗試次數。
錯誤三:把取消當作強殺執行緒
多數執行環境不能安全強殺任意任務。使用協作式檢查、可取消 I/O 和明確的放棄策略。
錯誤四:排空前不停止接納
新任務會讓佇列永遠非空。等待排空截止時間前先關閉接納。
錯誤五:忽略副本級並發
10 個副本各有 20 個 Worker 就是 200 個並發呼叫。要說明上限是本地、分片還是全局協調。
錯誤六:沒有結果保留策略
future 和輪詢記錄都需要過期、授權和失敗路徑,否則已接收任務會造成無界儲存。