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 看完了之后,我依然无法理解之前的那个堵塞现象发生的原因是什么,请各位大牛指教。
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)耗时太久,会然会阻塞执行队列中其它代码的执行,所以你第二个请求就会被第一个请求阻塞了
如果缓解这个问题呢?注意几个方面:
- 尽量少写耗时的同步代码,所有文件,网络操作全部用异步执行,对算法进行尽可能的优化
- 如果的确是一个大计算量的操作,也建议分解成一个一个小部分,然后通过process.nextTick, setTimeout这些来分片执行,从而间断的释放CPU时间
- 采用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 是单线程的,这段代码会阻塞整个事件循环,直到循环结束。这将导致其他请求(包括访问 /
路径的请求)也会被阻塞,因为它们都在等待当前请求的处理完成。
为了避免这种情况,可以使用异步操作(如 setTimeout
或 async/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
来模拟异步操作。这不会阻塞事件循环,因此不会影响其他请求的处理。