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

Bull Queue任务卡顿求助:Fetch是否阻塞Node.js事件循环?

Node.js+Bull Queue任务卡在active状态排查方案

我们在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 04:52:43