You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

开启keepAlive的ApolloServer如何感知客户端订阅连接不可达并触发通知

解决Apollo Server订阅中客户端异常断开的检测问题

我来帮你搞定这个问题——你遇到的情况其实是WebSocket异常断开时的常见痛点:当客户端突然断网、进程崩溃这类异常断开时,TCP连接不会立刻触发服务器端的断开事件,导致onDisconnect钩子没法执行,自然没法同步数据库里的设备连接状态。下面是具体的解决思路和实现代码:

原理说明

你已经配置了keepAlive: 10000,这个参数会让服务器每隔10秒给客户端发一次WebSocket Ping帧。如果客户端正常在线,会回复Pong帧;要是客户端异常断开,服务器就收不到Pong响应,咱们就可以利用这个机制来检测客户端是否可达。

具体实现步骤

我们要在客户端连接时,给每个WebSocket绑定Ping/Pong的监听逻辑,跟踪客户端的存活状态:

  • 在onConnect里设置存活超时定时器:客户端连接成功后,设置一个超时时间(比keepAlive稍长),如果在这个时间内没收到Pong,就判定客户端已断开,执行数据库更新。
  • 监听pong事件重置定时器:每次收到客户端的Pong回复,就重置定时器,证明客户端还在线。
  • onDisconnect中清理定时器:正常断开时把定时器清掉,避免内存泄漏。

修改后的代码示例

const server = new ApolloServer({ 
  typeDefs: typeDefs, 
  schemaDirectives: { role: RoleDirective }, 
  resolvers, 
  context: async ({req, connection}) => { 
    if (connection) { 
      console.log('CONNECT with connection') 
      return connection.context 
    } else if (req) { 
      console.log('CONNECT with request') 
      let token = req.headers.authorization || ''; 
      return await createContextFromRequest(req, token) 
    } else { 
      console.log('CONNECT with no request OR connection\n') 
    } 
  }, 
  subscriptions: { 
    keepAlive: 10000, // 每10秒发送一次Ping
    onConnect: async (connectionParams, websocket, context) => { 
      console.log('Websocket CONNECTED') 
      let token = connectionParams.authorization || '' 
      const userContext = await createContextFromRequest(context.request, token)

      // 超时时间设为12秒,比keepAlive多2秒,给网络延迟留缓冲
      const PING_TIMEOUT = 12000;
      let pingTimeoutId;

      // 重置超时定时器的函数
      const resetPingTimeout = () => {
        if (pingTimeoutId) clearTimeout(pingTimeoutId);
        pingTimeoutId = setTimeout(() => {
          // 超时未收到Pong,判定客户端不可达
          console.log(`Device ${userContext.deviceId || 'unknown'} is unreachable`);
          // 这里执行你的数据库更新逻辑,标记设备为离线
          // updateDeviceStatus(userContext.deviceId, 'offline');
          
          // 主动终止连接,释放服务器资源
          websocket.terminate();
        }, PING_TIMEOUT);
      };

      // 首次启动定时器
      resetPingTimeout();

      // 监听Pong事件,收到后重置定时器
      websocket.on('pong', resetPingTimeout);

      // 将定时器ID存到websocket对象上,方便断开时清理
      websocket._pingTimeoutId = pingTimeoutId;

      return userContext;
    }, 
    onDisconnect: (websocket, context) => { 
      console.log('Websocket DISCONNECTED\n')
      // 清理定时器,防止内存泄漏
      if (websocket._pingTimeoutId) {
        clearTimeout(websocket._pingTimeoutId);
      }
      // 保留正常断开时的数据库更新逻辑
      // updateDeviceStatus(context.deviceId, 'offline');
    } 
  } 
})

关键细节提醒

  • 超时时间选择:别和keepAlive间隔完全一致,留2-3秒缓冲,避免因网络波动误判客户端离线。
  • 主动终止连接:调用websocket.terminate()能确保服务器彻底释放该连接的资源,避免无效连接占用内存。
  • 内存泄漏防护:一定要在onDisconnect中清除定时器,否则已断开连接对应的定时器会持续运行,消耗服务器资源。
  • 设备标识关联:确保userContext中包含设备ID等唯一标识,这样才能准确找到要更新的数据库记录。

这样修改后,不管是客户端正常断开还是异常断网,服务器都能及时检测到并同步更新数据库里的设备连接状态啦。

内容的提问来源于stack exchange,提问作者eric-vinci

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 06:37:30