具代表性的面試主題

前端面試:如何設計可靠的 WebSocket 斷線重連客戶端?

前端困難
OfferCC 編輯團隊發佈 更新

題幹

即時協作頁面在網路切換後經常卡住,重新整理才恢復。你會如何設計 WebSocket 客戶端的偵測、重連、狀態恢復和使用者提示?

題幹與適用場景

題目考察前端工程師能否把 WebSocket 當成不可靠連線來管理。瀏覽器可能經歷 Wi-Fi 切換、休眠、代理中斷、伺服器重啟或靜默半開連線;只在 onclose 中立即 connect() 會製造重連風暴,也無法找回斷線期間事件。回答應涵蓋客戶端狀態、退避、心跳、補償同步、鑑權和頁面生命週期。

面試官考察點

強回答會先定義訊息語意和恢復邊界,再用明確狀態機約束連線轉換。它會使用隨機抖動的指數退避、應用層心跳偵測半開連線、序號或游標補拉缺失事件,並讓發送佇列區分可丟棄與必須確認的訊息。還應說明背景分頁、網路變化、token 過期、伺服器限流和使用者可見狀態。

回答前需要釐清的問題

  • 訊息是可遺失通知、可重播事件,還是必須一次處理的命令?伺服器是否提供游標補拉?
  • 連線鑑權如何刷新?重連時 token 過期、權限變化或協定版本變化怎麼辦?
  • 心跳由誰發送和確認?代理可能靜默丟包多久,允許多長偵測時間?
  • 需要支援多分頁、行動端背景、瀏覽器休眠和網路離線嗎?
  • 重連期間使用者可以繼續編輯嗎?衝突、順序和重複提交如何處理?

30 秒回答框架

「我會把客戶端建模成 idle、connecting、open、suspect、backoff、closed 六態。開啟後使用帶逾時的應用層 ping/pong,異常關閉或心跳逾時進入指數退避並加入隨機抖動,避免所有客戶端同時重連。每個事件帶遞增序號,重連成功先用 last-seen 游標補拉,再開放即時消費;命令採用冪等鍵和確認,通知可以丟棄。頁面隱藏、離線和 token 過期時暫停或重新鑑權,並向使用者顯示連線狀態。」

分步深入解答

第一步:定義連線狀態機

集中定義狀態和合法轉換,例如 connecting -> open -> suspect -> backoff -> connecting;使用者主動關閉進入 closed,不再自動重連。每次轉換記錄原因、嘗試次數和連線 id,方便診斷重複連線。

第二步:區分 close、error 與半開

瀏覽器的 error 不一定提供原因,close 也可能長時間不觸發。應用層心跳記錄發送時間、pong deadline 和最近收到的訊息;逾時後主動關閉舊 socket,再進入退避。

第三步:實作退避與抖動

RFC 6455 警告持續立即重連會形成拒絕服務式重連風暴。可用 min(cap, base * 2^attempt) + random(0, jitter) 計算延遲;成功穩定一段時間後重置次數。伺服器回傳限流或維護提示時,應尊重 Retry-After 或更長冷卻時間。

第四步:恢復事件游標

即時事件帶單調遞增的 stream sequence。客戶端保存最後連續處理的序號,重連握手攜帶該游標;伺服器先返回缺失區間,再切換即時流。若游標過期,伺服器返回快照版本,客戶端載入快照後繼續。

第五步:設計發送佇列與冪等

presence、typing 等通知可丟棄;編輯、支付意圖等命令必須確認。命令帶 clientMessageId,伺服器按冪等鍵去重並返回結果;斷線期間只快取有界佇列,超過上限就阻止繼續提交並提示使用者。

第六步:處理鑑權與協定變化

重連前檢查 token 是否即將過期,必要時先刷新;收到未授權關閉碼時停止盲目重試並進入重新登入。握手帶協定版本,不相容時降級或提示升級,不能在錯誤版本上循環重連。

第七步:結合頁面與網路生命週期

visibilitychange、online/offline 和行動端背景會改變策略。背景頁面可降低心跳頻率或暫停即時流,回到前景後執行健康檢查與游標同步。離線時停止撥號,網路恢復後再按退避啟動。

第八步:監控使用者體驗和伺服器壓力

客戶端記錄連線建立耗時、重連次數、心跳逾時、補拉事件、游標缺口和佇列丟棄數;伺服器觀察並發連線、握手失敗、重連峰值與重複訊息。錯誤日誌不能含 token 或訊息正文。

最小狀態機偽代碼

text
on_open(socket):
    state = OPEN
    send({type: "resume", lastSeen: cursor})

on_heartbeat_timeout():
    socket.close()
    state = BACKOFF
    delay = min(MAX, BASE * 2 ** attempts) + random(0, JITTER)
    schedule(connect, delay)

設計取捨與邊界

決策選擇原因
重連延遲截斷指數退避 + 抖動降低同步重連峰值
斷線恢復游標補拉,必要時快照不必依賴重新整理
訊息可靠性通知可丟棄,命令冪等確認按業務價值控制成本
背景頁面降頻或暫停,回前景同步節省電量並減少無效連線

WebSocket 只提供有序訊息,不自動提供 exactly-once、離線佇列或狀態同步。可靠性必須由協定層定義;若只需伺服器推送且可接受遺失,也可比較 SSE。

落地計畫與證據

先實作狀態機、心跳和帶抖動退避,再增加游標恢復與冪等命令。用網路切換、伺服器重啟、瀏覽器休眠、token 過期和大量客戶端同時斷線做演練。RFC 6455 建議異常關閉後採用隨機初始延遲與逐步增加退避;MDN WebSocket 文件可作為瀏覽器事件與 readyState 的依據。

試點退出條件

斷線後無需重新整理即可恢復;已確認命令沒有重複或遺失;重連峰值不壓垮伺服器;背景頁面不持續佔用連線;使用者能看到狀態與最後同步時間。

如何證明收益不是巧合

比較改造前後恢復成功率、p95 恢復時間、重複事件率、游標缺口、握手峰值和行動端耗電,並按網路、瀏覽器與頁面可見性分層。

常見誤區與追問

在 onclose 立即重連

伺服器重啟時所有客戶端同時撥號,形成 thundering herd。必須使用退避、抖動和限流提示。

只依賴 close 事件

半開連線可能沒有 close。用心跳和 deadline 判定失聯,再關閉舊 socket。

重連成功就認為資料完整

連線恢復不代表斷線期間事件已到達。用 last-seen 游標補拉與連續序號驗證完整性。

如何避免重複命令?

每個命令使用客戶端冪等鍵,伺服器持久化結果;客戶端收到確認或查到結果後才移除佇列項。

token 在重連時過期怎麼辦?

先刷新並重新握手;未授權關閉碼應停止重試並轉入登入流程,不能當作網路抖動。

多分頁如何控制連線數?

可用 BroadcastChannel 或 SharedWorker 選出一個持有連線的分頁,其餘分頁訂閱事件;owner 失聯時轉移所有權並重新驗證游標。

公開來源

同類題目