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

在我的node.js服务器我无法弄清楚,为什么它耗尽内存。 我的node.js服务器为每个接收到的http请求发出一个远程http请求,因此我试图用下面的示例脚本复制这个问题,这也是内存不足。

这只有在for循环中的迭代非常高时才会发生。

从我的angular度来看,这个问题与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 

Tks提前

Gulden PT

约束请求stream入服务器

通过设置实例上的maxConnections属性,可以防止内置Server及其HTTP / HTTPS变体的过载。 设置此属性将导致节点停止accept()连接,并强制操作系统在listen()积压已满且应用程序已在处理maxConnections请求时放弃请求。

限制传出的请求

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

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

如问题所示,直接使用节点networking子系统可能会导致内存不足错误。 像node-pool的活动池pipe理有吸引力,但它不能解决无约束排队的根本问题。 其原因是node-pool不提供有关客户端池状态的任何反馈。

更新 :从v1.0.7开始,node-pool包含一个受此post启发的补丁,以添加一个boolean返回值来acquire() 。 下一节中的代码不再是必需的,stream模式的示例是带有节点池的工作代码。

打开抽象

如Andrey Sidorov所示 ,可以通过显式地跟踪队列大小并使用请求代码混合队列代码来实现解决scheme:

 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!") } 

借用stream模式

stream模式是节点中惯用的解决scheme。 stream有一个write操作,当stream不能caching更多的数据时,返回false 。 当获得最大数量的客户端时,可以将相同的模式应用于池对象,其中acquire()返回false 。 当活动客户端的数量下降到最大值以下时,会发生drain事件。 池抽象再次closures,可以省略显式引用池大小。

 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!") } 

纤维

通过在队列顶部提供阻塞抽象可以获得替代解决scheme。 fibers模块公开了用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和围绕式的代码和asynchronous行为的困惑,并鼓励你写代码,让你快乐。

是的,你甚至在启动它们之前要排队1000000个请求。 此版本保持有限的请求数(100):

  function do_1000000_req( cb ) { num_active = 0; num_finished = 0; num_sheduled = 0; function shedule() { while (num_active < 100 && num_sheduled < 1000000) { num_active++; num_sheduled++; mypost(function() { num_active--; num_finished++; if (num_finished == 1000000) { cb(); return; } else if (num_sheduled < 1000000) shedule(); }); } } } do_1000000_req( function() { console.log('done!'); }); 

节点池模块可以帮助你。 欲了解更多详情,请参阅此文(法文), http: //blog.touv.fr/2011/08/http-request-loop-in-nodejs.html