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

Node.js TypeScript项目中使用node-redis客户端出现「Error: The client is closed」错误的求助

解决Redis客户端报错Error: The client is closed的问题

这个问题我之前处理过,核心原因主要有两个:Redis客户端初始化完成前就被调用,以及legacy模式下的API使用方式不正确。下面给你详细的解决方案:

一、先确保Redis连接完成后再启动服务器

你的index.ts里是并行执行了Redis初始化和服务器启动,这会导致服务器已经启动,但Redis还没建立好连接,控制器就去调用客户端,自然会报"client is closed"。

修改index.ts,等待Redis初始化完成后再启动服务器:

import { init } from 'dataSource/redis';
(async () => {
  try {
    // 先确保Redis连接成功
    await init();
    // 再启动服务器
    app.listen(PORT,() => {
      Logger.info(`Server Started in port : ${PORT}!`);
    });
  } catch (err) {
    Logger.error('Failed to initialize services', err);
    // 初始化失败直接退出进程
    process.exit(1);
  }
})();

二、修正Redis客户端的API使用方式

你开启了legacyMode: true,这意味着你使用的是Redis客户端的旧版回调式API,但你直接同步调用setEx,既没有处理回调也没有promisify,再加上连接未就绪,就会触发错误。这里提供两种解决方案:

方案1:继续使用legacy模式,对方法进行promisify

在你的Redis连接文件dataSource/redis.ts中,对需要使用的Redis方法进行promisify处理:

import { createClient } from 'redis';
import { promisify } from 'util';
import Logger from 'utils/logger';

const { REDIS_URL = 'redis://localhost:6379' } = process.env;
const options = {
  legacyMode: true,
  url: REDIS_URL,
};
const client = createClient(options);

// 对需要的方法进行promisify并绑定客户端
const setExAsync = promisify(client.setEx).bind(client);
const getAsync = promisify(client.get).bind(client);
// 其他需要的方法如del、hget等也可以按此方式处理

// 事件监听保持不变
client.on('connect', () => {
  Logger.info("Connected to Redis");
});
client.on('error', err => {
  Logger.error('redis error: ', err);
  // 注意:legacy模式下不要在error事件里递归调用init,会导致无限重连循环
});
client.on('ready', () => {
  Logger.info("redis is ready");
});
client.on('end', () => {
  Logger.info("redis connection is ended");
});
client.on('reconnecting', () => {
  Logger.info("redis connection is reconnecting");
});

const init = async () => {
  await client.connect();
};

// 导出promisified的方法而非原始client
export { init, setExAsync, getAsync };

然后在控制器中异步调用这些promisified的方法:

import { setExAsync } from 'datasource/redis';

// 必须在异步函数中调用
async function yourControllerMethod() {
  try {
    const expireTime = Number(process.env.REDIS_EXPIRE_TIME);
    if (isNaN(expireTime)) {
      throw new Error('REDIS_EXPIRE_TIME is not a valid number');
    }
    await setExAsync("Key", expireTime, "VALUE");
    Logger.info('Redis key set successfully');
  } catch (err) {
    Logger.error('Failed to set Redis key:', err);
  }
}

方案2:改用Redis客户端的新版Promise API(推荐)

如果不需要兼容旧代码,建议直接移除legacyMode,使用新版的Promise式API,更符合TypeScript的异步编程习惯:

修改dataSource/redis.ts:

import { createClient } from 'redis';
import Logger from 'utils/logger';

const { REDIS_URL = 'redis://localhost:6379' } = process.env;
// 移除legacyMode配置
const options = {
  url: REDIS_URL,
};
const client = createClient(options);

// 事件监听保持不变
client.on('connect', () => {
  Logger.info("Connected to Redis");
});
client.on('error', err => {
  Logger.error('redis error: ', err);
  // 新版客户端默认自带自动重连机制,无需手动调用init
});
client.on('ready', () => {
  Logger.info("redis is ready");
});
client.on('end', () => {
  Logger.info("redis connection is ended");
});
client.on('reconnecting', () => {
  Logger.info("redis connection is reconnecting");
});

const init = async () => {
  try {
    await client.connect();
    Logger.info("Redis client initialized successfully");
  } catch (err) {
    Logger.error('Failed to connect to Redis:', err);
    throw err; // 抛出错误让上层处理
  }
};

// 直接导出新版客户端,所有方法都是Promise-based
export { init, client };

然后在控制器中异步调用客户端方法:

import { client as redisClient } from 'datasource/redis';

async function yourControllerMethod() {
  try {
    const expireTime = Number(process.env.REDIS_EXPIRE_TIME);
    if (isNaN(expireTime)) {
      throw new Error('REDIS_EXPIRE_TIME must be a valid number');
    }
    await redisClient.setEx("Key", expireTime, "VALUE");
    Logger.info('Redis key set successfully');
  } catch (err) {
    Logger.error('Redis operation failed:', err);
  }
}

三、额外注意事项

  • 始终确保Redis操作在异步函数中执行,并用try/catch捕获错误,避免未处理的Promise rejection
  • 检查process.env.REDIS_EXPIRE_TIME是否存在且为有效数字,避免传入NaN导致Redis操作失败
  • 新版Redis客户端默认带有自动重连机制,无需在error事件中手动调用init,否则可能导致无限循环

内容的提问来源于stack exchange,提问作者Uday Mishra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 15:07:45