Nodejs中http.createServer([requestListener])的requestListener函数代码会产生堵塞现象

Nodejs中http.createServer([requestListener])的requestListener函数代码会产生堵塞现象

对于http.createServer([requestListener]),我们最常用的用法无怪乎: http.createServer(function(request, response) { //针对不同url的处理代码 }); 这里假设在某个url A对应处理代码中很耗时,这时候你请求A地址的时候肯定会使浏览器处于一直加载状态,这时候你再打开一个页面,然后去请求url B,假设B地址的代码仅仅是现实一个静态页,然后你会发现B地址也处于一直加载状态,也就是说对于url A的耗时处理代码把整个node进程都给堵塞了。 但是我看了一下node中的文档: http.createServer([requestListener])# Returns a new web server object.

The requestListener is a function which is automatically added to the ‘request’ event. 人家说requestListener会被添加到request事件中。http.createServer会返回一个http.Server,而这个类继承自EventEmitter,含有request事件监听。对于这个request事件,文档中是这么写的: Event: ‘request’# function (request, response) { }

Emitted each time there is a request. Note that there may be multiple requests per connection (in the case of keep-alive connections). request is an instance of http.IncomingMessage and response is an instance of http.ServerResponse 看完了之后,我依然无法理解之前的那个堵塞现象发生的原因是什么,请各位大牛指教。


13 回复

Node.js 中 http.createServer([requestListener])requestListener 函数代码会产生堵塞现象

在 Node.js 中,http.createServer([requestListener]) 是创建 HTTP 服务器的一种常见方法。其中的 requestListener 是一个函数,它会在每次接收到 HTTP 请求时被调用。然而,如果 requestListener 中的某些操作非常耗时,可能会导致整个 Node.js 进程阻塞,从而影响其他请求的处理。

示例代码

让我们来看一个简单的例子,说明这种现象:

const http = require('http');

http.createServer((request, response) => {
    if (request.url === '/slow') {
        // 模拟一个耗时操作
        for (let i = 0; i < 1e9; i++) {
            // 这个循环会消耗大量时间
        }
        response.end('Slow response');
    } else {
        response.end('Fast response');
    }
}).listen(3000, () => {
    console.log('Server running at http://localhost:3000/');
});

在这个例子中,当我们访问 /slow 路径时,会执行一个非常耗时的操作(例如一个巨大的循环)。这会导致当前的请求处理时间变长,并且会阻塞其他请求的处理,直到该耗时操作完成。

解释

虽然 http.createServer 返回的是一个 http.Server 对象,它继承自 EventEmitter 类,并且 request 事件会在每次请求时触发,但 Node.js 是单线程的事件驱动架构。这意味着所有的请求处理逻辑都在同一个线程上运行。因此,如果一个请求处理程序中的代码执行时间过长,它会阻塞整个事件循环,使得其他请求也无法得到及时处理。

为了防止这种情况,可以使用异步操作或分叉处理来确保不会阻塞事件循环。例如,可以使用 setTimeout 来模拟异步操作:

const http = require('http');

http.createServer((request, response) => {
    if (request.url === '/slow') {
        setTimeout(() => {
            response.end('Slow response');
        }, 5000); // 延迟5秒响应
    } else {
        response.end('Fast response');
    }
}).listen(3000, () => {
    console.log('Server running at http://localhost:3000/');
});

通过这种方式,setTimeout 会将耗时操作放到事件队列中,从而不会阻塞主线程,允许其他请求继续处理。


var events = require(‘events’);

var em = new events.EventEmitter();

em.on(‘request’,function(type) { console.log(‘request:’ + type); if (type == 1) { var now = new Date().getTime(); while(new Date().getTime() < now + 1000*3600) { // do nothing } } else if (type == 2) { } else { console.log(‘unspport type:’ + type); } });

em.emit(‘request’,2); em.emit(‘request’,1); em.emit(‘request’,3);

我模拟了一下,情形大概是这个样子,如果加入em.emit(‘request’,1);这句话,整个进程就会被卡住。不知道理解的对不对。

用这个 setImmediate

你自己写了阻塞代码。

当一个request到来时,Event-Loop会将这个Listener回调函数放入执行队列, node中所有的代码都是一个一个从执行队列中拿出来执行的。这些执行都是在工作线程上(Event Loop本身可以认为在一个独立的线程中,我们一般不提这个线程,而将node称呼为一个单线程的执行环境), 既然所有的回调都是在一个工作线程上运行,那么,如果某个回调(listener)耗时太久,会然会阻塞执行队列中其它代码的执行,所以你第二个请求就会被第一个请求阻塞了

如果缓解这个问题呢?注意几个方面:

  1. 尽量少写耗时的同步代码,所有文件,网络操作全部用异步执行,对算法进行尽可能的优化
  2. 如果的确是一个大计算量的操作,也建议分解成一个一个小部分,然后通过process.nextTick, setTimeout这些来分片执行,从而间断的释放CPU时间
  3. 采用Cluster来缓解,也就是多进程模型,当一个进程被阻塞时,其它进程可以继续服务

我能怎么说呢, 你这个while是cpu密集, nodejs适合的场景是io密集, 难道你以为异步就不用消耗cpu

我关心的是node的运行原理,以此作为自己编程的指导。

1.node中怎样写异步操作,你说的网络和文件操作确实是可以用异步执行的,但是除了这两个领域之外的,我自定义的一个函数,怎么写成异步的?对于events.EventEmitter来说,他的on和emit函数,是不是都是在工作线程上进行的,换句话说,是不是使用EventEmitter并不能产生异步效果? 2.使用process.nextTick或者setTimeout的话,他的回调函数应该还是运行在工作线程中的把,这个样子应该只会让堵塞操作延迟发生吧。 3.我其实是想问,在单进程下怎样使某一个耗时操作不堵塞工作线程;我想如果在单个进程中有堵塞操作的话,采用多进程是个治标不治本的策略,极端情况下还会把所有进程都堵塞掉。

我关心的是node的运行原理,以此作为自己编程的指导。我这里是写了一个极端情况,来引出我的问题。

你应该仔细去了解一下nodejs的异步模式,了解一下Event Loop,推荐一篇文章http://blog.csdn.net/resouer/article/details/13004377

这篇文章跟我说的是两个层次的东西。 关于node异步的处理,这里有更底层c++代码的讲解(文章链接:http://www.infoq.com/cn/articles/nodejs-asynchronous-io#3970668-tsina-1-88821-4940258fac58681d93622513463cbd0b ),概括一下,就是: node内部采用一个线程池来处理事件,应有程序调用异步代码的时候,将会在线程池中添加一个事件对象,线程池中处理完成后,将当前事件的状态标志为处理完成,然后node的主线程轮询来获取哪些事件处理完成,将处理完的交给应有程序. 综上所述,问题的关键是能够写出异步的代码来,但是异步的代码怎么写呢?系统自带的文件和网络处理有异步的实现,如果我们自己写一个函数能否也能实现异步呢?

cpu密集和io密集 的区别是什么?

当你在 requestListener 中执行耗时操作时,Node.js 的单线程特性会导致其他请求被阻塞。这是因为 Node.js 是基于事件驱动和非阻塞 I/O 模型的,但在处理耗时任务时,它仍然会阻塞主线程,从而影响其他请求的处理。

为了更好地理解这个问题,让我们通过一段代码示例来演示这种现象:

const http = require('http');

http.createServer((req, res) => {
    if (req.url === '/slow') {
        // 模拟耗时操作
        for (let i = 0; i < 1e9; i++) {}
        res.end('Slow Response');
    } else {
        res.end('Fast Response');
    }
}).listen(3000);

console.log('Server running at http://127.0.0.1:3000/');

在这个例子中,当你访问 /slow 路径时,Node.js 会执行一个耗时的循环。由于 Node.js 是单线程的,这段代码会阻塞整个事件循环,直到循环结束。这将导致其他请求(包括访问 / 路径的请求)也会被阻塞,因为它们都在等待当前请求的处理完成。

为了避免这种情况,可以使用异步操作(如 setTimeoutasync/await 结合 Promise),这样就不会阻塞事件循环。例如:

const http = require('http');

function simulateAsyncTask(callback) {
    setTimeout(() => {
        callback();
    }, 5000);
}

http.createServer((req, res) => {
    if (req.url === '/async') {
        simulateAsyncTask(() => {
            res.end('Async Response');
        });
    } else {
        res.end('Fast Response');
    }
}).listen(3000);

console.log('Server running at http://127.0.0.1:3000/');

在这个例子中,simulateAsyncTask 使用 setTimeout 来模拟异步操作。这不会阻塞事件循环,因此不会影响其他请求的处理。

回到顶部