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
相关产品推荐
相关产品推荐

