題幹與適用場景
設計一個供多支研發團隊使用的指標監控與告警系統。它監控 5 萬個服務實例,每個實例暴露 200 條活躍時間 序列,每 15 秒採集一次。系統要支援計數器、儀表、直方圖,依標籤篩選和彙總,展示看板,並依規則發送 告警。新故障從進入採集路徑到告警進入通知路由的 p99 不超過 60 秒;最近 6 小時的常用看板查詢 p95 不超過 2 秒。原始樣本保留 7 天,1 分鐘彙總保留 90 天,1 小時彙總保留 13 個月。
題目假設單一地域、三個可用區部署,租戶身分由可信認證資訊給出。通知管道可以沿用既有郵件、即時訊息和 電話服務,本題負責可靠地產生、去重、分組和路由通知。日誌與鏈路追蹤不在主設計範圍。面試中需要重點 解釋高基數標籤、重複和遲到樣本、缺失資料、規則狀態、查詢隔離,以及監控平臺本身故障時如何被發現。
2026 年的中英文公開材料都直接把 metrics monitoring and alerting system 作為系統設計面試題,討論時間 序列寫入、查詢、告警和高可用。Prometheus 官方資料提供時間序列身分、拉取採集、WAL、告警 pending/firing、分組與抑制等一手語意;Google SRE 則給出低雜訊告警和黑箱監控原則。因此,本題既有當前 代表性,也有可核驗的技術依據。
面試官考察點
第一,候選人能否把「樣本吞吐」和「活躍時間序列數」分開。寫入量由實例數、每實例序列數和採集週期決定; 記憶體、索引和查詢扇出往往先被基數擊穿。把 userid 或 requestid 放進標籤,可能在流量不變時製造 數量級更高的新序列。
第二,寫入確認、看板查詢和告警發現是否形成閉環。採集器拿到資料不代表資料已經耐久;告警規則也不能依賴 一個被重查詢拖垮的共用資源池。強答案會給寫入耐久邊界,為告警保留獨立預算,並明確遲到、重複、空值和 部分查詢結果的語意。
第三,儲存結構是否符合存取模式。近期資料會被高頻追加和查詢,歷史資料適合不可變區塊、壓縮、索引和物件 儲存。降採樣不能簡單地對平均值再次求平均;至少要保留 sum、count、min、max,直方圖也要依相容 桶邊界合併。
最後,告警系統要在事故期間仍可運作。事故會同時增加錯誤指標、查詢和通知數量。候選人需要處理規則分片、 狀態復原、通知去重、告警風暴、租戶雜訊、監控系統自我監控,以及獨立於本平臺的端到端存活探針。
回答前需要釐清的問題
- 採集對象是否穩定? 大部分是長生命週期服務,由服務發現提供目標;短任務和受限網路透過推送閘道接入。
- 規模是全域還是單一地域? 題設數字按單一地域峰值計算,跨地域擴展時各地域獨立採集,再提供受控的全域查詢。
- 確認邊界是什麼? 寫入閘道只有在樣本進入跨可用區複寫的耐久日誌後才回傳成功。
- 允許重複和亂序嗎? 允許網路重試帶來的重複,也允許有限窗口內的遲到;同序列同時間戳採用確定性衝突規則。
- 缺失樣本等於零嗎? 不等於。缺失可能是目標停機、採集失敗或網路分割,必須與真實數值零區分。
- 查詢是否都要 2 秒完成? 只承諾有界的最近 6 小時常用查詢;超大基數、跨月原始查詢受配額和非同步分析限制。
- 告警如何判定復原? 規則定義評估週期、持續時間、缺失資料策略和復原條件,不能由通知服務猜測。
- 租戶能否自訂任意標籤? 可以在配額內使用,但受標籤數量、長度、活躍序列和新序列速率限制。
- 是否要求 exactly-once 通知? 不要求。通知採用至少一次投遞,接收端以告警指紋和狀態轉換做冪等。
- 原始與彙總資料的關係? 彙總工作可從仍在保留期內的原始區塊重算,彙總區塊記錄解析度和覆蓋水位。
30 秒回答框架
「先算規模:活躍序列是 1,000 萬條,寫入約 66.7 萬樣本/秒、576 億樣本/天。採集器透過服務發現分片拉取, 短任務走推送閘道;標準化後的樣本依租戶和序列指紋進入跨可用區耐久日誌,再寫入近期資料層和不可變時間 區塊。標籤索引支援查詢,基數預算限制 user_id 一類危險標籤。規則引擎用獨立資源按固定時間點評估, 維護 inactive、pending、firing 狀態,告警管理器負責去重、分組、靜默、抑制和路由。最後用十倍新序列 衝擊、重複遲到樣本、可用區故障和外部 canary 驗證寫入、查詢及告警鏈路。」
分步驟深入解答
先確定六條不變條件:租戶身分不能由樣本標籤宣告;已經確認的樣本進入了耐久日誌;同一重試不會製造不同 結果;缺失樣本不被偷偷改寫成零;看板壓力不能餓死告警;所有拒絕、丟棄、延遲和降級都有可觀測計數。
第一步:重算規模,把基數當成一等公民。
活躍序列數是:
50,000 instances × 200 series/instance = 10,000,000 active series
10,000,000 ÷ 15 seconds ≈ 666,667 samples/second
666,667 × 86,400 ≈ 57.6 billion samples/dayPrometheus 官方儲存文件給出的本機樣本粗略起點是平均 1–2 位元組/樣本。按這個量級,壓縮樣本區塊約為 57.6–115.2 GB/天;三副本約為 172.8–345.6 GB/天。這個估算不含標籤索引、WAL、head、物件中繼資料、 副本以外的安全餘量,也不能取代對實際指標分布的壓測。容量評審必須把這些開銷分別列出。
更危險的是新序列。如果請求級 user_id 進入標籤集合,序列身分會隨標籤值變化。系統要同時限制每租戶和 每指標的活躍序列數、新序列建立速率、標籤數量、標籤長度與允許鍵,並提供先告警、再隔離或拒絕的策略。
第二步:選擇拉取為主、推送為輔的採集模型。
服務發現把目標清單交給採集器,控制面用一致性雜湊或 rendezvous hashing 分配目標。採集器每 15 秒透過 HTTP 拉取,為樣本附加可信的租戶、叢集、任務和實例資訊,同時產生 up、採集耗時、樣本數和抓取錯誤。 拉取模型自然知道「目標存在但抓取失敗」,也便於控制節奏。
短生命週期批次任務、無入站網路或只支援 OTLP 推送的環境透過區域閘道接入。閘道認證身分、限制批次大小、 標準化欄位後進入同一寫入契約。推送成功只能表示進入本平臺的耐久邊界,不能把客戶端傳送成功當成最終 儲存成功。採集器斷線時使用有界本機 spool;空間耗盡要按策略拒絕並計數,不能無限吃滿磁碟。
第三步:定義時間序列身分和寫入契約。
一條序列由 tenantid + metricname + canonical_labels 唯一識別;標籤依鍵排序並規範編碼後計算指紋。指紋 碰撞時仍須比較完整身分,不能只相信雜湊。樣本至少包含序列身分、來源時間、接收時間、值或直方圖、資料型別 和採集來源。
WriteBatch {
tenantId, sourceId, requestId,
samples: [{ metric, labels, sourceTimestamp, value }]
}閘道依序執行認證、標籤規範化、型別驗證、基數預算、時間範圍和批次限制,然後依 tenantid + seriesfingerprint 分區寫入跨可用區複寫日誌。日誌提交是 ack 邊界。同一 requestId 可安全 重試;儲存層再以 (seriesid, sourcetimestamp) 去重。同時間戳不同值要採用固定規則並暴露衝突計數,不能 依賴到達順序悄悄覆蓋。
第四步:把近期資料、歷史區塊和索引分層。
最近數小時保存在可追加的 head 中,支援遲到窗口內的有限修改。背景程序把資料凍結成依時間和分片組織的 不可變區塊,區塊內使用時間戳差分和值壓縮,隨後上傳物件儲存。區塊中繼資料記錄最小/最大時間、租戶、 解析度、校驗和與覆蓋水位;壓縮器合併小區塊並刪除已被完整覆蓋的舊區塊。
倒排索引從標籤鍵值映射到序列 ID,再由序列 ID 定位資料區塊。高頻標籤和序列中繼資料可快取,但租戶邊界 必須進入快取鍵。降採樣依 1 分鐘和 1 小時產生新區塊,保留 sum/count/min/max;計數器還要處理重設, 直方圖只在桶 Schema 相容時合併。原始區塊保留 7 天,1 分鐘區塊 90 天,1 小時區塊 13 個月。
第五步:讓查詢成本可預測。
查詢協調器解析時間範圍、標籤選擇器、彙總和步長,先鑑權,再用索引找序列並向相關區塊平行讀取。只有查詢 步長和函式允許時才選彙總區塊;例如平均值用 sum/count 重算,分位數不能從既有分位數再次彙總。近期資料 要合併 head 與已落盤的區塊,並按統一水位去重。
每次查詢都有最大序列數、掃描點數、並行、記憶體和截止時間。一般看板使用固定步長、預彙總和短時結果快取, 目標是最近 6 小時 p95 低於 2 秒;跨月高基數探索可轉為非同步任務。分片失敗時 API 必須回傳 partial=true 和缺失範圍,告警規則預設不接受部分結果。
QueryRange {
tenantId, expression, start, end, step, maxSeries
}
QueryResult { data, resolution, watermark, partial, warnings }第六步:把規則評估和通知路由拆開。
規則排程器依租戶和規則組分片,每 15 或 30 秒在明確的評估時間點執行。告警實例由規則名和結果標籤產生 穩定指紋,狀態在 inactive -> pending -> firing 之間轉換。for 持續時間過濾瞬時尖峰; keepfiringfor 或明確回滯避免短暫缺數導致反覆復原。規則還要選擇缺失資料是正常、告警或保留未知狀態。
兩個高可用評估器可以向告警管理器傳送相同指紋,由後者去重。告警管理器依團隊、服務和嚴重級別路由,把 同一事故的實例分組;叢集不可達時抑制其下游實例告警;維護窗口使用有期限靜默。通知狀態保存在複寫儲存中, 投遞採用至少一次語意,以 alertfingerprint + statetransition + receiver 做冪等。告警計算、狀態和通知 佇列使用獨立於臨時看板查詢的資源池。
第七步:處理故障、租戶隔離和自我監控。
採集器失效後,目標租約轉移到健康實例;短暫重疊採集依靠去重吸收。日誌或儲存背壓時,閘道降低批次並明確 拒絕,不能在記憶體中無限排隊。物件儲存故障時繼續寫複寫日誌和有界 head,並暫停區塊回收;超過安全水位 後按租戶配額保護告警關鍵指標。查詢故障不應阻止規則從已確認水位讀取。
租戶身分來自 mTLS 或服務端權杖,不能相信樣本中的 tenant 標籤。寫入、查詢和規則分別設定限額;RBAC 控制看板與告警設定;稽核規則、靜默和路由變更。系統自身監控寫入延遲、日誌積壓、head 水位、區塊年齡、 查詢掃描量、規則評估延遲、pending/firing 數量和通知失敗。
自我監控仍有共同故障域,因此另設外部黑箱 canary:從獨立環境持續寫入已知指標,等待規則轉為 firing 並 收到通知,再自動復原。外部 dead-man 檢查若未按期收到心跳,直接走獨立通知路徑。這能發現「監控平臺完全 沉默」。
第八步:用壓力與故障注入驗證承諾。
穩態壓測至少達到 66.7 萬樣本/秒,並驗證兩倍突發;隨後製造十倍新序列速率,檢查危險租戶是否被隔離、 其他租戶和規則評估是否仍滿足 SLO。資料正確性測試涵蓋重複批次、同時間戳衝突、遲到窗口、計數器重設、 直方圖合併、彙總重算和區塊壓縮前後結果一致。
故障測試依序殺死採集器、一個可用區的日誌副本、head 節點、查詢分片、規則評估器和通知供應商。驗證 ack 樣本不丟、租約轉移可去重、規則狀態可復原、通知可重試且不會形成無界風暴。最後測量 canary 從寫入、規則 持續時間、分組等待到通知到達的端到端延遲,確認 p99 不超過 60 秒或明確是哪一段耗盡預算。
高品質示範回答
「我先把 5 萬實例乘以每實例 200 條序列,得到 1,000 萬活躍序列;再除以 15 秒,寫入約 66.7 萬樣本/秒, 一天 576 億樣本。容量上必須同時預算樣本區塊和標籤索引,並限制每租戶活躍序列與新序列速率,防止 user_id 把基數打爆。
主路徑使用服務發現和分片採集器拉取;短任務走推送閘道。樣本經過認證、標籤規範化和配額檢查後,依租戶與 序列指紋寫入跨可用區複寫日誌,提交後才 ack。近期資料進入 head,隨後凍結為不可變壓縮區塊和標籤倒排 索引,上傳物件儲存;背景程序產生 1 分鐘和 1 小時彙總,平均值由 sum/count 重算。
查詢協調器依時間、標籤和解析度裁剪區塊,並用序列數、掃描點、記憶體和截止時間控制成本。規則引擎使用 獨立資源在固定時間點評估,維護 inactive、pending、firing 狀態;告警管理器依指紋去重,再分組、抑制、 靜默和路由。缺失資料有明確策略,通知採用至少一次投遞。
我會用 66.7 萬樣本/秒穩態、兩倍突發和十倍新序列衝擊壓測,再注入重複遲到樣本、可用區故障、查詢分片 失敗與通知商故障。獨立環境的 canary 持續完成寫入到通知的全鏈路,既驗證 60 秒 p99,也能發現監控平臺 整體失聲。」
常見錯誤
- 只計算樣本/秒 → 標籤索引和活躍序列可能先耗盡記憶體 → 同時計算 1,000 萬基數和新序列速率。
- 允許任意
user_id標籤 → 每個取值都會產生新序列 → 設定標籤策略、基數預算和隔離佇列。 - 收到請求就 ack → 程序或可用區故障會丟掉已確認資料 → 以複寫日誌提交作為確認邊界。
- 把缺失樣本當成零 → 採集失敗會偽裝成業務歸零 → 保留 stale/unknown,並由規則選擇缺失策略。
- 對平均值再次求平均 → 不同樣本量的時間桶會產生偏差 → 儲存
sum/count後重算。 - 聲稱所有查詢都能 2 秒回傳 → 無界標籤與時間範圍無法控制成本 → 限定 SLO,設定查詢預算和非同步路徑。
- 規則查詢允許部分結果 → 少一個分片可能把事故誤判為復原 → 告警預設要求完整水位或進入未知狀態。
- 每個實例單獨發通知 → 大故障會製造告警風暴 → 按事故分組,並使用抑制、靜默和速率限制。
- 承諾 exactly-once 通知 → 逾時重試無法區分已送達與未送達 → 採用至少一次並用穩定鍵冪等。
- 只用本系統監控自己 → 整個平臺故障時沒有訊號 → 增加獨立黑箱 canary 和 dead-man 路徑。
追問及應對
追問一:為什麼不讓所有客戶端直接推送?
穩定服務適合拉取:平臺掌握目標清單和頻率,能區分業務數值與抓取失敗,也能統一附加可信資源標籤。短任務、 無入站網路和現成 OTLP 客戶端適合推送。兩條入口最終使用同一驗證與耐久寫入契約,避免產生兩套查詢語意。
追問二:如何控制高基數而不誤傷正常業務?
同時看存量和增量:每租戶、每指標限制活躍序列數,並限制每分鐘新序列率。上線前提供標籤基數預覽;接近 閾值先告警,超限後可丟棄指定危險標籤、隔離整個指標或拒絕新序列,策略由租戶等級決定。平臺保留丟棄計數 和代表樣本,方便定位來源,但不能把原始敏感標籤寫入一般日誌。
追問三:遲到樣本如何影響告警?
規則在固定評估時間點讀取一個已確認水位,並可設定小的評估延遲吸收正常遲到。水位之後到達的舊樣本可以 更新歷史查詢,但預設不回滾已經傳送的人類通知;若業務需要更正,產生帶版本的修正事件。遲到窗口、最大 時鐘偏差和衝突規則必須公開且可測試。
追問四:為什麼不能直接儲存分位數?
多個實例或時間桶的 p95 不能透過再次取平均或再求 p95 得到全域 p95。應儲存可合併的直方圖桶或草圖,查詢 時合併分布後計算分位數。桶邊界或草圖參數不相容時必須分開查詢或明確遷移,不能靜默拼接。
追問五:如何避免高可用規則引擎重複通知?
兩個評估器可以都計算和傳送同一告警實例,實例指紋由規則和結果標籤穩定產生。告警管理器在複寫狀態上依 指紋、狀態和接收端去重、分組並路由。這樣不需要把規則評估強行做成單點;網路分割時仍接受極少量重複通知, 接收端繼續以同一冪等鍵防重。
追問六:物件儲存無法使用時怎麼辦?
繼續把新樣本寫入複寫日誌和有界 head,暫停區塊上傳、壓縮刪除與超出安全水位的歷史接納。優先保障告警 使用的關鍵指標,並透過租戶配額阻止雜訊租戶占滿空間。如果預計無法在日誌保留窗口內復原,系統必須開始 明確拒絕並通知值班人員,不能假裝全部寫入成功。
追問七:全域多地域查詢怎麼做?
每個地域獨立採集和告警,避免跨地域網路故障讓本地告警失聲。全域查詢協調器只讀取各地域已經發布的不可變 區塊或受控查詢端點,回傳每個地域的水位和 partial 狀態。真正需要全域告警的少數指標先在地域內彙總, 再把低基數結果複寫到獨立規則域。
追問八:如何判斷 60 秒告警目標被哪一段拖慢?
在 canary 樣本上記錄來源時間、接收時間、日誌提交水位、規則評估時間、pending 起止、分組等待和通知接收 時間。端到端直方圖依階段拆分,且由外部探針驗證最終到達。若規則設定了 5 分鐘 for,它屬於產品條件, 不應混進「平臺發現延遲」;SLO 要分別報告平臺處理時間與規則要求的持續時間。