具代表性的面試主題

Kubernetes 面試:如何設計大規模 LIST 回應的串流編碼?

系統設計困難
Offer.cc 編輯團隊發佈 更新

題幹

Kubernetes API Server 面對數萬物件的 LIST 請求時,如何用串流編碼降低記憶體峰值,同時保持回應格式、分頁一致性與失敗可診斷?

題幹與適用場景

一個叢集有數萬個 Pod 與自訂資源,控制器重啟時會同時發出全量 LIST。舊編碼器把整個 items 陣列序列化成連續緩衝區,API Server 記憶體出現尖峰。請設計串流編碼方案,說明 JSON 與 Kubernetes Protobuf 的範圍、與 limit/continue 分頁和 gzip 壓縮的關係、客戶端失敗恢復、背壓、可觀測性與回滾條件。

面試官考察點

  • 是否先定位記憶體峰值來自編碼緩衝,而不是把問題泛化成網路頻寬。
  • 是否區分串流編碼、分頁、壓縮和 watch,各自解決哪個瓶頸。
  • 是否能保持同一 LIST 回應的 JSON/Protobuf 結構、資源版本與錯誤語意。
  • 是否考慮慢客戶端、連線中斷、CPU 增長、代理緩衝與舊客戶端相容。
  • 是否給出可重現的壓測、指標、灰度與回滾門檻。

回答前需要澄清的問題

  1. 請求是單一 namespace 還是跨 namespace?物件大小、總量與並發 LIST 數決定記憶體預算。
  2. 客戶端要求一次完整回應,還是允許使用 limit/continue?允許分頁時可先降低單次規模,但不能取代伺服器編碼優化。
  3. 入口經過 HTTP/2、gzip 或反向代理嗎?代理是否緩衝回應會改變「邊編碼邊釋放」的收益。
  4. 目標是 JSON、Kubernetes Protobuf,還是兩者都要?編碼器覆蓋範圍會影響發布順序。

30 秒回答框架

我會先把記憶體問題定位到整個 items 陣列一次性序列化,再把編碼器改成逐項寫出:先寫集合前綴,逐個編碼 item,最後寫後綴。JSON 與 Kubernetes Protobuf 保持相同資源語意,分頁仍負責控制結果集大小,壓縮只負責頻寬。實作時用有界緩衝和寫入背壓處理慢客戶端,觀測 RSS 峰值、編碼 CPU、首位元組延遲、完成延遲與中斷率。先灰度 JSON,再驗證 Protobuf、代理與舊客戶端,超過門檻就回滾。

分步驟深入解答

1. 先拆解峰值記憶體

舊路徑通常同時持有物件列表、編碼中間結構與完整回應緩衝。物件數增加時,回應大小近似隨 items 總位元組成長;多個並發 LIST 會疊加峰值。串流方案只承諾降低編碼階段的額外緩衝,不能消除物件讀取、快取、排序或授權過濾本身的記憶體成本。先用堆分析與並發壓測確認瓶頸,再決定改編碼器或先限流。

2. 設計逐項編碼器

集合固定欄位先寫出,items 陣列開頭寫出後,編碼器對每個物件執行「序列化一個物件—寫入—釋放臨時緩衝」。不能把 item 拼進無限成長的 bytes。Kubernetes 官方實作針對 collection 的 JSON 與 Protobuf 編碼,重點處理佔大宗的 items,而不是改變 API 物件結構。

text
write(listPrefix)
for item in items:
    encoded = encodeOne(item)
    writeWithBackpressure(encoded)
    release(encoded)
write(listSuffix)

串流寫出改變的是記憶體生命週期,不改變物件順序、metadata、resourceVersion 或錯誤狀態定義。若客戶端中途斷線,伺服器停止後續編碼並釋放目前 item,不能把半個回應當成成功結果。

3. 區分分頁、壓縮和 watch

limit/continue 把一個大集合拆成多個一致性的分頁,減少單次請求規模,也讓客戶端逐步處理;它會引入 token 過期、重新開始與客戶端迴圈。串流編碼仍適用於單頁很大的回應,是伺服器編碼峰值優化。gzip 減少網路位元組,但壓縮器也可能累積緩衝,必須量測 flush 策略。watch 是持續事件流,語意與斷點恢復不同,不能取代一次一致的 LIST。

4. 處理背壓與代理

寫入 socket 受慢客戶端影響時,編碼器必須尊重 Write 阻塞與取消訊號,設定每連線緩衝上限;否則「逐項編碼」仍可能在代理或閘道重新聚合。記錄首位元組與最後位元組時間,區分編碼等待、網路背壓、代理緩衝與客戶端讀取慢。HTTP/2 frame 拆分不等於應用已釋放完整回應緩衝,必須在伺服器編碼層驗證記憶體曲線。

5. 相容、灰度與回滾

保持 JSON/Protobuf 的 wire 結構與 content negotiation,不改變 continue、resourceVersion 或錯誤碼。先對物件數高、並發低的資源型別啟用,比較 RSS 峰值、CPU、首位元組延遲、完整回應延遲、連線中斷與 API 錯誤率,再擴大範圍。若舊代理不接受分塊或壓縮回應,保留 feature gate 或依客戶端能力回退舊編碼器。回滾條件應是記憶體峰值、P99 延遲或錯誤率超過基線,而不只看平均吞吐。

高品質示範回答

我會把問題限定為 API Server 的編碼峰值。先用並發 LIST 壓測確認完整 items 緩衝占用,再讓編碼器寫集合前綴,逐個序列化並寫出 item,釋放臨時緩衝,最後寫後綴。降低的是編碼階段額外記憶體,不會自動降低物件讀取、排序或授權過濾成本。JSON 與 Kubernetes Protobuf 保持原結構;limit/continue 用於結果集分頁,gzip 用於頻寬,watch 保持另一種事件語意。寫入受 socket 背壓與取消訊號約束,代理緩衝單獨觀測。發布時先灰度 JSON,再覆蓋 Protobuf 與主要代理,比較 RSS 峰值、編碼 CPU、首位元組、完成延遲與中斷率;超過門檻就關閉 feature gate 或依客戶端回退。測試包含慢客戶端、連線中斷、空列表、大 item、分頁 token 過期與舊客戶端。

常見錯誤

  • 只加分頁 → 單頁仍可能很大,編碼器仍一次性構造緩衝 → 同時驗證逐項編碼和分頁策略。
  • 把 gzip 當記憶體解法 → 壓縮可能繼續持有緩衝 → 分別量測編碼、壓縮與 socket 階段。
  • 把 HTTP/2 frame 拆分當成伺服器串流編碼 → 應用層仍可能先生成完整 body → 檢查 API Server 堆與寫入路徑。
  • 允許無界緩衝等待慢客戶端 → 並發慢連線再次放大記憶體 → 設定連線上限、取消與背壓指標。
  • 改變 LIST 結構或 resourceVersion → 破壞客戶端與一致性語意 → 只替換編碼生命週期並做 wire 回歸。

追問及應對

如果客戶端在第 3000 個物件後斷線,伺服器怎麼處理?

取消編碼上下文,停止讀取後續物件,釋放目前緩衝並記錄中斷原因。客戶端不能把半個 body 當成功 LIST;若需要完整集合,依原分頁或從頭重新請求。

如果分頁已經限制在 500 個物件,為什麼仍要串流編碼?

500 個物件仍可能很大,尤其是自訂資源。分頁控制結果集邊界,串流編碼控制伺服器編碼期間的臨時記憶體,兩者解決不同階段,可以疊加。

如果代理把回應完全緩衝後才轉發,方案是否失效?

伺服器仍可降低自身編碼峰值,但客戶端首位元組與端到端釋放收益會被代理抵消。把代理緩衝作為部署前置檢查,按路徑記錄首位元組時間,必要時關閉代理行為或調整灰度範圍。

公開來源

同類題目

相關面試工具

用 Solve 整理系統設計回答

從澄清需求開始,展開規模、架構、元件選擇和取捨。

查看工具