Bull Queue任务卡顿求助:Fetch是否阻塞Node.js事件循环?
我们在Heroku平台采用Node.js+Redis+Bull Queue处理后台任务,运行数天后偶尔出现任务卡在active状态,后续任务只能进入wait状态。任务仅负责轻量数据处理并调用外部系统,卡顿发生时日志停留在Dynamic-Data: Starts sending data,无任何报错,既不触发超时也不标记失败,已排除性能问题。结合提供的核心代码,以下是具体排查方向和修复方案:
1. Fetch请求超时逻辑漏洞
当前sendRequest函数的超时清除逻辑仅在请求成功响应后执行,若请求因网络/代理问题长期处于pending状态,即使AbortController触发终止,也可能存在信号未正确传递、定时器未清理的问题,导致Promise一直挂起。
修复方案:
将定时器清理移至finally块,确保无论请求成功/失败都清除定时器;同时明确捕获超时终止错误,便于排查:
async function sendRequest(options) { const controller = new AbortController(); const signal = controller.signal; options.signal = signal; const timeoutId = setTimeout(() => { controller.abort(); }, 22000); try { const response = await fetch(options.url, options); return await response.json(); } catch (err) { if (err.name === 'AbortError') { winston.error("Dynamic-Data: Request timeout aborted"); } throw err; } finally { clearTimeout(timeoutId); } }
同时检查proxyAgent的配置,确保其未设置比22秒更长的超时时间,避免覆盖AbortController的超时逻辑。
2. Bull Queue任务处理模式冲突
当前混用了async/await和done回调的任务处理模式,Bull官方建议:若使用async函数处理任务,无需调用done,直接通过返回值/抛出错误通知任务状态。混合使用可能导致任务状态更新异常,卡在active。
修复方案:
移除done回调,改用纯async函数模式处理任务,并确保错误被正确抛出,让Bull自动标记任务状态:
// 任务处理逻辑 workQueue.process(maxJobsPerWorker = 10, async (job) => { await data_process(job.data); }); // 修改data_process,确保错误向上抛出 async function data_process(data) { try { let responses = []; const systemsCount = data.systemsToSendDataTo.length; let completed_requests = 0; const fetchPromises = data.systemsToSendDataTo.map(async (systemData) => { // ...原有请求逻辑 try { const JSONresponse = await sendRequest(options); responses.push(JSONresponse); } catch (err) { responses.push(err); } finally { completed_requests++; if (completed_requests === systemsCount) { winston.info("Info: ", {responses: responses}); } } }); await Promise.all(fetchPromises); winston.debug("Dynamic-Data: All fetch promises resolved."); } catch(error) { winston.info("Dynamic-Data: Error:", { err: error}); throw error; // 抛出错误,让Bull标记任务为failed } }
3. Promise挂起与变量引用错误
原代码中handleFinalResponse函数引用了未定义的systemsToSendDataTo变量(实际应为data.systemsToSendDataTo),虽不会导致任务挂起,但会使最终响应日志无法输出,干扰问题排查。同时,若某个sendRequest的Promise因外部系统/代理问题长期pending,会导致Promise.all一直等待,进而使整个任务卡在active状态。
修复方案:
- 修正变量引用错误,改用提前定义的
systemsCount判断完成状态; - 给Bull Queue全局设置任务超时,即使内部逻辑未响应,也能自动标记任务失败:
// 创建队列时配置默认任务超时 const workQueue = new Queue('work', { redis: redisConfig, defaultJobOptions: { timeout: 30000 // 30秒超时,根据业务调整 } });
4. 进程级资源泄漏与平台问题
运行数天后出现卡顿,需排查资源泄漏或Heroku平台限制:
- 定期记录
process.memoryUsage()数据,排查内存泄漏; - 检查
proxyAgent的连接池配置,设置合理的maxSockets和连接超时,避免连接耗尽; - 查看Heroku平台日志,确认是否存在连接数超限、dyno资源告警等平台级问题。
内容的提问来源于stack exchange,提问作者Arey Jeremy

