Redis subClient调用psubscribe报错:TypeError: 该方法不存在
问题解决:Redis subClient.psubscribe 不是函数的错误
问题场景
在开发环境初始化Redis的pub/sub客户端用于Socket.io集群,代码如下:
if (process.env.STATUS === 'development') { const pubClient = redis.createClient({ port: process.env.REDIS_PORT, host: process.env.REDIS_HOST, }); const subClient = pubClient.duplicate(); io.adapter(redisAdapter({ pubClient, subClient })); chatSocket(io, pubClient); }
Socket连接处理代码:
import {setupChatSocket} from '../socket/serverEvents.js' const chatSocket = function (io, pubClient) { let clients = JSON.parse(pubClient.get('clients')); if (!clients) clients = []; io.sockets.on('connection', function (socket) { setupChatSocket(socket, clients, pubClient, io); }); }; export default chatSocket
在setupChatSocket中执行this.subClient.psubscribe(this.channel + '*', onError)时,抛出错误:TypeError: this.subClient.psubscribe is not a function
原因分析
- 传递的客户端错误:你传给
setupChatSocket的是pubClient而非subClient,如果this.subClient是从传入的客户端赋值来的,那它本质是发布客户端,没有psubscribe方法(只有订阅客户端才有订阅相关方法)。 - 客户端未正确初始化:如果
this.subClient是自行创建的,可能没有完成连接,或者创建方式错误,导致不是有效的Redis订阅客户端实例。 - 适配器客户端复用冲突:Socket.io的Redis适配器已经在使用你创建的
subClient做集群消息同步,直接复用它可能导致状态异常,无法执行额外的订阅操作。
解决步骤
步骤1:传递正确的subClient到处理函数
修改初始化代码,把subClient也传入chatSocket:
if (process.env.STATUS === 'development') { const pubClient = redis.createClient({ port: process.env.REDIS_PORT, host: process.env.REDIS_HOST, }); const subClient = pubClient.duplicate(); io.adapter(redisAdapter({ pubClient, subClient })); // 同时传入pubClient和subClient chatSocket(io, pubClient, subClient); }
更新chatSocket函数,接收并传递subClient给setupChatSocket:
import {setupChatSocket} from '../socket/serverEvents.js' const chatSocket = function (io, pubClient, subClient) { let clients = JSON.parse(pubClient.get('clients')); if (!clients) clients = []; io.sockets.on('connection', function (socket) { setupChatSocket(socket, clients, pubClient, subClient, io); }); }; export default chatSocket
步骤2:确保subClient正确初始化并使用
在setupChatSocket中,若需要每个连接单独订阅,应创建独立的订阅客户端(避免复用适配器的客户端):
// 在setupChatSocket内部 export function setupChatSocket(socket, clients, pubClient, subClient, io) { // 为当前连接创建独立的订阅客户端 const connectionSubClient = pubClient.duplicate(); connectionSubClient.on('error', (err) => console.error('Redis订阅错误:', err)); // 等待连接完成后再执行订阅 connectionSubClient.connect().then(() => { const channel = '你的频道前缀'; connectionSubClient.psubscribe(`${channel}*`, (err) => { if (err) console.error('订阅失败:', err); }); // 处理订阅到的消息 connectionSubClient.on('pmessage', (pattern, channel, message) => { socket.emit('message', message); }); }); // 连接断开时关闭订阅客户端,避免资源泄漏 socket.on('disconnect', () => { connectionSubClient.quit(); }); // 其他socket业务逻辑... }
步骤3:区分适配器客户端和业务订阅客户端
Socket.io的Redis适配器用的pub/sub客户端是做集群内Socket节点通信的,不要用它处理业务频道的订阅,避免干扰适配器正常工作。业务订阅应使用独立创建的Redis客户端实例。
额外注意事项
- Redis客户端进入订阅模式后,只能执行
subscribe/psubscribe/unsubscribe/punsubscribe这类订阅相关命令,不能再执行set/get等普通读写命令,业务读写操作需用单独的pubClient。 - 务必在Socket连接断开时关闭对应订阅客户端,防止资源泄漏。
内容的提问来源于stack exchange,提问作者Hamoudeh Fouad
相关产品推荐
相关产品推荐

