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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 22:07:48