理解Node的事件驱动,异步非阻塞

  • Post author:
  • Post category:其他




异步和事件触发:浏览器

了解浏览器的工作原理对我们了解Node的工作原理会有很大帮助。它们都是事件驱动(用事件轮询)和非阻塞的I/O处理(用异步I/O)。下面举个例子说明这是什么意思。

我们来看一小段jQuery用XMLHttpRequest(XHR)做Ajax请求的代码:

在这里插入图片描述

这个程序会发送一个到resource.json的HTTP请求。当响应返回时会调用带着参数data的匿名函数(在这个上下文中的“回调函数”),data就是从那个请求中得到的数据。

注意,代码没有写成下面这样

在这里插入图片描述

在这个例子中,假定对resource.json的响应在准备好后会存储在变量data中,并且在此之前函数console.log不会执行。I/O操作(Ajax请求)会“阻塞”脚本继续执行,直到数据准备好。

因为浏览器是单线程的,如果这个请求用了400ms才返回,那么页面上的其他任何事件都要等到那之后才能执行。那样用户体验将很糟糕。

谢天谢地,实际情况不是这样的。当浏览器中有I/O操作时,该操作会在事件轮询的外面执行(脚本执行的主顺序之外),然后当这个I/O操作完成时,它会发出一个“事件”,会有一个函数(通常称作“回调”)处理它,如图1-1所示。——所有需要data才能继续的代码放在回调函数中。那些不需要data依然能继续执行的代码放在回调函数之外,不会受到阻塞。

这个I/O是异步的,并且不会“阻塞”脚本执行,事件轮询仍然可以响应页面上执行的其他交互或请求。这样,浏览器可以对客户做出响应,并且可以处理页面上的很多交互动作。

在这里插入图片描述



异步和事件触发:服务器

可能大多数人都了解传统的服务端编程的I/O模型,就像上文那个“阻塞”的jQuery例子一样。下面是一个PHP的例子:

在这里插入图片描述

这段代码做了些I/O操作,并且在所有数据回来之前,这个进程会被阻塞。对于很多程序而言,这个模型没什么问题,并且很容易理解。但有一点可能会被忽略:这个进程也有状态,或者说内存空间,并且在I/O完成之前基本上什么也不会做。根据I/O操作的延迟情况,那可能会有10ms到几分钟的时间。延迟也可能是由下列意外情况引发的:

 硬盘正在执行维护操作,读/写都暂停了;

 因为负载增加,数据库查询变得更慢了;

如果程序在I/O上阻塞了,当有更多请求过来时,服务器会怎么处理呢?在这种情景中通常会用多线程的方式。一种常见的实现是给每个连接分配一个线程,并为那些连接设置一个线程池。当需要大量的线程处理很多并发的服务器连接时,线程会消耗额外的操作系统资源。线程需要CPU和额外的RAM来做上下文切换—–(上述处理方式称有阻塞I/O的多线程方式)。

在Node中,处理方式则是带有异步I/O的事件轮询(像浏览器一样,事件驱动的轮询),I/O几乎总是在主事件轮询之外进行,使得服务器可以一直处于高效并且随时能够做出响应的状态,就像NGINX一样。这样进程就更加不会受I/O限制,因为I/O延迟不会拖垮服务器,或者像在阻塞方式下那样占用很多资源。因此一些在服务器上曾经是重量级的操作,在Node服务器上仍然可以是轻量级的。



DIRT 程序

实际上,Node所针对的应用程序有一个专门的简称:DIRT。它表示数据密集型实时程序。因为Node自身在I/O上非常轻量,它善于将数据从一个管道混排或代理到另一个管道上,这能在处理大量请求时持有很多开放的连接,并且只占用一小部分内存。它的设计目标是保证响应能力,跟浏览器一样。

对Web来说,实时程序是个新生事物。现在有很多Web程序提供的信息几乎都是即时的,比

如通过白板在线协作、对临近公交车的实时精确定位,以及多人在线游戏。不管是用实时组件增强已有程序,还是打造全新的程序,Web都在朝着响应性和协作型环境逐渐进发。而这种新型的Web应用程序需要一个能够实时响应大量并发用户请求的平台来支撑它们。这正是Node所擅长的领域,并且不仅限于Web程序,其他I/O负载比较重的程序也可以用到它。

Node从构建开始就有一个事件驱动和异步的模型,对于JavaScript而言则是由“宿主”环境决定的。JavaScript最常见的宿主环境浏览器是事件驱动和异步的。

Node重新实现了宿主中那些常用的对象,尽量让浏览器和服务器保持一致,比如:

 计时器API(比如setTimeout);

 控制台API(比如console.log)。

Node还有一组用来处理多种网络和文件I/O的核心模块。其中包括用于HTTP、TLS、HTTPS、文件系统(POSIX)、数据报(UDP)和NET(TCP)的模块。这些核心模块刻意做得很小、底层并且简单,只包含要给基于I/O的程序用的组成部分。第三方模块基于这些核心模块,针对常见的问题进行了更高层的抽象。



简单的异步程序

先来一个ajax的例子:

$.post('./resource.json',function(data){
	console.log(data);
})

做一个跟这个差不多的例子,不过这次是用文件系统(fs)模块从硬盘中

加载resource.json。注意看下面这个程序跟前面那个jQuery的例子有多像:

var fs = require('fs');
fs.readFile('./resource.json', function(er,data) {
	console.log(data)
})

这段程序要从硬盘里读取resource.json文件。当所有数据都读出来后,会调用那个匿名函数

(即“回调函数”),传给它的参数是er(如果出现错误)和data(文件中的数据)。

这个过程是在后台完成的,这样在该过程中,我们可以继续处理其他任何操作,直到数据准

备好。我们之前说过的那些事件触发和异步的好处都是自动实现的。差别在于,这个不是在浏览

器中用jQuery发起一个Ajax请求,而是在Node中访问文件系统抓取resource.json。后面这个过程

如图1-5所示。

在这里插入图片描述



Node构建HTTP服务器

Node常被用来构建服务器。下面是个简单的HTTP服务器实现,它会用“Hello World”响应所有请求:

var http = require('http');
var server = http.createServer();
server.on('request', function(req,res) {
	res.writeHead(200,{'Content-type' : 'text/plain'});
	res.end('hello world\n');
})
server.listen(3000);

只要有请求过来,它就会激发回调函数function (req, res),把“Hello World”写入到响应中返回去。这个事件模型跟浏览器中对onclick事件的监听类似。在浏览器中,点击事件随时都可能发生,所以要设置一个函数来执行对事件的处理逻辑,而Node在这里提供了一个可以随时响应请求的函数。



版权声明:本文为qq_34568700原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。