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

时间:2015-10-06 02:15:51

标签: node.js http asynchronous blocking

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

  像这样(可以跳过这些)。

问题

我正在使用node代表多个客户端发出有序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();
});

3 个答案:

答案 0 :(得分:4)

您的代码是非阻塞的,因为它使用具有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);

但是,它基本上使用零CPU,因为它只是连续5次异步操作。实际的node.js进程参与调度下一个setTimeout()的时间可能不超过1ms,然后node.js进程可能会执行许多其他操作,直到系统发布事件以触发下一个计时器。

您可以阅读有关node.js事件队列如何在这些引用中工作的更多信息:

Run Arbitrary Code While Waiting For Callback in Node?

blocking code in non-blocking http server

Hidden threads in Javascript/Node that never execute user code: is it possible, and if so could it lead to an arcane possibility for a race condition?

How does JavaScript handle AJAX responses in the background?(关于浏览器的文章,但概念是一样的)

  

如果我对正在发生的事情有正确的理解,那就意味着   我目前拥有的(正在开发中)绝不会扩展到   甚至超过10个客户。

这不是一个正确的理解。 node.js进程可以在同一时间轻松地在飞行中拥有数千个非阻塞请求。您按顺序测量的时间只是一个开始时间 - 它与CPU资源或消耗的其他操作系统资源无关(请参阅下面有关非阻塞资源消耗的评论)。

  

我仍然担心将此节点用于节点   然后申请。考虑到这一点,我担心它会如何扩展   它所做的工作不是简单的I / O,而是计算密集型。   我觉得我应该切换到一个支持的平台   多线程。我正在问的问题/我所表达的担忧   感?我可能只是吐出总BS而且不知道我是什么   谈论。

非阻塞I / O几乎不消耗CPU(当请求最初发送时只有一点点,然后在结果返回时稍微消耗一点),但是当计算机正在等待删除结果时,没有CPU被消耗掉全部且没有OS线程被消耗。这是node.js在非阻塞I / O中很好地扩展的原因之一,因为当计算机等待来自删除站点的响应时没有使用任何资源。

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

答案 1 :(得分:3)

通常,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');
        }
    })
}

运行上面的代码,您会看到非阻塞测试处理所有请求的时间与处理单个请求的时间大致相同(例如,如果设置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();
});

您的代码仅阻止&#34;更多计算&#34;部分。因此,您可以完全忽略等待其他部分执行所花费的时间。实际上,这正是节点如何同时处理多个请求的关键。在等待其他部分调用相应的回调(您提到可能需要1秒钟)时,节点可以执行其他JavaScript代码并处理其他请求。

答案 2 :(得分:1)

您可以使用队列来处理nodeJs中的并发http调用 https://www.npmjs.com/package/concurrent-queue

    var cq = require('concurrent-queue');
    test_queue = cq();

    // request action method
    testQueue: function(req, res) {
        // queuing each request to process sequentially
        test_queue(req.user, function (err, user) {
            console.log(user.id+' done');
            res.json(200, user)
        });
    },


    // Queue will be processed one by one.
    test_queue.limit({ concurrency: 1 }).process(function (user, cb) {
        console.log(user.id + ' started')

        // async calls will go there
        setTimeout(function () {
            // on callback of async, call cb and return response.
            cb(null, user)
        }, 1000);

    });

请记住,它需要为仅需要一个用户一次访问或更新资源的敏感业务呼叫实现。

这将阻止您的I / O,并使您的用户等待,并且响应时间将变慢。

优化:

您可以通过创建依赖于资源的队列来使其更快并对其进行优化。因此,每个共享资源都有一个单独的队列,并且对同一资源的同步调用只能对同一资源执行,而对于不同资源,则将异步执行 < / p>

让我们假设您要基于当前用户来实现它。因此,对于同一用户,http调用只能同步执行,对于不同用户,https调用将异步

testQueue: function(req, res) {

    // if queue not exist for current user.
    if(! (test_queue.hasOwnProperty(req.user.id)) ){
        // initialize queue for current user
        test_queue[req.user.id] = cq();
        // initialize queue processing for current user
        // Queue will be processed one by one.
        test_queue[req.user.id].limit({ concurrency: 1 }).process(function (task, cb) {
            console.log(task.id + ' started')
            // async functionality will go there
            setTimeout(function () {
                cb(null, task)
            }, 1000)
        });
    }

    // queuing each request in user specific queue to process sequentially
    test_queue[req.user.id](req.user, function (err, user) {
        if(err){
            return;
        }
        res.json(200, user)
        console.log(user.id+' done');
    });
},

这将很快并且仅针对您想要的资源阻止I / O。