具代表性的面試主題

後端面試:如何在 gRPC 呼叫鏈中傳播 deadline 與取消?

後端困難
Offer.cc 編輯團隊發佈 更新

題幹

一個 API 請求依序呼叫三個 gRPC 服務,使用者中途離開頁面。請設計 deadline、取消傳播、重試與服務端清理策略,說明如何避免子呼叫超過父請求預算、如何處理串流 RPC,以及寫入在取消後如何確認結果。

題幹與適用場景

一個 API 請求依序呼叫使用者、目錄與推薦三個 gRPC 服務。使用者可能離開頁面,推薦服務也可能短暫超時。系統要控制端到端 延遲、釋放服務端資源、避免重試放大,並在寫入取消後維持可查詢狀態。

gRPC 文件把 deadline 定義為客戶端願意等待的上限,超時會產生 DEADLINE_EXCEEDED;取消會立即終止不再需要的 RPC。高分 回答要分開預算傳播、重試邊界、串流清理和未知結果,不能只為每層設定固定五秒。

面試官考察點

看候選人是否使用絕對 deadline 或剩餘預算、把取消傳給子 RPC 與資料庫、讓重試共用總預算,以及不把「沒有回應」當成寫入未執行。 還要說明觀測欄位與壓測驗證。

回答前需要澄清的問題

  • 頂層 SLO、客戶端 deadline 和部分結果規則是什麼?
  • 三個服務是唯讀還是包含扣款等副作用?
  • 哪些狀態可重試,是否有冪等鍵?
  • 串流訊息能否重複,是否支援游標或重連?
  • 資料庫和外部 API 是否接受取消?
  • 取消後要查詢狀態、補償還是對帳?

30 秒回答框架

「入口建立絕對 deadline,子呼叫只繼承剩餘預算,不重新得到完整超時。父取消立即傳給 RPC、資料庫和可取消工作;重試共用總 deadline、次數上限與退避。唯讀可在安全錯誤上重試,寫入用冪等鍵或狀態查詢。串流結束釋放游標與連線,記錄每層剩餘預算、 取消原因和最終狀態。」

分步驟深入解答

第一步:定義端到端時間預算

入口把客戶端 deadline 視為不可突破的上限。服務收到後計算 remaining = deadline - now,為子呼叫設定不晚於它的時間, 並保留排隊、序列化和回應餘量。

text
parent_deadline = 2.0s from request start
child_deadline = min(parent_deadline, now + remaining_budget)

不要每層再等兩秒,否則三層最壞超過六秒。應用仍用單調時間監控排隊與工作階段。

第二步:傳播取消並真正停止工作

離開頁面、顯式取消和 deadline 到期都要觸發取消。服務端 handler 監聽 context 的 cancellation token,傳給資料庫、HTTP、佇列和串流迭代器。 只返回錯誤但繼續背景工作會造成洩漏。

取消時釋放連線、暫存檔與鎖,停止新訊息;已提交交易不能靠取消撤銷,後續用查詢或補償處理。

第三步:在剩餘預算內重試

只重試明確瞬時錯誤,如 UNAVAILABLE,限制次數、指數退避和隨機抖動。每次沿用同一 deadline,不能重設計時器;到期立即停止。

text
for attempt in 1..maxAttempts:
  if remaining(deadline) <= backoff: stop
  result = call(child, deadline, cancellation)
  if result is success or permanent_error: return result
  sleep(jittered_backoff, cancellation)
return DEADLINE_EXCEEDED

統一重試責任,監控各層 attempt 與 retry amplification。

第四步:區分讀取與寫入

唯讀可依契約重試;寫入要有操作 ID、唯一約束和狀態查詢。取消發生在外部寫入後,客戶端得到的是 UNKNOWN,不能換新 ID 盲重試。

text
PENDING -> CONFIRMED
       \-> FAILED
       \-> UNKNOWN (reconcile before retry)

狀態查詢返回權威結果;deadline 只控制等待,不能提供跨系統原子回滾。

第五步:處理串流 RPC 生命週期

串流設定總 deadline,必要時設定閒置超時;每次讀取檢查取消。導航離開後取消,服務端停止產生訊息並關閉游標或訂閱。重連需使用游標或序號, 不能從頭重播造成副作用。

記錄最後訊息時間、取消原因、重連次數與積壓。優雅停止時讓活躍串流在剩餘 deadline 內完成或返回可識別狀態。

第六步:驗證傳播和資源邊界

測試父取消、各子服務超時、資料庫慢查詢、UNAVAILABLE、永久錯誤、串流中斷、寫入後回應遺失、重試和優雅停止。斷言子呼叫不超過父 deadline, 取消後無背景洩漏,重試不超預算。

記錄 RPC 方法、trace ID、剩餘時間、attempt、狀態碼、取消來源和操作 ID,不記敏感載荷。壓測觀察連線、佇列、重試放大與尾延遲。

高品質示範回答

「入口建立絕對 deadline,聚合層把剩餘預算傳給三個子服務。handler 將取消傳到資料庫、HTTP 和串流迭代器,返回前釋放資源。唯讀只在瞬時錯誤上有限重試, 共用總 deadline。」

「寫入用操作 ID,取消後標記 UNKNOWN 並查狀態;串流用總 deadline、游標和重連。注入父取消、子超時、回應遺失和優雅停止,驗證沒有洩漏與重試放大。」

常見錯誤

  • 每層重設完整超時 → 突破入口 SLO → 傳播絕對 deadline。
  • handler 返回就算取消 → 下游仍運行 → 傳遞取消並清理資源。
  • 所有錯誤重試 → 負載放大 → 依語義、預算與上限重試。
  • 寫入超時換新 ID → 可能重複副作用 → 查權威狀態或補償。
  • 串流重連從頭重播 → 重複訊息 → 使用游標、序號與冪等消費。
  • 只記最終錯誤 → 無法定位預算耗盡層 → 記錄剩餘時間與 attempt。

追問及應對

追問一:子服務可以延長父 deadline 嗎?

不可以。若需要更長操作,改成非同步任務並返回操作 ID,不要偷偷延長同步請求。

追問二:取消能撤銷已提交交易嗎?

不能保證。取消只能阻止尚未執行工作;已提交交易要查詢、補償或對帳,介面必須暴露狀態。

追問三:如何避免雙重重試?

指定單一重試責任方,其他層只傳播錯誤;統一 attempt 和總預算,監控每層放大倍數。

追問四:串流沒有訊息時是否取消?

依產品語義區分長連線與閒置,使用總 deadline、閒置超時或心跳。閒置超時應產生可重連狀態,不應無限佔用資源。

追問五:deadline 已過但服務端仍計算怎麼辦?

服務端監聽取消並讓下游可取消;不可取消的計算移到受控背景佇列,記錄操作 ID 與資源上限,避免佔用請求執行緒。

公開來源

同類題目