プロンプトとコンテキスト
この質問は、フロントエンドエンジニアがWebSocketを信頼性の低い接続として扱っているかどうかをテストします。Wi-Fiの切り替え、スリープ、プロキシのリセット、サーバーの再起動、およびサイレントなハーフオープンソケットは日常的に発生します。oncloseから即座にconnect()を呼び出すと、再接続ストームが発生し、オフライン中に逃したイベントを復旧できません。クライアントの状態、バックオフ、ハートビート、リプレイ、認証、およびページライフサイクルについて網羅してください。
面接官がテストするポイント
優れた回答では、メッセージのセマンティクスと復旧の境界を定義し、明示的なステートマシンによって状態遷移を強制します。ジッター付き指数バックオフ、ハーフオープン検知のためのアプリケーションハートビート、逃したイベントに対するシーケンスまたはカーソルによるリプレイ、およびドロップ可能な通知と確認応答が必要なコマンドを区別するキューを使用します。また、バックグラウンドタブ、ネットワークの変更、期限切れのトークン、サーバーのスロットリング、ユーザーに見える状態についても議論します。
明確にすべき質問
- メッセージは破棄可能な通知、リプレイ可能なイベント、または処理が必須のコマンドのどれですか?サーバーはカーソルリプレイをサポートしていますか?
- 認証はどのように更新されますか?再接続中にトークン、権限、またはプロトコルバージョンが変更された場合はどうなりますか?
- ハートビートの送信と確認は誰が行いますか?プロキシはどれくらいの期間パケットをサイレントにドロップする可能性がありますか?
- クライアントは複数タブ、モバイルのバックグラウンド化、ブラウザのスリープ、およびオフライン遷移をサポートする必要がありますか?
- 切断中にユーザーは編集を継続できますか?コンフリクト、順序付け、および重複送信はどのように処理されますか?
30秒の回答フレームワーク
「クライアントをidle、connecting、open、suspect、backoff、closedの状態でモデル化します。接続確立後、期限付きのアプリケーションping/pongによってサイレント障害を検知します。異常終了またはハートビートのタイムアウト時はジッター付き指数バックオフに入り、クライアントが一斉に再接続しないようにします。すべてのイベントは単調増加シーケンスを持ち、再接続後、クライアントはライブイベントを消費する前に最後の連続したカーソルから再開します。コマンドは冪等性キーを保持し確認応答を必要としますが、通知は破棄される場合があります。オフライン、非表示、トークン期限切れの状態では、明確なユーザーフィードバックを伴って一時停止または再認証を行います。」
ステップごとの詳細な回答
ステップ1:接続ステートマシンの定義
コールバックがisConnectedを直接変更するのを防ぎ、状態と正当な遷移を一元管理します。典型的なパスはconnecting -> open -> suspect -> backoff -> connectingです。ユーザーによる切断はclosedに入り、自動再接続を無効にします。すべての遷移について理由、試行回数、および接続IDを記録します。
ステップ2:クローズ、エラー、ハーフオープンソケットの区別
ブラウザのerrorイベントには対処可能な理由が含まれていない場合があり、ハーフオープンの経路ではcloseが届かないこともあります。ハートビートでは送信時刻、pongの期限、最後に受信したメッセージを記録します。タイムアウト時には、2つの接続が重複イベントを配信しないように、新しいソケットをスケジュールする前に古いソケットを明示的に閉じます。
ステップ3:バックオフとジッターの実装
RFC 6455では、執拗な即時再接続がDoSのようなストームになり得ると警告しています。min(cap, base * 2^attempt) + random(0, jitter)を使用し、接続が安定した後にのみ試行回数をリセットします。Retry-Afterなどのサーバーメンテナンスやスロットリングのヒントを尊重します。
ステップ4:イベントカーソルの復旧
イベントには単調増加するストリームシーケンスが付与されます。クライアントは最後の連続したシーケンスを保持し、再開ハンドシェイクでそれを送信します。サーバーは欠落した範囲を返し、その後ライブ配信に切り替えます。カーソルが期限切れの場合はスナップショットバージョンを返し、クライアントはそれをロードしてスナップショットのシーケンス以降から再開します。
ステップ5:送信キューと冪等性の設計
プレゼンスやタイピング通知は破棄できますが、編集や決済インテントには確認応答が必要です。コマンドにはclientMessageIdを持たせ、サーバーはそのキーで重複排除して結果を保存します。制限付きのオフラインキューを維持し、キューがいっぱいになった場合は、無制限のメモリキューを隠蔽するのではなく、それ以上のコマンドの受け入れを停止して状態を説明します。
ステップ6:認証とプロトコル変更の処理
再接続前にトークンが期限切れに近いか確認し、必要に応じて更新します。認可エラーによるクローズコードの場合は、盲目的な再試行を停止してログインフローに入ります。ハンドシェイクにプロトコルバージョンを含め、無限ループに陥るのではなく、互換性のあるバージョンを交渉するかアップグレードパスを表示します。
ステップ7:ページおよびネットワークライフサイクルの統合
visibilitychange、online/offline、およびモバイルのバックグラウンド化によって戦略を変更します。非表示のページではハートビート頻度を減らすかライブ配信を一時停止し、復帰時にヘルスチェックとカーソル同期を実行します。オフライン時は即座に接続試行を停止し、オンライン復帰時は失敗する接続を空転させるのではなくバックオフスケジュールを開始します。
ステップ8:ユーザー体験とサーバー負荷の可観測性
接続時間、再接続試行回数、ハートビートタイムアウト、リプレイ数、カーソルのギャップ、破棄されたキューアイテムを記録します。サーバー側では、テナントおよびクライアントバージョンごとに、同時接続ソケット数、ハンドシェイク失敗、再接続ピーク、重複メッセージを監視します。トークンやメッセージ本文は絶対にログ出力しないでください。トランスポートのエラーコードではなく、「再接続中」や「最新状態に同期完了」を表示します。
最小限のステートマシン疑似コード
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を比較検討しますが、トランスポートの選択と復旧の保証を混同しないでください。
ロールアウト計画と検証
まずステートマシン、ハートビート、ジッター付きバックオフをリリースし、その後カーソル復旧と冪等コマンドを追加します。ネットワークの変更、サーバーの再起動、ブラウザのスリープ、トークンの期限切れ、多数のクライアントの一斉切断の訓練を実施します。RFC 6455では、異常終了後のランダムな初期遅延と増加するバックオフを推奨しており、MDNにはブラウザのWebSocketイベントとreadyStateが文書化されています。
パイロット運用の終了基準
ページがリロードなしで回復すること、確認応答付きコマンドが重複も喪失もしないこと、再接続のピークがサーバーを過負荷にしないこと、非表示ページが不要なソケットを保持しないこと、およびユーザーが接続状態と最終同期時刻を確認できること。いずれかの失敗が発生した場合は、プロトコルまたはライフサイクルポリシーを最優先で修正します。
効果を証明する方法
導入前後で復旧成功率、p95復旧時間、重複イベント率、カーソルギャップ、ハンドシェイクのピーク、およびモバイルの消費エネルギーを比較します。平均値によって特定プラットフォームの失敗が隠れてしまわないよう、ネットワーク、ブラウザ、ページの可視性ごとにセグメント化します。
よくある間違いとフォローアップ
oncloseでの即時再接続
サーバーが再起動した際、すべてのクライアントが一斉に接続を試みます。バックオフ、ジッター、サーバーのスロットリングヒントを使用し、安定した後にのみ試行回数をリセットしてください。
closeのみに依存する
ハーフオープンの経路ではcloseが発行されないことがあります。ハートビートとタイムアウトを使用し、古くなったソケットは自身で明示的にクローズしてください。
再接続の成功がデータの完全性を意味すると仮定する
接続の復旧はオフライン中に逃したイベントをリプレイしません。最終確認カーソル、スナップショットバージョン、および連続シーケンスチェックを使用してください。
コマンドの重複をどのように防ぎますか?
すべてのコマンドにクライアント側の冪等性キーを付与し、サーバー側の結果を永続化し、確認応答または結果クエリの後にのみキューアイテムを削除します。
再接続中にトークンが期限切れになった場合はどうなりますか?
トークンを更新して再度ハンドシェイクを行います。認証エラーによるクローズは再試行を停止してログインフローに入ります。これは一時的なネットワークエラーではありません。
複数タブ間での接続をどのように制御しますか?
BroadcastChannelまたはSharedWorkerを使用して、1つのタブがソケットを所有し、他のタブがそれを購読するようにします。所有タブが消えたときに所有権を移譲し、カーソルを再検証します。