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

Generic Pool max/min参数失效:Node.js Redis连接池请求pending问题

问题:Redis连接池无法处理超过max限制的请求,全部处于pending状态

我在Node.js服务器中通过API访问Redis,为降低高频调用下的服务器压力,使用generic-pool实现连接池。配置max:20后,当发送30个异步请求时,所有请求都处于pending状态,无法正常处理。尝试增大max值也无法解决问题,需要分析原因并给出修复方案。

相关代码:

var PASSWORD = config.REDIS_PASSWORD;
var pool = createPool({
  create: () => {
    console.log("Creating new Redis client...");
    return new Promise((resolve, reject) => {
      const client = PASSWORD
        ? redis.createClient({
            host: config.REDIS_HOST,
            port: config.REDIS_PORT,
            password: PASSWORD,
          })
        : redis.createClient({
            host: config.REDIS_HOST,
            port: config.REDIS_PORT,
          });

      client.on("error", (err) => {
        console.error("Redis error:", err);
        reject(err);
      });

      client.on("ready", () => {
        console.log("Redis client ready");
        resolve(client);
      });
    });
  },
  destroy: (client) => {
    console.log("Destroying Redis client...");
    return new Promise((resolve) => {
      client.quit(() => {
        console.log("Redis client destroyed");
        resolve();
      });
    });
  },
  max: 20,
  min: 3,
});

const logPoolStats = () => {
  console.log(`Pool Size: ${pool.size}`);
  console.log(`Available: ${pool.available}`);
  console.log(`Borrowed: ${pool.borrowed}`);
  console.log(`Pending: ${pool.pending}` + "\n");
};

app.post("/executeCommand", async (req, res) => {
  const { redisKey, command, args = [] } = req.body;

  if (!redisKey || !command) {
    console.log("Invalid parameters received:", req.body);
    return res.status(400).json({ error: "Invalid parameters" });
  }

  let argumentsArray = Array.isArray(args) ? args : JSON.parse(args);
  argumentsArray = [redisKey, ...argumentsArray];

  let client;
  try {
    logPoolStats();
    client = await pool.acquire();
    logPoolStats();
    if (typeof client[command] === "function") {
      client[command](...argumentsArray, (err, result) => {
        pool.release(client);
        console.log("Client released back to pool");
        logPoolStats();
        if (err) {
          console.error(`Error executing command ${command}:`, err);
          return res
            .status(500)
            .json({ error: "Failed to execute Redis command" });
        }
        console.log(`Executed command ${command} with result:`);
        return res.json({
          command: command,
          data: result,
        });
      });
    } else {
      console.log(`Invalid Redis command attempted: ${command}`);
      pool.release(client);
      return res
        .status(400)
        .json({ error: `Invalid Redis command: ${command}` });
    }
  } catch (error) {
    if (client) {
      pool.release(client);
    }
    console.error("Failed to execute Redis command:", error);
    return res.status(500).json({ error: "Failed to execute Redis command" });
  }
});

问题原因分析

  • Redis回调与异步流程冲突:API处理中使用Redis客户端的回调模式执行命令,但pool.release()放在回调内部。当请求数超过max时,后续请求等待连接,但已占用的连接只有在回调执行完成后才会释放。如果Redis命令执行缓慢或回调未触发(如异常未捕获),会导致连接泄漏,池内无可用连接,所有请求陷入pending。
  • 缺少连接池超时配置:未设置acquireTimeoutMillis,当所有连接被占用时,新请求会无限等待,不会触发超时失败逻辑。
  • 客户端初始化容错不足:如果Redis客户端的ready事件未正常触发(如网络故障、认证失败),create函数的Promise无法resolve,连接池无法创建新连接,进一步加重pending问题。

解决建议与修复代码

1. 改用Redis Promise API

避免回调模式,使用Promise化的Redis命令,确保连接释放逻辑在可控的异步流程中执行。

2. 添加连接池超时配置

设置连接获取超时时间,防止请求无限等待;配置闲置连接回收,优化资源占用。

3. 完善错误处理与连接释放

使用finally块确保连接一定会被释放,避免异常导致连接泄漏。

修复后的完整代码:

const util = require('util');
var PASSWORD = config.REDIS_PASSWORD;
var pool = createPool({
  create: () => {
    console.log("Creating new Redis client...");
    return new Promise((resolve, reject) => {
      const client = PASSWORD
        ? redis.createClient({
            host: config.REDIS_HOST,
            port: config.REDIS_PORT,
            password: PASSWORD,
          })
        : redis.createClient({
            host: config.REDIS_HOST,
            port: config.REDIS_PORT,
          });

      client.on("error", (err) => {
        console.error("Redis error:", err);
        reject(err);
        client.quit();
      });

      client.on("ready", () => {
        console.log("Redis client ready");
        resolve(client);
      });

      client.on("end", () => {
        console.log("Redis client disconnected");
      });
    });
  },
  destroy: (client) => {
    console.log("Destroying Redis client...");
    return new Promise((resolve) => {
      client.quit(() => {
        console.log("Redis client destroyed");
        resolve();
      });
    });
  },
  max: 20,
  min: 3,
  acquireTimeoutMillis: 10000, // 请求获取连接超时10秒
  idleTimeoutMillis: 30000, // 闲置连接30秒后回收
  testOnBorrow: true, // 借出前检查连接可用性
});

const logPoolStats = () => {
  console.log(`Pool Size: ${pool.size}`);
  console.log(`Available: ${pool.available}`);
  console.log(`Borrowed: ${pool.borrowed}`);
  console.log(`Pending: ${pool.pending}` + "\n");
};

app.post("/executeCommand", async (req, res) => {
  const { redisKey, command, args = [] } = req.body;

  if (!redisKey || !command) {
    console.log("Invalid parameters received:", req.body);
    return res.status(400).json({ error: "Invalid parameters" });
  }

  let argumentsArray = Array.isArray(args) ? args : JSON.parse(args);
  argumentsArray = [redisKey, ...argumentsArray];

  let client;
  try {
    logPoolStats();
    client = await pool.acquire();
    logPoolStats();

    if (typeof client[command] !== "function") {
      console.log(`Invalid Redis command attempted: ${command}`);
      return res.status(400).json({ error: `Invalid Redis command: ${command}` });
    }

    // 将Redis命令Promise化(redis@4+版本可直接使用client[command](...args))
    const commandFn = util.promisify(client[command]).bind(client);
    const result = await commandFn(...argumentsArray);

    console.log(`Executed command ${command} with result:`);
    res.json({
      command: command,
      data: result,
    });
  } catch (error) {
    console.error("Failed to execute Redis command:", error);
    res.status(500).json({ error: "Failed to execute Redis command" });
  } finally {
    // 无论成功失败,确保连接释放
    if (client) {
      pool.release(client);
      console.log("Client released back to pool");
      logPoolStats();
    }
  }
});

额外优化点

  • 如果使用redis@4+版本,客户端原生支持Promise,无需util.promisify,直接调用client[command](...argumentsArray)即可返回Promise。
  • testOnBorrow配置可在借出连接前自动检查连接状态,避免使用已失效的连接。

内容的提问来源于stack exchange,提问作者harsha bajaj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 01:24:51