題目與範圍
假設有 2 萬個租戶、每小時 100 萬次操作,短時突發可達 10 倍,授權決策 p95 低於 100 毫秒。客戶先購買額度,再執行操作。每次操作成本可能不同,請求可以並發到達,使用事件至少投遞一次。設計必須防止餘額變負和重複扣費,並保留稽核軌跡。這是購買所得價值的帳本,不是限流器,也不是週期訂閱計費系統。
面試官考察什麼
- 區分不可變額度流水與快速餘額讀模型。
- 讓授權、扣取、釋放、退款和發放操作具備冪等性。
- 處理並發請求、延遲事件、重試、過期和對帳。
- 說明一致性邊界、熱點租戶、可觀測性與恢復方案。
可以先問的釐清問題
確認額度是否過期、失敗操作是否先預留再釋放、退款由誰審批、成本是否在執行前確定,以及一個租戶是否會在多個區域消費。再確認決策延遲要求和是否允許臨時超額授權。
30 秒回答
我會使用由發放、預留、扣取、釋放、退款和過期組成的追加寫入不可變帳本。事務內維護餘額投影以支援快速讀取,用冪等鍵讓每次變更可重複執行。授權時原子檢查可用額度並建立帶過期時間的預留;成功後扣取,失敗後釋放;回收器處理遺留預留。透過 outbox、可重播事件和對帳任務比較投影與帳本,並用可稽核的補償流水修復偏差。
分步深入回答
1. 建模額度狀態
按租戶或帳戶保存整數額度單位、帳本序號,以及發放、預留、扣取、釋放、退款、過期總額的投影。每筆變更記錄 operation_id、冪等鍵、原因、操作者和時間。舊流水不修改,糾正只能追加補償流水。
2. 在不重複消費的前提下授權
對租戶熱點鍵使用同一事務或線性一致的條件更新:只有可用額度不少於本次成本時才建立預留。重複冪等鍵返回原決定;同一鍵攜帶不同參數則拒絕。預留保存過期時間,扣取和釋放都要帶狀態條件,不能同時成功。
3. 連接業務執行與事件
業務請求攜帶預留識別碼。確認成功後扣取,失敗或取消後釋放。使用事件可能重複或延遲,消費者按 event_id 去重,對未知預留執行對帳,不能直接減少計數器。outbox 在事務提交後發布帳本變更。
4. 擴展、替代方案與恢復
按租戶分區,讓同一租戶路由到固定寫入區域,並用准入控制或串行命令流隔離極熱租戶。租戶寫入量中等時,關聯式資料庫事務更簡單;極熱租戶更需要事件溯源命令流,以順序和可重播性換取查詢複雜度。只有能接受延遲的讀取才使用副本,並展示資料時間。清掃器使遺留預留過期,重播帳本可重建投影。監控負可用額度、過期後扣取、重播延遲和對帳差異。
高品質示例回答
我會先確認過期、退款權限、多區域消費,以及成本是否在執行前已知。不可變的每租戶帳本是真實來源,餘額表只是可重建投影。authorize 在冪等鍵和過期時間約束下原子預留額度,成功後 capture,失敗後 release 或等待過期。重複或延遲事件先去重再對帳。嚴格禁止重複消費時採用租戶歸屬寫入區域;若必須多區域獨立消費,則分配明確的區域預算並顯式對帳債務,說明最大臨時超額。所有糾正都追加補償流水。
常見失誤
- 只有可變計數器 → 重試和退款無法稽核 → 追加帶操作識別碼的不可變流水。
- 請求到達就扣費 → 失敗操作也消耗額度 → 先預留,成功後才扣取。
- 把重試當作新消費 → 同一操作被扣兩次 → 重用同一冪等鍵。
- 預留從不回收 → 崩潰工作程序永久鎖住額度 → 增加過期時間和條件清掃器。
- 用最終一致的跨區讀取 → 兩個寫入者可能超額消費 → 單寫入者或明確區域預算。
- 修改舊行修復歷史 → 稽核軌跡失去可信度 → 追加補償流水。
追問與回答
用戶端在授權後逾時,怎麼辦?
先查詢預留,或使用同一冪等鍵重試下一狀態。只有確認原預留已扣取、釋放或過期後,才建立新的授權。
月結後才發現需要退款,怎麼辦?
追加一筆引用原扣取的退款流水,並記錄審批資訊。投影增加可用額度,帳本保留兩筆事件及其順序。
兩個區域能同時消費同一帳戶嗎?
嚴格防止重複消費時,優先使用單寫入者或租戶歸屬區域。若必須本地消費,則分配明確區域預算並對帳債務,同時說明臨時超額上限。
如何證明餘額正確?
定期重播帳本,比較推導出的可用額度與投影,並把扣取流水與業務結果比較。每次修復寫入冪等補償流水和稽核記錄。