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

使用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));

问题原因分析

  1. 默认返回格式不匹配:node-redis v4+版本中,get命令默认返回字符串而非原始Buffer。protobuf解码需要Buffer类型,直接传入字符串就会抛出illegal buffer错误。test3能成功是因为订阅操作会触发客户端切换到二进制模式,异步执行的get请求恰好赶上这个模式切换,返回了Buffer——这是偶然的时序结果,不是正确实现。
  2. 订阅模式的命令限制: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 14:53:14