← 中文资源

简体中文版本

WebSocket 指数退避重连客户端

让 WebSocket 客户端在断线后自动重连:指数退避加随机抖动防惊群,心跳保活,重试预算耗尽即停止,主动关闭不触发重连。

阅读英文原文

示例说明

实时行情、推送这类长连接断开是常态而非异常。裸的立即重连会在服务端故障时形成重连风暴,指数退避让间隔逐次翻倍,随机抖动错开多客户端的重连相位;心跳 ping 检测半开连接;重试上限保证彻底失联时客户端最终放弃并交由上层处理,而不是无限空转。

技术要点

  • 指数退避加抖动防重连惊群
  • pong 心跳检测静默断链
  • closed 标志区分主动关闭与意外断开

代码解读

connect 每次创建全新 socket,open 事件把尝试计数清零并主动 ping;close 事件里先判断是否主动关闭或超出预算,再计算 2 的次幂乘基数加抖动的延迟并封顶 30 秒。close 方法置位 closed 标志并 terminate,防止优雅停机时触发多余重连。

使用建议

重连成功后要重新订阅之前的主题并请求增量补齐,弥补断线窗口的数据空洞。心跳间隔要短于网关的空闲超时。把连接状态暴露给 UI 层,用户感知比静默重试更友好。

代码示例

import WebSocket from 'ws';

function createResilientSocket(url, { maxRetries = 8, baseMs = 500 } = {}) {
  let attempts = 0;
  let closed = false;
  let socket;

  const connect = () => {
    socket = new WebSocket(url);

    socket.on('open', () => {
      attempts = 0;
      socket.ping();
      socket.on('pong', () => console.log('heartbeat ok'));
    });

    socket.on('message', (data) => console.log('recv:', data.toString()));

    socket.on('close', (code) => {
      if (closed || attempts >= maxRetries) return;
      const jitter = Math.random() * baseMs;
      const delay = Math.min(2 ** attempts * baseMs + jitter, 30_000);
      attempts += 1;
      console.log('closed', code, 'retrying in', Math.round(delay), 'ms');
      setTimeout(connect, delay);
    });
  };

  connect();
  return { close: () => { closed = true; socket.terminate(); } };
}

const client = createResilientSocket('wss://stream.example.com/feed');
setTimeout(() => client.close(), 60_000);