題干與適用場景
這道題考察後端工程師能否把「批次」拆成明確的執行和結果語意。假設客戶要批次更新訂單標籤或使用者設定,一次請求中的項目可能互不相關,也可能共享配額、版本或依賴;網路逾時還可能發生在服務端已完成一部分之後。你需要設計同步和非同步邊界,讓用戶端能安全地繼續處理。
適用對象包括後端工程師、API 設計者和平台工程師。重點是原子性選擇、單項狀態、請求識別、冪等重試、權限與資源限制、回應相容性和恢復路徑,不要求綁定 REST、gRPC 或某個佇列。回答應說明預設行為、顯式選擇部分成功的方式,以及全失敗、部分失敗和未知結果的區別。
面試官考察點
強回答會先詢問項目之間是否有依賴,再決定原子批次、可選部分成功或非同步操作。它不會只回傳一個 HTTP 200 和「失敗數量」,而是能把每個輸入映射到穩定結果、錯誤類別和可重試性。Google AIP-234 指出同步批次方法若改成部分成功會破壞既有用戶端,部分失敗需要按輸入索引回傳詳細狀態;Stripe 的冪等鍵文件則強調重試必須綁定相同參數並保存首次結果。回答還應覆蓋限額、稽核和監控。
回答前需要釐清的問題
- 項目之間是否有順序或交易依賴?業務需要全有或全無,還是允許獨立成功?
- 單項處理是否有外部副作用,能否透過冪等鍵安全重播?
- 客戶需要同步結果,還是可以接受回傳 operation 並輪詢或訂閱進度?
- 批次上限、請求體大小、逾時、租戶配額和公平性如何定義?
- 哪些錯誤可重試、哪些需要修改輸入,未知結果如何查詢?
30 秒回答框架
「我會先區分原子批次和可部分成功的批次,預設保守,只有客戶顯式聲明且業務允許時才開啟部分成功。每個輸入都有穩定索引或客戶請求 ID,服務端為整個批次和每個項目記錄冪等狀態、結果和錯誤類別。同步處理只適合有界規模;超過預算就建立 operation,由背景工作分片執行,客戶查詢進度。回應明確區分成功、永久失敗、暫時失敗和未知狀態,重試只重用相同的單項冪等鍵。最後用限流、稽核、指標和故障注入驗證不會重複副作用或吞掉失敗。」
分步深入解答
第一步:先選擇原子性模型
如果項目共享一個不可分割的業務約束,例如轉帳兩邊必須同時成功,選擇全批原子交易或明確拒絕批次介面。若項目互相獨立,才考慮部分成功。不要用「盡力而為」掩蓋模型;用戶端必須知道是全成功、全失敗、部分完成,還是服務端無法確認。
第二步:定義請求和單項身分
請求應包含批次 ID、項目陣列和客戶生成的單項請求 ID。單項 ID 在同一業務範圍內穩定,重複提交同一 ID 時必須比較關鍵參數;參數不同要回傳衝突,而不是靜默覆蓋。批次 ID 用於追蹤,不能取代單項冪等鍵,因為部分重試可能只包含原批次中的失敗項目。
第三步:明確同步和非同步邊界
小批次可以同步回傳每個項目的最終結果,但仍要設定總處理時間和資源預算。大批次或可能呼叫外部服務的操作應回傳 operation ID,由背景工作分片執行並保存進度。客戶透過查詢介面得到已完成、處理中、可重試和永久失敗的數量;斷線後可以繼續查詢,不必重新發起副作用。
第四步:設計可解析的回應
回應必須讓客戶端按輸入找到結果,不能依賴陣列位置在服務端重新排序後仍然正確。可以使用穩定索引和客戶 ID,錯誤包含機器可讀 code、是否可重試、重試後可能改變什麼,以及安全的使用者提示。示意結構如下:
{
"batch_id": "b_123",
"status": "PARTIAL",
"results": [
{"index": 0, "request_id": "r_0", "status": "SUCCEEDED"},
{"index": 1, "request_id": "r_1", "status": "FAILED", "error": {"code": "VERSION_CONFLICT", "retryable": false}}
],
"next_page_token": null
}Google AIP-234 對非同步批次更新建議用 failed_requests 映射輸入索引到詳細狀態;這種設計避免客戶端再維護 request ID 到原始請求的映射,也不需要把敏感請求體回顯到回應。若要給既有同步 API 增加部分成功語意,應發布新版本或透過顯式欄位協商,避免舊用戶端把成功狀態碼誤解為全部完成。
第五步:處理冪等、重試和未知結果
服務端在真正開始副作用前可以拒絕無效參數,不保存冪等結果;一旦執行開始,就保存結果或可查詢的處理中狀態。網路逾時後的客戶端不能憑猜測重做整個批次,應重用相同批次和單項鍵,查詢未知項目,再只重試明確可重試的項目。暫時錯誤使用退避和抖動,永久錯誤要求修改輸入,重複鍵參數不一致則回傳衝突。
第六步:隔離資源與執行順序
把批次工作拆成有界分片,限制每個租戶、批次和依賴服務的並發。項目有依賴時按拓撲或顯式階段執行;獨立項目可以並行,但要設定共享重試預算,避免失敗時放大流量。權限和配額應在單項執行前檢查,不能因為批次入口繞過普通單項 API 的授權。
第七步:定義錯誤、取消和恢復
把錯誤分成輸入驗證、權限、版本衝突、配額、暫時依賴失敗和未知結果。取消只阻止尚未開始的項目,已完成的副作用不能假裝回滾;若業務需要補償,應設計獨立的補償操作。背景任務、結果和原始請求要持久化,服務重啟後可以從單項狀態繼續,而不是重新執行全部項目。
第八步:驗證一致性與運維可見性
測試全成功、全失敗、混合結果、重複請求、參數衝突、逾時後查詢、依賴抖動、取消競態和服務重啟。監控批次成功率、單項失敗類別、未知狀態數量、重試放大、佇列年齡、處理延遲、配額拒絕和客戶重複提交。稽核記錄批次、單項、操作者、授權決定和最終結果,確保客服或營運能解釋「哪些完成了」。
設計取捨與邊界
部分成功不是預設更先進的選擇。它適合項目獨立、客戶能逐項修復的業務;涉及餘額、庫存或跨資源不變量時,原子性或顯式工作流更安全。HTTP 207 Multi-Status 能表達多個資源的狀態,但 RFC 4918 的語意來自 WebDAV;普通 JSON API 不應只因為有 207 就聲稱客戶端理解了部分結果。選擇狀態碼時要考慮既有客戶端相容性,真正的逐項語意放在穩定回應體中。
什麼時候選擇全批原子?
當任一項目失敗都會讓整體狀態不合法,或補償成本不可接受時,選擇全批原子。可以透過交易、預檢查或工作流保證,但要說明跨分片和外部副作用無法天然共享資料庫交易,必要時需要預留、提交和補償階段。
什麼時候選擇非同步 operation?
當處理時間不可預測、批次很大、需要外部呼叫或客戶端不應長連線等待時,回傳 operation。operation 狀態應可重入查詢,結果分頁,進度指標不能把「已接收」冒充「已完成」。
失敗演練與演進計畫
先選一個有限租戶範圍的批次介面試點,記錄每個項目的狀態和重試行為,再逐步放大批次上限。演練網路在第 30 個項目完成後斷開、一個依賴持續回傳 503、同一鍵攜帶不同參數,以及 operation worker 重啟。確認客戶端能查詢未知結果、服務端不會重複副作用,才允許擴大配額或啟用部分成功。
如何從同步版本演進到部分成功?
保留舊版本的原子語意,新增版本回傳 operation 和逐項失敗資訊;或者增加必須顯式開啟的 returnpartialsuccess 欄位,並為未開啟時保留舊行為。遷移文件寫清狀態碼、回應欄位、重試規則和棄用日期,避免客戶端默默改變解釋。
如何評估客戶端是否正確使用?
觀察客戶端是否保存單項 ID、是否只重試可重試失敗、是否查詢未知結果,以及重複副作用和無效重試的比例。對關鍵 SDK 提供狀態解析和查詢封裝,但服務端仍需容忍未知欄位和重複請求。
常見誤區與追問
回傳 HTTP 200 並附一個失敗數量
這會讓舊用戶端把部分完成當成全部成功,也無法告訴客戶端哪些項目可以重試。回應應明確整體狀態、逐項身分、錯誤類別和後續動作。
失敗就重試整個批次
整個批次重試可能重複已成功的副作用。先用批次和單項冪等鍵查詢狀態,只重試明確的暫時失敗;參數改變時使用新的業務請求 ID。
部分成功時如何排序結果?
按輸入索引或穩定 request ID 關聯結果,不依賴處理完成順序。結果分頁時仍保留唯一身分,客戶端才能合併多頁狀態。
單項成功後批次被取消,能否回滾?
取消只影響尚未開始的項目。已發生的外部副作用需要補償 API 或人工處理,不能用「批次取消」偽造回滾成功。
如何防止批次入口繞過權限?
在批次級檢查租戶和操作權限,在單項執行前再次檢查資源歸屬、版本和欄位權限。批次只是調度形式,不能擴大單項 API 的授權範圍。
如何解釋最終的部分失敗?
用批次 ID、單項 ID、狀態、錯誤 code、是否可重試和時間戳給出可稽核結果;對客服可顯示安全的業務說明,對工程排障保留關聯 trace 和依賴錯誤。