題目與情境
你負責 POST /exports 大型匯出 API。請求會驗證參數並啟動工作,但匯出可能耗時數分鐘。面試官要求你在 200、201 與 202 之間選擇,並說明客戶端如何知道最終結果。
假設伺服器能持久化匯出記錄並投遞工作。客戶端可能逾時後重送請求。回答必須定義完整合約,不能只報一個狀態碼。
面試官在考察什麼
- 能否區分「資源已建立」與「請求已接受、稍後處理」。
- 能否建模持久狀態資源、終態與錯誤詳情。
- 重試是否會建立兩個匯出工作,或遺失原始回應。
- 資料庫更新與佇列投遞在沒有分散式交易時是否仍可靠。
作答前的釐清問題
- POST 是否立即建立持久匯出資源?若是,
201 Created可以描述該資源;否則202可以確認工作已接受。 - 同一個邏輯請求是否允許重試?若允許,應要求冪等鍵或呼叫端操作 ID。
- 客戶端需要輪詢、Webhook,還是兩者都要?這會改變狀態表示與通知合約。
- 匯出檔案的保留與授權規則是什麼?工作完成不代表任何人都能下載結果。
30 秒回答框架
「只有在處理被延後且最終結果尚未準備好時,我才回傳 202 Accepted。我先建立持久工作記錄,再回傳狀態 URI 與操作識別。客戶端使用退避輪詢,或接收經過驗證的回呼。冪等鍵把重試映射到同一個工作與回應。工作經過排隊、執行、成功或失敗等明確狀態;worker 與 outbox 都可重試,狀態 API 仍是事實來源。」
分步深入解答
1. 依資源生命週期選擇狀態碼
200 OK 表示請求已完成並回傳表示。201 Created 表示資源已建立且可被識別。202 Accepted 表示請求已接受,處理可能尚未開始或尚未完成;它不承諾最後成功。
若匯出記錄同步建立,且它就是呼叫端要管理的資源,可以回傳 201 與該資源。若 API 只是確認背景工作已接受,則用 202 加監控 URI 更清楚。選擇應由可觀察生命週期決定,不是因為系統內部用了佇列。
2. 讓回應可以執行
回傳操作 ID、狀態 URL,以及包含 state、時間戳與安全重試提示的表示。最小回應可以是:
HTTP/1.1 202 Accepted
Location: /exports/exp_123
Retry-After: 5
Content-Type: application/json
{"id":"exp_123","state":"queued","status_url":"/exports/exp_123"}每次讀取狀態資源都要授權。queued 和 running 是非終態;succeeded 回傳短期下載引用;failed 回傳穩定錯誤碼與修正提示,不能洩露堆疊。客戶端也要能處理超過保留期後資源消失。
3. 讓重試收斂
要求建立工作的操作帶上 Idempotency-Key。保存相關請求的雜湊、工作 ID 與回應狀態。相同鍵搭配相同請求時回傳原結果;相同鍵搭配不同請求時回傳客戶端錯誤。不能只依賴時間視窗,因為遲到的重試可能在視窗後抵達。
不同鍵仍可以建立兩個匯出工作。冪等性防止一個邏輯操作重複建立,不代表 worker 能做到恰好執行一次。
4. 關閉資料庫到佇列的間隙
在一個資料庫交易中同時寫入匯出記錄與 outbox 事件。relay 發布待處理 outbox,並在 broker 確認後標記已送出。崩潰可能造成事件重複發布,因此消費者以匯出 ID 作為冪等鍵。這保證已提交的工作最終可被發現,但不宣稱資料庫與 broker 具備原子提交。
worker 以條件狀態轉換更新工作,例如 queued -> running -> succeeded|failed。過期重試不能把 succeeded 改回 running。指標應包括佇列等待時間、執行時間、終態失敗率與 outbox 延遲。
5. 定義輪詢、回呼與取消
狀態 API 提供 ETag 或版本號,讓輪詢使用條件請求。客戶端依伺服器提示做指數退避,並在終態停止輪詢。Webhook 是最佳化,不是唯一結果通道:投遞可能失敗,因此客戶端必須能讀取狀態資源進行對帳。
取消使用獨立命令,例如 POST /exports/exp_123/cancel。只允許可取消狀態,而且取消本身也要冪等。工作已 succeeded 後,遲到的取消不能回滾結果。
高品質示範回答
我會先確認這個呼叫是否同步建立持久匯出資源。若是,可以回傳 201 與資源記錄。對於結果延後、回應只確認接受的操作,我回傳 202,附帶操作 ID 和需要授權的狀態 URL。我要求冪等鍵,保存請求指紋和工作 ID,讓重試得到同一表示。
交易同時寫入匯出記錄和 outbox 事件,relay 與冪等消費者按至少一次投遞處理。狀態機單調地從排隊到執行,再到成功或失敗。客戶端用條件請求和退避輪詢,Webhook 只是加速器。我會監控佇列時間、outbox 延遲和終態錯誤,並明確保留、授權、下載連結過期和取消規則。202 只確認接受,不確認成功。
常見失誤
- 錯誤表現:把
202當成工作一定成功 → 失敗原因:語義允許處理失敗或根本未開始 → 修正方法:暴露終態失敗與保留策略。 - 錯誤表現:只回傳
202,不給狀態 URL → 失敗原因:客戶端無法發現狀態,只能猜測 → 修正方法:回傳受授權保護的狀態資源與操作 ID。 - 錯誤表現:資料庫提交後再直接發布佇列訊息 → 失敗原因:崩潰可能留下無人處理的工作 → 修正方法:使用交易 outbox 與可重播 relay。
- 錯誤表現:認為佇列能保證恰好執行一次 → 失敗原因:投遞重試與崩潰會造成重複 → 修正方法:消費者冪等,狀態轉換帶條件。
- 錯誤表現:相同冪等鍵無條件回傳成功 → 失敗原因:它可能掩蓋請求已被修改 → 修正方法:比較請求指紋,衝突時拒絕。
追問與回答
這裡應該改用 201 嗎?
當同步呼叫建立持久匯出資源並能透過 Location 識別它時回傳 201。當有意義的結果被延後、回應只確認接受時回傳 202。有些 API 可以用 201 建立工作資源,同時把資源狀態設為 pending;必須說明狀態描述的是哪個資源。
如果客戶端永遠不輪詢怎麼辦?
在約定保留期內持久保存狀態,傳送可選的驗證 Webhook,並允許客戶端之後用操作 ID GET。回呼失敗不能刪除唯一的狀態路徑。客戶端幾天後回來時,仍要執行下載連結過期和授權檢查。
worker 可以兩次更新同一個工作嗎?
可以,投遞通常是至少一次。使用唯一匯出 ID、帶條件的狀態轉換和冪等輸出寫入。重複的 succeeded 事件應該無害;終態回到 running 的轉換應被拒絕並記錄。