是否在节点中使顺序HTTP请求成为阻塞操作?

you*_*rrr 8 asynchronous http blocking node.js

请注意,我的问题的无关信息将被"引用"

像这样(随意跳过这些).

问题

我正在使用节点代表多个客户端发出有序HTTP请求.这样,最初使用几个不同的页面加载来获得所需结果的内容现在只通过我的服务器接受一个请求.我目前正在使用'async'模块进行流量控制,使用'request'模块进行HTTP请求.大约有5个回调,使用console.time,从开始到结束大约需要2秒钟(下面包含草图代码).

现在我对节点缺乏经验,但我知道节点的单线程特性.虽然我已多次读过该节点不是为CPU绑定任务而构建的,但直到现在我才真正理解这意味着什么.如果我对正在发生的事情有正确的理解,这意味着我目前拥有的(正在开发中)不会扩展到甚至超过10个客户端.

由于我不是节点的专家,我问这个问题(在标题中)得到确认,确保几个连续的HTTP请求确实是阻塞的.

结语

如果是这种情况,我希望我会问一个不同的SO问题(经过适当的研究)讨论各种可能的解决方案,我应该选择继续在节点中解决这个问题(这本身可能不适合我正在尝试的去做).

其他结束的想法

我真的很抱歉,如果这个问题不够详细,太苛刻,或者语言特别华丽(我试着简明扼要).

感谢所有能帮我解决问题的人!

我之前提到的代码:

var async = require('async');
var request = require('request');

...

async.waterfall([
    function(cb) {
        console.time('1');

        request(someUrl1, function(err, res, body) {
            // load and parse the given web page.

            // make a callback with data parsed from the web page
        });
    },
    function(someParameters, cb) {
        console.timeEnd('1');
        console.time('2');

        request({url: someUrl2, method: 'POST', form: {/* data */}}, function(err, res, body) {
            // more computation

            // make a callback with a session cookie given by the visited url
        });
    },
    function(jar, cb) {
        console.timeEnd('2');
        console.time('3');

        request({url: someUrl3, method: 'GET', jar: jar /* cookie from the previous callback */}, function(err, res, body) {
            // do more parsing + computation

            // make another callback with the results
        });
    },
    function(moreParameters, cb) {
        console.timeEnd('3');
        console.time('4');

        request({url: someUrl4, method: 'POST', jar: jar, form : {/*data*/}}, function(err, res, body) {
            // make final callback after some more computation.
            //This part takes about ~1s to complete
        });
    }
], function (err, result) {
    console.timeEnd('4'); //
    res.status(200).send();
});
Run Code Online (Sandbox Code Playgroud)

sle*_*man 6

通常,node.js 中的 I/O 是非阻塞的。您可以通过同时向您的服务器发出多个请求来测试这一点。例如,如果每个请求需要 1 秒来处理,阻塞服务器将需要 2 秒来处理 2 个同时请求,但非阻塞服务器处理两个请求只需要 1 秒多一点。

但是,您可以通过使用sync-request模块而不是request故意使请求阻塞。显然,这不推荐用于服务器。

下面是一些代码来演示阻塞和非阻塞 I/O 之间的区别:

var req = require('request');
var sync = require('sync-request');

// Load example.com N times (yes, it's a real website):
var N = 10;

console.log('BLOCKING test ==========');
var start = new Date().valueOf();
for (var i=0;i<N;i++) {
    var res = sync('GET','http://www.example.com')
    console.log('Downloaded ' + res.getBody().length + ' bytes');
}
var end = new Date().valueOf();
console.log('Total time: ' + (end-start) + 'ms');

console.log('NON-BLOCKING test ======');
var loaded = 0;
var start = new Date().valueOf();
for (var i=0;i<N;i++) {
    req('http://www.example.com',function( err, response, body ) {
        loaded++;
        console.log('Downloaded ' + body.length + ' bytes');
        if (loaded == N) {
            var end = new Date().valueOf();
            console.log('Total time: ' + (end-start) + 'ms');
        }
    })
}
Run Code Online (Sandbox Code Playgroud)

运行上面的代码,您将看到非阻塞测试处理所有请求所需的时间与处理单个请求所需的时间大致相同(例如,如果您设置 N = 10,则非阻塞代码执行 10 次比阻塞代码更快)。这清楚地说明了请求是非阻塞的。


补充回答:

您还提到您担心您的进程会占用大量 CPU。但是在您的代码中,您没有对 CPU 实用程序进行基准测试。您混合了网络请求时间(I/O,我们知道它是非阻塞的)和 CPU 处理时间。要测量请求处于阻塞模式的时间,请将代码更改为:

async.waterfall([
    function(cb) {
        request(someUrl1, function(err, res, body) {
            console.time('1');
            // load and parse the given web page.
            console.timeEnd('1');
            // make a callback with data parsed from the web page
        });
    },
    function(someParameters, cb) {
        request({url: someUrl2, method: 'POST', form: {/* data */}}, function(err, res, body) {
            console.time('2');
            // more computation
            console.timeEnd('2');

            // make a callback with a session cookie given by the visited url
        });
    },
    function(jar, cb) {
        request({url: someUrl3, method: 'GET', jar: jar /* cookie from the previous callback */}, function(err, res, body) {
            console.time('3');
            // do more parsing + computation
            console.timeEnd('3');
            // make another callback with the results
        });
    },
    function(moreParameters, cb) {
        request({url: someUrl4, method: 'POST', jar: jar, form : {/*data*/}}, function(err, res, body) {
            console.time('4');
            // some more computation.
            console.timeEnd('4');

            // make final callback
        });
    }
], function (err, result) {
    res.status(200).send();
});
Run Code Online (Sandbox Code Playgroud)

您的代码仅阻塞在“更多计算”部分。因此,您可以完全忽略等待其他部分执行所花费的任何时间。事实上,这正是 node 可以同时处理多个请求的方式。在等待其他部分调用相应的回调时(您提到可能需要长达 1 秒),节点可以执行其他 javascript 代码并处理其他请求。


jfr*_*d00 5

您的代码是非阻塞的,因为它使用具有该request()功能的非阻塞I / O。这意味着在获取一系列HTTP请求时,node.js可以自由服务其他请求。

如何async.waterfall()使您的请求顺序排列并将一个结果传递给下一个。请求本身是非阻塞的,async.waterfall()不会更改或影响该请求。您所拥有的系列意味着您连续有多个非阻塞请求。

您所拥有的类似于一系列嵌套setTimeout()调用。例如,此代码序列需要5秒钟才能到达内部回调(就像您async.waterfall()花n秒钟才能到达最后一个回调):

setTimeout(function() {
    setTimeout(function() {
        setTimeout(function() {
            setTimeout(function() {
                setTimeout(function() {
                    // it takes 5 seconds to get here
                }, 1000);
            }, 1000);
        }, 1000);
    }, 1000);
}, 1000);
Run Code Online (Sandbox Code Playgroud)

但是,这基本上使用了零CPU,因为它只是5个连续的异步操作。实际的node.js进程可能需要不超过1毫秒的时间来安排下setTimeout()一个定时器,然后从字面上看,node.js进程可能会做很多其他事情,直到系统发布一个事件来触发下一个定时器。

您可以在以下参考资料中了解有关node.js事件队列如何工作的更多信息:

在节点中等待回调时运行任意代码?

非阻塞http服务器中的阻塞代码

Javascript / Node中的隐藏线程永远不会执行用户代码:是否可能,如果这样,是否会导致出现竞争状态的不可思议的可能性?

JavaScript如何在后台处理AJAX响应?(写关于浏览器,但是概念是一样的)

如果我对正在发生的事情有正确的了解,这意味着我目前(开发中)所拥有的资源绝不会扩展到超过10个客户。

这不是正确的理解。一个node.js进程可以轻松地同时运行数千个非阻塞请求。顺序测量的时间只是开始时间-与CPU资源或其他消耗的OS资源无关(请参阅下面有关非阻塞资源消耗的注释)。

我仍然有关于在此特定应用程序中使用节点的问题。考虑到它所做的工作不是简单的I / O而是计算密集型的,我担心它会如何扩展。我觉得我应该切换到支持多线程的平台。我要问的问题/我表达的担忧是否有意义?我可能只是吐出全部学士学位,却不知道我在说什么。

非阻塞I / O几乎不占用CPU(最初发送请求时仅消耗一点,然后在结果返回时仅消耗一点),但是当compmuter等待删除结果时,根本没有消耗CPU,并且没有操作系统线程已消耗。这是node.js可以很好地扩展非阻塞I / O的原因之一,因为当计算机等待来自删除站点的响应时,不使用任何资源。

如果您对请求的处理是计算密集型的(例如,需要花费大量的纯阻塞CPU时间来处理),那么可以,您可能想探索让多个进程参与运行计算。有多种方法可以做到这一点。您可以通过nodejs集群模块使用集群(因此,您只需拥有多个相同的node.js进程,每个进程都处理来自不同客户端的请求)。或者,您可以创建要执行的计算密集型工作的工作队列,并拥有一组子进程来执行计算密集型工作。或者,还有其他几种选择。这不是需要从node.js转向解决的问题类型-可以使用node.js很好地解决。