使用node-redis与protobuf.js从Redis获取Buffer失败问题排查
问题:node-redis 结合 protobuf.js 读取 Buffer 数据异常
我正在使用node-redis和protobuf.js,但从Redis数据库中正确获取Buffer数据时遇到问题。只有下方示例中的test3场景能正常获取并解码Buffer,其他测试要么抛出Error: illegal buffer,要么在订阅后执行get命令报错。怀疑初始化过程有遗漏,求指出问题所在。
Node.js 测试代码
var redis = require('redis') let protobuf = require('protobufjs') const initRedis = async () => { const root = protobuf.loadSync('mymodel.proto') const Message = root.lookupType('MyModel') const redisClient = redis.createClient({ url, }) await redisClient.connect() const getAndDecodeValue = async (key) => { const value = await redisClient.get(key) console.log(Message.decode(value)) } if (currentTest === 'test1') { // Throws Error: illegal buffer getAndDecodeValue(key) } else if (currentTest === 'test2') { // Throws Error: illegal buffer await getAndDecodeValue(key) } else if (currentTest === 'test3') { // Works and decodes the value correctly. getAndDecodeValue(key) redisClient.subscribe('channel-1', async (key, channel) => { console.log(key, channel) }) } else if (currentTest === 'test4') { // Throws Error: illegal buffer await getAndDecodeValue(key) redisClient.subscribe('channel-1', async (key, channel) => { console.log(key, channel) }) } else if (currentTest === 'test5') { // Throws Error: [ErrorReply: ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context] redisClient.subscribe('channel-1', async (key, channel) => { console.log(key, channel) }) getAndDecodeValue(key) } }
数据写入Redis的C++代码参考
const std::string channel_name = "channel-1"; _redis->set(sw::redis::StringView(key), sw::redis::StringView(data)); _redis->publish(sw::redis::StringView(channel_name), sw::redis::StringView(key));
问题原因分析
- 默认返回格式不匹配:node-redis v4+版本中,
get命令默认返回字符串而非原始Buffer。protobuf解码需要Buffer类型,直接传入字符串就会抛出illegal buffer错误。test3能成功是因为订阅操作会触发客户端切换到二进制模式,异步执行的get请求恰好赶上这个模式切换,返回了Buffer——这是偶然的时序结果,不是正确实现。 - 订阅模式的命令限制:Redis客户端一旦执行
subscribe进入订阅状态,就只能执行订阅相关命令(SUBSCRIBE/UNSUBSCRIBE等),无法再执行get这类普通读写命令,这就是test5报错的原因。
解决方案
方案1:显式指定get返回Buffer
修改getAndDecodeValue函数,调用get时添加returnBuffers: true选项,强制返回原始Buffer,无需依赖订阅模式:
const getAndDecodeValue = async (key) => { const value = await redisClient.get(key, { returnBuffers: true }); console.log(Message.decode(value)); };
方案2:拆分订阅与读写客户端
如果需要同时处理订阅和数据读写,创建两个独立的Redis客户端:一个用于订阅频道,另一个用于执行get/set等普通操作,避免订阅模式的命令限制:
// 用于普通读写的客户端 const redisClient = redis.createClient({ url }); await redisClient.connect(); // 用于订阅的客户端 const redisSubscriber = redis.createClient({ url }); await redisSubscriber.connect(); redisSubscriber.subscribe('channel-1', (message, channel) => { console.log(message, channel); });
方案3:避免依赖异步时序
test3的成功是偶然的异步顺序导致,不要依赖这种不稳定的行为,优先采用上面两种规范方案。
内容的提问来源于stack exchange,提问作者4-bit
相关产品推荐
相关产品推荐

