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

Socket.io+负载均衡场景下高效存储Socket连接的方案咨询

负载均衡场景下WebSocket连接的高效存储方案

核心痛点

你当前用内存Map<number, Socket.socket>存储连接,在PM2多进程+负载均衡的架构下,每个进程的内存空间相互独立,跨进程无法访问其他进程里的连接实例,导致无法给连接到其他进程的用户推送消息,这是核心问题。

1. Redis发布/订阅(Pub/Sub)+ 本地连接映射

这是业内最常用的轻量方案,不需要集中存储所有连接,而是用Redis做跨进程消息中转:

  • 每个PM2进程启动后,订阅Redis的专属频道(比如socket_process_${process.pid})
  • 维护一个Redis Hash表(比如user_process_map),用户连接时记录用户ID -> 进程ID的映射,断开时删除对应记录
  • 当需要给某用户推送消息时,先从Redis Hash中查找到用户所在的进程ID,然后向该进程的专属频道发送消息
  • 目标进程收到消息后,从本地内存Map中取出对应的Socket连接推送内容
  • 代码示例:
    // 用户连接时更新Redis映射
    await redisClient.hset('user_process_map', userId, process.pid);
    // 推送消息逻辑
    const targetPid = await redisClient.hget('user_process_map', userId);
    if (targetPid) {
      await redisClient.publish(`socket_process_${targetPid}`, JSON.stringify({
        userId,
        content: '推送内容'
      }));
    }
    // 进程订阅专属频道
    redisClient.subscribe(`socket_process_${process.pid}`, (message) => {
      const { userId, content } = JSON.parse(message);
      const socket = localSocketMap.get(userId);
      socket?.send(content);
    });
    
  • 优势:Redis Pub/Sub延迟极低,实时性好;本地Map保留连接实例,避免跨进程直接操作Socket的风险;资源占用少,性能高
  • 注意:要处理进程异常退出的情况,比如给Redis映射设置过期时间,或者用PM2的退出钩子清理对应记录

2. 粘性会话(Sticky Sessions)

如果你的负载均衡器支持粘性会话(比如Nginx的ip_hash、Cloudflare的会话亲和性),可以让同一个用户的WebSocket连接始终路由到同一个PM2进程:

  • 配置负载均衡器,根据客户端IP或用户ID做哈希路由,固定请求到某一进程
  • 每个进程只需维护本地的Map连接表,不需要跨进程通信
  • Nginx配置示例:
    upstream ws_backend {
      ip_hash;
      server 127.0.0.1:3000;
      server 127.0.0.1:3001;
      # 其他PM2进程端口
    }
    
  • 优势:实现成本极低,不需要额外中间件;没有跨进程通信的性能损耗
  • 缺点:负载可能不均(比如某IP段用户集中);进程重启时该进程的所有连接会断开,影响用户体验

3. Redis哈希表存储连接元数据(多设备场景)

如果你的用户存在多设备登录的情况,可以把连接的元数据(用户ID、进程ID、连接标识)存在Redis哈希表中:

  • 每个连接生成唯一ID,将连接ID -> { userId, pid, connInfo }存入Redis Hash
  • 推送消息时,根据用户ID查询所有关联的连接ID,再分别向对应进程的频道发送消息
  • 这种方式可以支持一个用户多个连接的推送需求

4. 基于Socket.IO的Redis适配器(框架化方案)

如果你的项目用Socket.IO而非原生WebSocket,可以直接用官方的Redis适配器:

  • 适配器会自动处理跨进程的连接同步和消息路由,无需自己维护映射表和Pub/Sub逻辑
  • 代码示例:
    const { Server } = require('socket.io');
    const { createAdapter } = require('@socket.io/redis-adapter');
    const { createClient } = require('redis');
    
    const io = new Server(3000);
    const pubClient = createClient({ url: 'redis://localhost:6379' });
    const subClient = pubClient.duplicate();
    await Promise.all([pubClient.connect(), subClient.connect()]);
    io.adapter(createAdapter(pubClient, subClient));
    
  • 优势:开箱即用,Socket.IO封装了所有集群逻辑,减少重复开发
  • 缺点:依赖Socket.IO框架,原生WebSocket无法直接使用

选型建议

  • 原生WebSocket项目:优先选Redis Pub/Sub + 本地Map,兼顾灵活性和性能
  • Socket.IO项目:直接用Redis适配器,高效省心
  • 小体量项目、对负载均衡均匀性要求不高:可以先用粘性会话快速解决问题

内容的提问来源于stack exchange,提问作者Rojith.P

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 22:16:17