Node.js项目Redis缓存遇连接错误求助:ClientClosedError与Socket问题
问题描述
我正尝试在Node.js项目中集成Redis。Redis实例初始化代码如下(redis.ts):
import * as redis from "redis"; import { promisify } from "util"; export const client = redis.createClient(); export const getAsync = promisify(client.get).bind(client); export const setAsync = promisify(client.set).bind(client); client.on("error", (err) => { console.log("Error " + err); });
开发了一个待办事项(Todo)应用,其中有一个控制器负责从数据库查询单个Todo数据,Todo ID通过params传递,修改后的控制器代码如下:
export const getTodo = async (req: Request, res: Response, next: NextFunction) => { try { const { id } = req.params; const cachedTodo = await getAsync(`todo-${id}`); if (cachedTodo) { res.status(200).json({ message: "todo details", todo: cachedTodo, category: cachedTodo.category.name }); } const todo: any = await Todo.findOne({ include: [Category], where: { id: id, userId: res.locals.userId } }); await setAsync(`todo-${id}`, todo); res.status(200).json({ message: "todo details", todo: todo}); } catch (err) { res.status(500).json({ message: getErrorMessage(err) }); } };
运行代码时,先遇到ClientClosedError: The client is closed错误,添加await client.connect()到getAsync()之前后,又出现Socket already opened错误。使用Postman测试接口时,首次请求加载无法停止,取消后再次请求会触发该错误。
问题排查与修复
1. Redis客户端连接逻辑错误
你的redis.ts仅创建了客户端实例但未主动建立连接,旧版Redis客户端(v3及以下)不会自动发起连接,导致ClientClosedError。但如果在每次请求时调用client.connect(),又会重复触发连接操作,引发Socket already opened错误。
修复方案:在redis.ts初始化时一次性完成连接,避免重复执行:
import * as redis from "redis"; import { promisify } from "util"; export const client = redis.createClient(); export const getAsync = promisify(client.get).bind(client); export const setAsync = promisify(client.set).bind(client); // 初始化阶段完成Redis连接,仅执行一次 (async () => { try { await client.connect(); console.log("Redis connected successfully"); } catch (err) { console.error("Redis connection failed:", err); } })(); client.on("error", (err) => { console.error("Redis client error:", err); });
2. 控制器响应逻辑漏洞
当前代码中,缓存命中后返回响应但未终止函数执行,后续会继续执行数据库查询和二次响应,导致请求挂起(重复调用res.json会破坏响应流程)。
修复方案:缓存命中时添加return终止函数:
export const getTodo = async (req: Request, res: Response, next: NextFunction) => { try { const { id } = req.params; const cachedTodo = await getAsync(`todo-${id}`); if (cachedTodo) { // 缓存存储的是字符串,需解析为JSON才能访问属性 const parsedTodo = JSON.parse(cachedTodo); return res.status(200).json({ message: "todo details from cache", todo: parsedTodo, category: parsedTodo.category.name }); } const todo: any = await Todo.findOne({ include: [Category], where: { id: id, userId: res.locals.userId } }); // Redis仅支持字符串存储,需将对象转为JSON字符串 await setAsync(`todo-${id}`, JSON.stringify(todo)); return res.status(200).json({ message: "todo details from database", todo: todo}); } catch (err) { return res.status(500).json({ message: getErrorMessage(err) }); } };
3. Redis数据类型存储错误
Redis仅支持字符串、哈希、列表等特定数据类型,直接存储todo对象会被序列化为[object Object],无法正常解析使用。必须将对象转为JSON字符串存储,取出时再解析为对象。
4. 额外优化建议
- 给缓存设置过期时间,避免数据长期不一致:
// 设置缓存1小时后过期 await setAsync(`todo-${id}`, JSON.stringify(todo), "EX", 3600); - 若使用Redis v4+版本,可直接使用官方Promise API,无需手动
promisify:import { createClient } from "redis"; export const client = createClient(); client.on("error", (err) => console.error("Redis Client Error", err)); await client.connect(); // 直接使用client.get()/client.set(),自带Promise支持
内容的提问来源于stack exchange,提问作者PaOne
相关产品推荐
相关产品推荐

