Websocket+Redis多频道定向订阅发布实现方案咨询
最佳实现方案
一、Redis客户端放置位置结论
- 发布客户端
pub必须放在LOCATION 1全局初始化位置,不要每个WebSocket连接单独新建。Redis连接是TCP长连接,全局复用性能最高,每个连接新建会产生大量不必要的连接开销,很容易打满Redis服务端的连接上限。 - 订阅客户端
sub既不能按你当前的写法直接全局使用,也绝对不能放在LOCATION 2为每个连接单独新建,这是你现有代码的核心逻辑缺陷。
注意:如果每个WebSocket连接都新建一个Redis sub客户端,万级连接规模下Redis会直接因为连接数耗尽崩溃;如果全局只用一个sub订阅所有频道但不做连接分组,你收到消息后还是得遍历全量连接匹配规则,性能达不到预期。
二、高性能精准推送实现逻辑
不用全量遍历所有WebSocket连接、基于Redis Pub/Sub能力的生产级实现方案如下:
- 全局初始化阶段(LOCATION 1位置)完成三个核心初始化动作:
- 初始化1个全局复用的
pub客户端,所有向频道推送消息的逻辑统一复用这一个连接 - 初始化1个频道-连接映射表,结构为
Map<string, Set<WebSocket>>,key是业务频道名(mice-feed/cat-feed/dog-feed),value是当前服务节点上订阅了该频道的所有WebSocket连接实例集合 - 初始化1个全局复用的
sub客户端,一次性订阅所有三个业务频道,监听消息事件,收到对应频道消息时只给该频道映射集合里的连接推送,完全不用遍历全量连接
- 初始化1个全局复用的
// LOCATION 1 全局初始化代码示例 const ws = require('ws'); const redis = require('redis'); // 全局复用发布端连接 const pub = redis.createClient(); // 全局复用订阅端连接 const sub = redis.createClient(); // 核心:频道与对应连接的映射表 const channelConnections = new Map([ ['mice-feed', new Set()], ['cat-feed', new Set()], ['dog-feed', new Set()] ]); // 一次性订阅所有业务频道 sub.subscribe('mice-feed', 'cat-feed', 'dog-feed'); // 监听订阅消息,仅向对应频道的连接推送 sub.on('message', (channel, message) => { const targetConnections = channelConnections.get(channel); if (!targetConnections) return; // 仅遍历当前频道的在线连接,集合规模远小于全量连接,性能极高 targetConnections.forEach(conn => { if (conn.readyState === ws.OPEN) { conn.send(message); } }); }); const wsServer = new ws.Server({ noServer: true, path: "/", });
- WebSocket连接回调(LOCATION 2位置)只做连接维度的逻辑:
- 完成用户身份认证,判定用户所属类别,匹配到对应的目标频道
- 将当前WebSocket实例加入映射表中对应频道的连接集合
- 监听当前连接的
close事件,连接断开时将实例从对应频道的集合中移除,避免内存泄漏
// LOCATION 2 连接回调代码示例 wsServer.on("connection", function connection(websocketConnection, connectionRequest) { // 替换为你的实际身份认证、用户类别判定逻辑 const userCategory = 'mice'; const targetChannel = `${userCategory}-feed`; // 将当前连接加入对应频道的连接集合 const channelSet = channelConnections.get(targetChannel); if (channelSet) { channelSet.add(websocketConnection); } // 连接关闭时清理无效连接引用 websocketConnection.on('close', () => { if (channelSet) { channelSet.delete(websocketConnection); } }); // 保留你原有的其他连接处理逻辑 });
三、方案优势
- 整个服务节点仅维护2个Redis长连接(1个发、1个收),不会给Redis造成额外连接压力
- 消息推送时仅遍历对应频道的在线连接,不用扫描全量连接,连接规模越大性能优势越明显
- 天然支持多节点部署:不管WebSocket服务部署多少个实例,每个实例的sub客户端都会收到频道消息,各自推送本节点上的对应频道用户即可,不需要额外做节点间通信
- 完全避免消息越权:不同类别的用户连接存在独立的Set集合中,根本不会接触到其他频道的消息,不会出现非对应用户收到消息的问题
避坑提醒:不要为每个WebSocket连接单独创建Redis订阅客户端,1万在线用户就会给Redis打1万个连接,远超Redis默认的连接数阈值,会直接导致服务不可用。
内容的提问来源于stack exchange,提问作者Deolu A
相关产品推荐
相关产品推荐

