用 request 包发了几大波请求后 Nodejs 好像阻塞了,可能是什么原因

用 request 包发了几大波请求后 Nodejs 好像阻塞了,可能是什么原因

概述

其实不是阻塞,但不知道怎么用一句话描述,具体现象后面有详细讲。

用的是 request. 伪代码如下


let count = 100;

function batch() { // 从数据库用 SELECT … LIMIT 100 取出 100 条 url for (let url in urlList) { const req = request(…); req.on(‘error’, () => { // 一些其它操作 count–; if (count === 0) { count = 100; batch(); } }); req.on(‘end’, () => { // 一些其它操作 count–; if (count === 0) { count = 100; batch(); } }); }

第一波发出去的请求是正常的,过了几波后,在创建完 request, 控制权交还给事件循环时, 要等好长时间才会开始发请求,这个等待的时间会越来越久。试过调整连接池,但只能缓解,不能解决。

大家有没有碰到过类似的问题?或者有没有排查问题的思路?

等待发请求时的具体现象

  • 通过打印日志,确定:
    • 计数器工作正常
    • 创建 request 对象的那个循环已经执行完了
  • 但是
    • lsof 看到没有创建任何 tcp 连接(前面的数据库连接除外)。
    • 没有任何 request 的调试日志输出
    • 用 gdb backtrace node 进程,看到几个线程是阻塞在 poll 上,其余几个线程是阻塞在信号量上
  • 等待一段时间后
    • 出现 request 的调试日志输出, lsof 看到 tcp 连接
    • 这个等待时间会越来越久

4 回复

batch 里的循环没有引用 count, 在 error/end 修改 end 有什么用…
你这样一下创建了 n 多请求, 会导致系统打开的 fd 急剧增加…

如果你是使用 callback 的话可以使用 async.parallelLimit, see https://github.com/caolan/async#paralleltasks-callback
promise, co, 或者 async/await 可以使用 promise.map 控制下并发, see https://github.com/magicdawn/promise.map


> 在 error/end 修改 end 有什么用…

在 error/end 修改 count 有什么用…

我的目的就是并发很多请求,因为这些 url 大部分是无法访问的。
修改 count 这个先不管吧,这是伪代码(公司政策不允许外发代码),实际代码这里肯定是没问题的,因为打印的日志显示计数从 100 降到 0 了,并且 batch 也已经被调用了。

async.parallel 看起来不错,下周我去换上。不过这个应该不是关键问题吧,我这也相当于是自己实现了 async.parallel 的功能。

在 Node.js 中使用 request 包发送大量请求后遇到阻塞问题,可能的原因包括以下几个方面:

  1. 事件循环阻塞: Node.js 是基于事件循环的,如果某个操作(如网络请求)占用了太多时间,会导致事件循环无法及时处理其他任务。虽然 request 包是异步的,但如果请求数量过多且未正确处理,可能会导致资源耗尽。

  2. 内存泄漏: 如果请求处理不当,可能会导致内存泄漏,进而耗尽系统资源,使 Node.js 进程变得缓慢甚至阻塞。

  3. 连接池耗尽: 默认情况下,request 包可能不会有效管理 HTTP 连接池。如果发送的请求数量超过连接池的限制,新的请求可能会被阻塞,等待连接释放。

为了解决这个问题,你可以考虑以下几点:

  • 使用 Promise 或 async/await 控制并发: 通过限制并发请求的数量,避免一次性发送过多请求。
const request = require('request-promise-native');

async function fetchUrls(urls) {
  const results = [];
  for (const url of urls) {
    results.push(await request(url));
  }
  return results;
}

// 使用并发控制库,如 p-limit
const pLimit = require('p-limit');
const limit = pLimit(5); // 限制并发数为5

async function fetchUrlsLimited(urls) {
  const promises = urls.map(url => limit(() => request(url)));
  return Promise.all(promises);
}
  • 检查并优化内存使用: 确保请求处理中没有内存泄漏。

  • 配置连接池: 使用支持连接池的 HTTP 客户端库,如 axiosnode-fetch(配合 undici 等)。

回到顶部