題目與情境
事件生產者向外部 HTTP 端點傳送業務事件。平台要持久化事件、至少一次投遞、讓重複安全,並為每個租戶提供清楚的重試與重播控制。
面試官考察什麼
- 選擇明確的投遞保證,不在 HTTP 上承諾恰好一次。
- 分離接收、調度、投遞嘗試與訂閱方副作用。
- 結合事件 ID、簽章時間戳、重試策略、死信處理與公平性。
作答前的釐清問題
- 事件量、載荷大小、端點數和最大投遞延遲是多少?
- 需要按租戶、端點保證順序,還是不要求順序?
- 消費者能否冪等處理事件,去重狀態要保留多久?
- 載荷有什麼重播、刪除、隱私和稽核要求?
30 秒回答框架
我會先持久化帶穩定 ID 的不可變事件,再調度投遞嘗試並快速回應。工作程序用時間戳簽章原始載荷,採用帶抖動的指數退避,並區分可重試與終止失敗。消費者在副作用前按事件 ID 去重。按租戶調度、熔斷和死信佇列防止離線端點拖垮其他租戶;重播建立新的投遞嘗試,但不改變原事件身分。
分步深入
1. 先保證事件持久化
透過交易性 outbox 或可靠寫入,把業務事件與投遞記錄一起落盤。記錄租戶、端點、事件 ID、嘗試次數、下次嘗試時間與狀態。送出後記錄成功前崩潰是正常情況,會產生再次嘗試,因此消費者必須容忍重複。
2. 安全驗證與簽章
使用時間戳與原始位元組簽章,並把事件 ID 納入簽章訊息。Stripe 文件使用帶時間戳的簽章限制重播攻擊。消費者在解析前驗證簽章,拒絕超出容忍窗口的時間戳,並設計金鑰輪換。
3. 分類回應並重試
網路逾時、連線失敗和部分 5xx 屬於可重試錯誤。驗證錯誤、事件版本不支援和多數 4xx 屬於終止或人工處理錯誤。使用帶抖動的指數退避、最大嘗試窗口和死信狀態;不要對永久失敗端點無限重試。
4. 明確處理重複與重播
消費者用持久唯一約束保存已處理事件 ID,並盡量與業務副作用同交易提交。人工重播重用不可變載荷,建立新的投遞嘗試並記錄稽核;儀表板區分首次投遞、自動重試與人工重播。網路只能提供至少一次,副作用的恰好一次需要消費者交易。
5. 擴展而不讓租戶互相飢餓
按租戶或端點分區佇列,限制每租戶並發與速率,並為健康租戶預留容量。端點連續失敗時觸發熔斷。監控最老待處理事件年齡、成功延遲、嘗試分布、重複率、簽章失敗和死信量。
高品質示範回答
「我會在調度前持久化帶穩定 ID 的事件,並明確承諾至少一次投遞。每次嘗試都用事件 ID 和時間戳簽章原始載荷;逾時和 5xx 採用帶抖動重試,終止性 4xx 進入死信。消費者在副作用交易內按事件 ID 去重。按租戶分佇列、熔斷、年齡告警和可稽核重播,讓離線端點不會拖垮其他租戶。」
常見誤區
- 承諾 HTTP 恰好一次 → 崩潰會產生不確定結果 → 宣告至少一次並要求消費者冪等。
- 簽章解析後的 JSON → 等價格式可能驗證失敗 → 簽章和校驗原始位元組。
- 所有 4xx 無限重試 → 永久錯誤消耗容量 → 分類錯誤並將終止情況送入死信。
- 使用全域單佇列 → 單一租戶可能拖垮全部任務 → 分區、限速並預留租戶容量。
追問與回答
去重狀態要保留多久?
至少覆蓋自動重試和支援的重播窗口,並增加安全餘量。若允許數月後重播,應保留精簡事件帳本,或明確要求消費者選擇重播身分與保留策略。
重播應產生新的事件 ID 嗎?
通常不應。業務事件身分保持穩定,投遞嘗試使用獨立 ID 和稽核記錄。如此消費者能識別重播是同一事件,避免重複業務副作用。
消費者在副作用提交前回傳 200 怎麼辦?
這是消費者契約問題,平台無法從回應推斷真實成功。消費者應在持久接收後確認,使用 inbox 或交易性去重記錄,並提供不確定結果的對帳流程。