Node.js 在 http.request 循环中处理内存不足

2024-05-07

在我的 Node.js 服务器中,我无法弄清楚为什么它会耗尽内存。我的node.js 服务器为它收到的每个http 请求发出一个远程http 请求,因此我尝试使用下面的示例脚本来复制该问题,该脚本也耗尽了内存。

仅当 for 循环中的迭代次数非常高时才会发生这种情况。

从我的角度来看,该问题与 Node.js 正在对远程 http 请求进行排队这一事实有关。如何避免这种情况?

这是示例脚本:

(function() {
  var http, i, mypost, post_data;
  http = require('http');
  post_data = 'signature=XXX%7CPSFA%7Cxxxxx_value%7CMyclass%7CMysubclass%7CMxxxxx&schedule=schedule_name_6569&company=XXXX';
  mypost = function(post_data, cb) {
    var post_options, req;
    post_options = {
      host: 'myhost.com',
      port: 8000,
      path: '/set_xxxx',
      method: 'POST',
      headers: {
        'Content-Length': post_data.length
      }
    };
    req = http.request(post_options, function(res) {
      var res_data;
      res.setEncoding('utf-8');
      res_data = '';
      res.on('data', function(chunk) {
        return res_data += chunk;
      });
      return res.on('end', function() {
        return cb();
      });
    });
    req.on('error', function(e) {
      return console.debug('TM problem with request: ' + e.message);
    });
    req.write(post_data);
    return req.end;
  };
  for (i = 1; i <= 1000000; i++) {
    mypost(post_data, function() {});
  }
}).call(this);


$ node -v
v0.4.9
$ node sample.js
FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory

提前致谢

古尔登PT


限制进入服务器的请求流

可以防止内置过载Server及其 HTTP/HTTPS 变体,通过设置maxConnections实例上的属性。设置此属性将导致节点停止accept()ing 连接并强制操作系统在以下情况下丢弃请求listen()积压已满,申请已在处理中maxConnections要求。

限制传出请求

有时,有必要限制传出请求,如问题中的示例脚本所示。

直接使用节点或使用通用池

正如问题所示,未经检查地直接使用节点网络子系统可能会导致内存不足错误。就像是node-pool使得主动池管理很有吸引力,但它并没有解决无约束排队的根本问题。这样做的原因是node-pool不提供有关客户端池状态的任何反馈。

UPDATE:从 v1.0.7 开始,节点池包含受这篇文章启发的补丁,用于添加布尔返回值acquire()。以下部分中的代码不再需要,流模式的示例是节点池的工作代码。

破解抽象

正如所证明的安德烈·西多罗夫 https://stackoverflow.com/questions/6623683/node-js-process-out-of-memory-in-http-request-loop/6624173#6624173,可以通过显式跟踪队列大小并将排队代码与请求代码混合来达到解决方案:

var useExplicitThrottling = function () {
  var active = 0
  var remaining = 10
  var queueRequests = function () {
    while(active < 2 && --remaining >= 0) {
      active++;
      pool.acquire(function (err, client) {
        if (err) {
          console.log("Error acquiring from pool")
          if (--active < 2) queueRequests()
          return
        }
        console.log("Handling request with client " + client)
        setTimeout(function () {
          pool.release(client)
          if(--active < 2) {
            queueRequests()
          }
        }, 1000)
      })
    }
  }
  queueRequests(10)
  console.log("Finished!")
}

借用流模式

The streams http://nodejs.org/docs/v0.4.12/api/streams.html#streams模式是节点中惯用的解决方案。流有一个write返回的操作false当流无法缓冲更多数据时。相同的模式可以应用于池对象acquire()返回false当获得最大数量的客户时。 Adrain当活动客户端数量低于最大值时,会发出事件。池抽象再次关闭,并且可以省略对池大小的显式引用。

var useStreams = function () {
  var queueRequests = function (remaining) {
    var full = false
    pool.once('drain', function() {
        if (remaining) queueRequests(remaining)
    })

    while(!full && --remaining >= 0) {
      console.log("Sending request...")
      full = !pool.acquire(function (err, client) {
        if (err) {
          console.log("Error acquiring from pool")
          return
        }
        console.log("Handling request with client " + client)
        setTimeout(pool.release, 1000, client)
      })
    }
  }
  queueRequests(10)
  console.log("Finished!")
}

Fibers

可以通过在队列顶部提供阻塞抽象来获得替代解决方案。这fibers https://github.com/laverdet/node-fibers模块暴露协程 https://secure.wikimedia.org/wikipedia/en/wiki/Coroutine是用 C++ 实现的。通过使用纤程,可以在不阻塞节点事件循环的情况下阻塞执行上下文。虽然我发现这种方法非常优雅,但由于对所有看起来同步的事物的好奇厌恶,它在节点社区中经常被忽视。请注意,排除callcc实用程序,实际的循环逻辑非常简洁。

/* This is the call-with-current-continuation found in Scheme and other
 * Lisps. It captures the current call context and passes a callback to
 * resume it as an argument to the function. Here, I've modified it to fit
 * JavaScript and node.js paradigms by making it a method on Function
 * objects and using function (err, result) style callbacks.
 */
Function.prototype.callcc = function(context  /* args... */) {
  var that = this,
      caller = Fiber.current,
      fiber = Fiber(function () {
        that.apply(context, Array.prototype.slice.call(arguments, 1).concat(
          function (err, result) {
            if (err)
              caller.throwInto(err)
            else
              caller.run(result)
          }
        ))
      })
  process.nextTick(fiber.run.bind(fiber))
  return Fiber.yield()
}

var useFibers = function () {
  var remaining = 10
  while(--remaining >= 0) {
    console.log("Sending request...")
    try {
      client = pool.acquire.callcc(this)
      console.log("Handling request with client " + client);
      setTimeout(pool.release, 1000, client)
    } catch (x) {
      console.log("Error acquiring from pool")
    }
  }
  console.log("Finished!")
}

结论

有许多正确的方法可以解决这个问题。但是,对于需要在多个上下文中共享单个池的库作者或应用程序,最好正确封装该池。这样做有助于防止错误并生成更清晰、更模块化的代码。防止无约束排队就变成了事件舞蹈或协程模式。我希望这个答案能够消除对阻塞式代码和异步行为的大量 FUD 和困惑,并鼓励您编写让您满意的代码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Node.js 在 http.request 循环中处理内存不足 的相关文章

随机推荐

  • 使用 Jackson 和 Spring 序列化 Joda DateTime

    我在使用 Spring Boot 和 Jackson databind 2 5 2 将 Joda DateTime 从 java 序列化和反序列化为 json 并再次返回时遇到问题 我的 pom xml 看起来像这样
  • 批处理:在特定程序中打开特定文件?

    当记事本是 txt 文件的默认程序时 如何告诉 Windows 在写字板中打开 C test test txt 接受的答案对我不起作用 我不确定这是因为我试图运行的程序 还是因为路径中有空格 即使我用引号引起来 或者其他原因 不管怎样 我可
  • 不构建自己的错误跟踪系统的原因[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 现在我已经多次遇到一个团队的计划 他们想要构建自己的错误跟踪系统 不是作为产品 而是作为内部工具 我听到的赞成的论点通常是这样的 想要通过一些内部构建
  • 无法在 JavaCC 中加载主类

    我是人工智能学生 我们使用 JavaCC 我是新来的 我正在尝试简单的例子 但出现了一些错误 1 我从它的网站下载了JavaCC 0 6 2 我把它解压到C盘 3 我将此代码写入扩展名为 jj 的文件中 PARSE BEGIN Test i
  • 向 Java 类添加编程注释

    使用示例 我想在类字段上添加一个自定义注释 MyContainer 然后在所有此类字段上自动添加相关的 Hibernate 注释 取决于字段类型和属性 另外 我需要向类添加 JAXB XmlType 注释 并使类型名称基于类名称 我还想根据
  • Android 上的透明视频

    有什么办法让Android播放带有透明区域的视频吗 当我尝试在 VideoView 中播放包含透明区域的 WebM 视频时 视图的背景保持黑色 我希望看到透明区域上显示的父视图的背景 而不是黑色 到目前为止 我发现的唯一可行的解 决方案是从
  • C# 多线程 - 在线程之间移动对象

    我正在使用一个 winforms 控件 它既是一个 GUI 元素 又执行一些尚未向开发人员公开的内部处理 当这个组件被实例化时 可能需要 5 到 15 秒的时间才能准备好 所以我想做的就是将它放在另一个线程上 完成后将其带回 gui 线程并
  • 反应本机导航抽屉:按下覆盖层时抽屉不会关闭

    我尝试过很多建议https github com react navigation react navigation issues 5370 https github com react navigation react navigatio
  • 推送通知 Drupal 模块 - 删除设备(Web 服务)

    我已经开始使用这个模块 并且我已经成功注册设备 按照他的作者发布的说明进行操作 here http www danielhanold com story drupal 7 push notifications how to register
  • 如何从传统的java web应用程序(带有web.xml)迁移到spring boot?

    我想将我的项目切换到基于弹簧的产品 我的第一步是将我的 java web 应用程序从生成的 WAR 文件转换为由 spring boot 提供支持的独立可执行 jar 让我们从 github 中获取一个开源 Web 应用程序示例 Vaadi
  • 如何使用 jsPDF 和 HTML2Canvas 从网站获取多页 pdf?

    我有一个使用 HTML2Canvas 来截取屏幕截图的脚本div在页面中 然后使用 jsPDF 将其转换为 pdf 问题是生成的 pdf 只有一页 而屏幕截图在某些情况下需要不止一页 例如 屏幕截图大于 8 5x11 宽度很好 但我需要它来
  • 检查 mySQL 数据库中现有用户的最佳方法? [复制]

    这个问题在这里已经有答案了 我正在尝试在 PHP 中创建用户登录 创建脚本 并且想知道创建用户时检查用户名是否存在的最佳方法 目前 我有以下代码 function createUser uname pword server gt conne
  • 获取带有结束标记且缺少起始标记的精确 html()

    目前对于带有 HTML 的正文 hello p hai p span Welcome span 关于警报 body html 它提醒hello p hai p span Welcome span Fiddle http jsfiddle n
  • 页面错误陷阱的成本

    我有一个应用程序 它定期 每 1 或 2 秒后 通过分叉自身来获取检查点 因此 检查点是原始进程的一个分支 它一直保持空闲状态 直到原始进程发生某些错误时被要求启动 现在我的问题是fork的写时复制机制的成本有多大 每当原始进程写入内存页面
  • 从soap标头中删除mustUnderstand属性

    如何从轴客户端中的soap标头中删除mustunderstand属性 即使我没有特别设置它 当我设置soap标头信息mustundertand时 actor属性会自动添加到soap消息中 有人知道如何删除它们吗 我正在使用 Axis2 1
  • 使用 django Rest 框架 ModelViewSet 类而不是 APIView 渲染表单

    我想使用其余框架为我的 django 模型创建样板表单 文档显示它使用 APIView http www django rest framework org topics html and forms rendering forms htt
  • 如何获取当前所选键盘布局的显示名称

    我需要以语言栏显示输入语言的方式向用户显示输入语言列表 例如 目前我有 class Program static void Main string args var langs InputLanguage InstalledInputLan
  • 从 Perl 脚本 DBI 关闭 MSSQL 服务器

    我正在写一个 perl 脚本 其中我必须关闭我的 mssql 服务器 做一些操作 然后我必须重新启动它 我知道一种方法是使用 netstat 来停止服务 但我不能使用它 所以我尝试安装 DBI 和 DBD ODBC 模块 我可以通过以下代码
  • 解密后缺少几个字符

    这是我原来的xml table table
  • Node.js 在 http.request 循环中处理内存不足

    在我的 Node js 服务器中 我无法弄清楚为什么它会耗尽内存 我的node js 服务器为它收到的每个http 请求发出一个远程http 请求 因此我尝试使用下面的示例脚本来复制该问题 该脚本也耗尽了内存 仅当 for 循环中的迭代次数