題幹與適用情境
設計一個內部服務探索系統,服務 2,000 個邏輯服務與 10 萬個動態執行個體,執行個體分布在 3 個區域。容器重新排程、自動擴縮和滾動部署會持續改變位址;一次大規模部署可能在 2 分鐘內替換 1 萬個端點。呼叫端包含多種語言,不能要求每個團隊都維護複雜的探索 SDK。
題設把兩個時限分開。執行個體主動進入不可接流量狀態後,該變化從被控制平面觀測到停止新流量的 p99 不超過 3 秒。程序或節點突然消失時,硬故障偵測 p99 不超過 15 秒。探索控制平面即使中斷 10 分鐘,既有呼叫也應依靠最後已知端點繼續;這不代表期間能發現新執行個體或保證舊執行個體仍然存活。
以上服務數、執行個體數、區域數、部署規模與 SLO 都是面試假設。範圍包含註冊、租約、健康狀態、端點查詢與增量傳播、快取、流量摘除、多區域和驗證。請求負載平衡演算法只討論與探索相關的部分,業務 API、完整服務網格資料平面和公網 DNS 不在主要範圍。本題歸入 system-design,因為核心是跨控制平面、代理、健康檢查與呼叫鏈的整體架構。
面試官考察點
第一個訊號是能否區分註冊、探索、健康判斷與路由。註冊中心記錄「誰聲稱自己在哪裡」;健康系統判斷「現在是否應接流量」;探索把候選端點送到呼叫側;代理或客戶端才選擇一個端點。把四件事畫成一個資料庫,會漏掉傳播延遲與失敗邊界。
第二個訊號是控制平面與資料平面解耦。若每次業務請求都同步查詢中心,中心抖動會直接變成全站故障。強方案讓代理保存版本化快照,在背景接收變更;控制平面不可用時,資料平面繼續使用最後已知集合,並以短連線 timeout、受限重試與被動異常剔除抵禦陳舊端點。
第三個訊號是承認探索資訊一定可能陳舊。DNS TTL、代理快取、watch 延遲、故障偵測與滾動退出都會製造時間窗。強回答會定義「從哪個事件開始計時」,分別給主動摘除與硬故障 SLO,並計算探測週期、門檻與傳播時間。宣稱「強一致就不會打到壞執行個體」忽略網路分割和程序在讀取後立即故障。
最後看規模與維運。10 萬個執行個體若每 10 秒直接續租一次,就是每秒 1 萬次續租;部署期間還會有大量端點變更與 watch 扇出。候選人需要控制寫入放大、重連風暴、完整快照、跨區域故障域和誤判健康檢查,而非只列出 Consul、etcd 或 Kubernetes 名稱。
回答前需要釐清的問題
- 誰是註冊事實來源? 編排器已掌握 Pod 生命週期時,應由控制器產生註冊資訊;虛擬機或外部程序可以由帶工作負載身分的本機代理註冊。允許任意執行個體無認證自註冊會污染目錄。
- 3 秒從何時開始? 本題從控制平面接受 READY 到 DRAINING 或未就緒變化開始,不從應用第一次決定退出開始。若應用在上報前卡住,要由硬故障偵測路徑處理。
- 15 秒硬故障容許多少誤摘除? 連續三次探測失敗可降低瞬時封包遺失誤判,但 5 秒週期加 timeout 接近整個預算。若業務更怕誤摘除,需要被動錯誤率、區域容量與更長確認時間窗共同決定。
- 呼叫端需要執行個體 IP 還是穩定服務位址? 只需穩定 VIP 時,DNS 加平台負載平衡最簡單;需要版本、區域或分片中繼資料時,代理或客戶端探索更合適。
- 控制平面中斷時是 fail-open 還是 fail-closed? 一般內部服務可用最後快照繼續;涉及權限撤銷或強隔離的決策不能依賴陳舊探索快取,應由獨立身分與授權層拒絕。
- 是否允許跨區域自動容錯移轉? 無狀態讀服務可依策略切換;帶區域資料主權、單主寫入或高跨區成本的服務必須在中繼資料中明確禁止或限定。
30 秒回答框架
「我會把探索做成區域化控制平面和本機資料平面。編排器或認證代理把執行個體寫入分片目錄,狀態經過 STARTING、READY、DRAINING、UNHEALTHY、EXPIRED。只有 READY 進入可路由集合;主動退出先變成 DRAINING,再排空連線。區域 leader 對變更排序並產生單調 revision,分發層向 5,000 個節點代理推送增量;代理發現 revision 缺口就拉完整快照。
業務請求只查詢本機代理或穩定 VIP,不同步依賴中心。中心中斷時代理保留最後快照,同時以連線 timeout、受限重試與被動異常暫時剔除壞端點。健康檢查分啟動、就緒、存活與被動訊號;5 秒主動探測加三次失敗用於約 15 秒硬故障偵測。最後用滾動替換、網路分割、watch 中斷、重連風暴與錯誤探針做故障注入,量化摘除延遲、陳舊請求與復原收斂。」
分步深入解答
第一步:定義資料模型與狀態機
目錄鍵至少是命名空間、服務名稱和連接埠名稱,避免不同環境或協定碰撞。端點紀錄包含穩定執行個體 ID、位址、區域、可用區、版本、權重、能力標籤、狀態、租約到期時間和修訂號。標籤只允許預先定義的維度,禁止把任意高基數資料帶入探索面。
狀態機比單一 healthy 布林值更能表達生命週期:STARTING 不接流量;READY 可接新流量;DRAINING 停止新請求但允許既有連線排空;UNHEALTHY 是探測或被動訊號判定失敗;EXPIRED 表示租約未續。每次狀態變更都帶原因、來源與單調 revision,方便稽核亂序更新。
同一端點更新依執行個體 ID 和啟動世代去重。舊程序延遲抵達的續租不能讓已被替換的位址復活。若編排器是事實來源,控制器觀察期望狀態與實際就緒狀態;若使用自註冊,寫入端必須以工作負載身分認證,並限制它只能修改自己的服務與執行個體紀錄。
第二步:選擇探索模式,不把複雜度複製到每種語言
DNS 加穩定 VIP 適合呼叫端只需要服務名稱、平台已處理端點和健康狀態的情境。DNS 直接回傳執行個體位址雖然簡單,但 TTL 會在查詢量與陳舊時間之間取捨;不遵守短 TTL 的客戶端快取還會擴大風險。
客戶端探索能依版本、區域和負載選擇端點,代價是每種語言都要實作 watch、快取、負載平衡、重試與安全升級。題設包含多語言呼叫端,因此建議伺服器側探索:每個節點執行本機代理,或使用既有平台代理。應用呼叫穩定本機位址,代理維護端點集合並選擇後端。多一跳的成本換來統一語意與快速升級。
若已經全部執行在 Kubernetes 中,Service、DNS 與 EndpointSlice 通常已涵蓋基礎探索,不應重做註冊中心。獨立控制平面只在跨虛擬機、跨叢集、複雜版本路由或統一策略確有需求時引入,並優先消費編排器端點事實。
第三步:讓寫入路徑可排序,讓讀取路徑可陳舊
每個區域部署 3 或 5 個目錄副本,以共識 leader 接受註冊與狀態變更;依服務鍵或租戶分片,避免全域單一 leader 承擔 10 萬個執行個體。跨區域不做每次寫入的同步共識,區域故障不應阻塞其他區域。全域層只同步服務策略和允許的容錯移轉目標。
一次寫入成功表示該區域目錄以 revision 接受了變更,不表示所有代理都已看到。分發器把有序增量推給訂閱者;代理落盤最後完整快照與 revision。收到連續 revision 就套用增量,發現缺口、驗證失敗或保留日誌已截斷就拉取該服務完整快照。快照原子替換,不能讓一半新清單與一半舊清單混合。
讀取路徑允許有界陳舊來換可用性。代理記錄快照年齡、最後控制平面聯絡時間與 watch lag。超過一般陳舊門檻時告警,但控制平面 10 分鐘中斷期間仍使用最後集合。若所有已知端點都失敗,回傳明確的無可用後端,不可繞過策略請求任意區域。
第四步:把主動摘除與硬故障偵測分開
優雅退出由應用先撤回就緒狀態,目錄進入 DRAINING,分發到代理後停止選擇該端點;接著等待最長連線排空時間,再終止程序。應用本身也要停止接受新工作,因為探索傳播並非瞬時,舊代理或長連線可能仍持有位址。
硬故障沒有主動訊號。假設主動探測每 5 秒一次、連續 3 次失敗才摘除,故障恰好發生在一次成功探測後,單是取樣就可能接近 15 秒,再加單次 timeout 和傳播。要滿足 15 秒 p99,探測 timeout、排程抖動與分發必須一起編入預算,或縮短週期。代理觀察到連線被拒、timeout 或高錯誤率時,可以對本機端點做短期被動剔除,但不能憑一個呼叫端的局部網路問題全域註銷執行個體。
啟動、就緒與存活也不能混用。啟動訊號保護慢啟動;就緒失敗只停止流量;存活失敗才觸發重啟。把共用資料庫故障放進所有執行個體的存活檢查,會同時重啟整個服務並加重依賴壓力。關鍵依賴可以影響就緒,但要用短 timeout、抖動和容量保護避免探針風暴。
第五步:計算寫入、變更與扇出規模
10 萬個執行個體若每 10 秒直接續租,穩態為每秒 1 萬次續租。可以由編排器 watch 取代每執行個體心跳,或讓節點代理彙整續租並加入隨機抖動;租約到期仍是遺留紀錄的安全網,不應成為正常摘除的唯一機制。
兩分鐘替換 1 萬個端點,相當於平均每秒約 83 次新增和 83 次移除,也就是約 167 次成員變更。若每個變化都向 5,000 個節點代理獨立傳送,最差會形成每秒約 83.5 萬次投遞。實際訂閱應依代理所需服務篩選,在短時間窗合併同服務變化,並透過分層分發節點扇出。不能為了減少訊息把 3 秒主動摘除 SLO 合併成一分鐘批次。
完整快照同樣需要預算。每個端點若序列化後粗估 256 bytes,10 萬個端點的全域快照約 24.4 MiB;正常代理只拉取所訂閱服務,不拉全球目錄。重連採用指數退避與隨機抖動,分發層提供短期增量日誌,避免控制平面復原時 5,000 個代理同時請求完整快照。
第六步:定義跨區域與安全邊界
執行個體預設只註冊到所在區域,呼叫優先同區域同可用區的 READY 端點。區域目錄失去 quorum 時禁止接受新寫入,但本機代理繼續讀取快取。其他區域不自動把該區域端點改成健康,也不依跨區探測覆蓋本機事實。
服務策略宣告是否允許跨區域、僅讀或可寫、目標順序、容量上限和資料邊界。全域容錯移轉依明確策略觸發;無狀態讀服務可快速切換,單主資料庫寫入代理則必須先確認主權轉移。探索只回答候選位址,不能取代業務一致性協定。
註冊、註銷與 watch 都要求工作負載身分和最小權限;目錄變更寫入稽核日誌。代理驗證控制平面身分,敏感服務可結合雙向 TLS。端點標籤不是可信授權聲明,呼叫端仍要在連線或請求層驗證服務身分與權限。
第七步:用時間線與故障注入驗收
先驗證一次滾動替換:舊執行個體撤回就緒,記錄目錄接受 revision、代理套用 revision、最後一次新請求與程序退出時間;新執行個體只有啟動完成且就緒後才進入集合。斷言主動摘除傳播 p99 小於 3 秒,排空中的既有請求不中斷,也沒有未就緒執行個體收到流量。
接著終止程序但不送出註銷,驗證 5 秒探測、連續失敗門檻與分發總和滿足 15 秒 p99。分別注入單一代理網路故障、整個節點故障、目錄 follower 落後、leader 切換、區域失去 quorum、增量缺口、完整快照損壞和控制平面中斷 10 分鐘。復原時使用抖動重連,確認沒有完整拉取風暴。
線上指標至少包括註冊與續租寫入率、目錄寫入延遲、各服務 READY 數、狀態抖動率、探測延遲、watch lag、快照年齡、revision 缺口、完整回退次數、代理重連率、對已摘除端點的新請求數、陳舊端點連線失敗率和無可用後端次數。只有業務流量時間線能證明端點傳播生效,控制平面顯示健康仍不夠。
高品質示範回答
「我先把註冊事實、健康判斷、探索傳播與請求路由分開。區域目錄由共識組接收帶身分的執行個體變更,每筆紀錄有服務鍵、執行個體 ID、啟動世代、位址、區域、版本、狀態、租約與 revision。執行個體只有 READY 才進入可路由集合;退出時先變成 DRAINING、停止新流量、排空後再終止。硬故障走主動探測與租約,被動錯誤只讓單一代理暫時剔除,不能直接代表全域事實。
題設有多語言呼叫端,所以我不會讓每個程序直接 watch 註冊中心。5,000 個節點代理依需求訂閱服務,保存完整快照與單調 revision,連續套用增量,遇到缺口才拉該服務快照。業務請求只走本機代理,不同步查詢控制平面。控制平面中斷 10 分鐘時使用最後已知端點,搭配短連線 timeout、受限重試與本機異常剔除;代價是新執行個體不可見,舊位址可能陳舊,這必須透過指標揭露。
10 萬個執行個體每 10 秒續租會有每秒 1 萬次寫入,因此優先消費編排器 watch 或由節點代理彙整。兩分鐘替換 1 萬個端點約產生每秒 167 次增刪變更,依訂閱篩選、短時間窗合併和分層扇出,仍保留 3 秒摘除預算。每 5 秒探測且連續 3 次失敗已接近 15 秒,timeout 與傳播也要算入。
我會以流量時間線驗收:主動撤回後 3 秒內不再有新請求,硬崩潰 15 秒內退出候選集合;leader 切換、區域分割和控制平面停止 10 分鐘時既有流量繼續。最後讓 5,000 個代理帶抖動重連,驗證 revision 缺口會完整復原且不會壓垮中心。」
常見錯誤
- 每個請求先查註冊中心 → 控制平面延遲或故障直接進入業務路徑 → 在本機快取版本化端點,背景接收變化。
- 只有一個
healthy布林值 → 啟動、接流量、排空和需要重啟的語意混在一起 → 使用明確狀態機並記錄轉換來源。 - 把就緒失敗當作重啟理由 → 下游依賴故障會觸發整個服務重啟風暴 → 就緒負責摘流量,存活只判斷本程序不可復原故障。
- 認為短 DNS TTL 消除陳舊 → 客戶端快取、傳播與故障偵測仍有時間窗,查詢量也會上升 → 量化 TTL 取捨並保留資料平面容錯。
- 讓執行個體無認證自註冊 → 錯誤或惡意端點可進入內部流量 → 使用編排器事實或受限工作負載身分。
- 所有代理直接訂閱全球目錄 → 部署與重連造成寫入放大及完整快照風暴 → 依服務篩選、分層扇出、增量 revision 與抖動重連。
- 全域強一致複寫每個心跳 → 跨區域延遲或分割阻塞正常區域 → 區域內排序寫入,跨區只同步策略和允許的容錯移轉資訊。
- 探索中心回傳端點就視為呼叫成功 → 端點可能在讀取後立即故障 → 連線 timeout、受限重試、斷路器與冪等仍屬於呼叫協定。
- 健康檢查依賴所有下游 → 一個共用依賴故障讓全部執行個體同時退出 → 只檢查接流量所需的關鍵條件,並給探針短 timeout 與抖動。
追問及應對
追問一:為什麼不讓 10 萬個執行個體直接使用客戶端探索?
客戶端探索可以少一跳,也能做細粒度路由,但會把 watch、快取、revision 補洞、負載平衡、異常剔除與升級複製到每種語言和每個程序。題設有多語言團隊,節點代理把 10 萬個客戶端連線收斂到約 5,000 個資料平面執行個體。若只有一種成熟執行環境且延遲預算極嚴,統一 SDK 可能更適合,但必須保留同樣的協定一致性測試與強制升級機制。
追問二:控制平面中斷時為什麼敢用舊端點?
最後快照讓仍健康的既有執行個體繼續服務,代價是無法得知新增、摘除與容錯移轉變化。代理必須揭露快照年齡,用短 timeout 與被動錯誤減少對壞位址的請求,並限制重試預算。對於安全撤權或禁止存取的變化,不應依賴探索快取傳播;獨立身分與授權層應 fail-closed。超過業務允許的最大快照年齡後,可以依服務策略降級或拒絕,而非全域統一處理。
追問三:5 秒探測、三次失敗真的能滿足 15 秒嗎?
不一定。故障若發生在探測剛成功之後,三個失敗取樣可能接近 15 秒,單次 timeout、排程抖動與端點傳播會讓總時間更長。要滿足 p99,可以縮短週期、讓 timeout 小於週期,並利用連線被拒等被動訊號加速本機剔除。接著用故障注入測量從最後成功請求到最後一次新路由的分布,不能只用設定乘法宣稱通過。
追問四:滾動部署為什麼需要 DRAINING,而非直接刪除?
直接刪除只能阻止看到新清單的呼叫端,無法處理舊快取、keep-alive、長 RPC 和已排隊工作。DRAINING 先從新請求候選集合移除,同時保留程序一段排空時間。應用也應停止接受新工作,並讓 timeout 邊界小於平台終止寬限期。驗證要同時統計最後一次新請求、在途完成率和被強制終止的請求數。
追問五:如果一個區域失去 quorum,是否應由另一區域接管註冊寫入?
不能自動接管同一區域的執行個體事實。跨區觀察可能把網路分割誤判成執行個體全部死亡,兩個控制平面也可能分別接受衝突狀態。失去 quorum 的區域停止目錄寫入,代理繼續讀取快取;全域層只依預先宣告的服務策略把新呼叫切到允許的區域。原區域復原後以 revision 和啟動世代重新同步,不能讓過期續租覆蓋新狀態。