Node.js异步IO

为什么要异步I/O?

  • 用户体验角度讲,异步IO可以消除UI阻塞,快速响应资源

    • JavaScript是单线程的,它与UI渲染共用一个线程。所以在JavaScript执行的时候,UI渲染将处于停顿的状态,用户体验较差。而异步请求可以在下载资源的时候,JavaScript和UI渲染都同时执行,消除UI阻塞,降低响应资源需要的时间开销。
    • 假如一个资源来自两个不同位置的数据的返回,第一个资源需要M毫秒的耗时,第二个资源需要N毫秒的耗时。当采用同步的方式,总耗时为(M+N)毫秒,代码大致如下:

      //耗时为M毫秒
      getData(‘from_db‘);
      //耗时为N毫秒
      getData(‘from_remote_api‘);

      当采用异步的方式,总耗时为max(M,N),代码大致如下:

      getData(‘from_db‘,function(result){
        //消费时间为M
      });
      
      getData(‘from_remote_api‘,function(result){
        //消费时间为N
      });

      随着应用的复杂性,情景会变成M+N+...和max(M,N,...),此时同步和异步的优劣就会更加凸显。另一方面,随着网站和应用的扩展,数据往往会分布到多台服务器上,而分布意味着M和N的值会线性增长,这也会放大异步和同步在性能上的差异。总之,IO是昂贵的,分布式IO是更昂贵的!

  • 资源分配角度讲,异步IO可以让单线程远离阻塞,以更好地利用CPU

    • 假设业务线上有一组互不相关的任务需要完成,现行的主流方法有以下两种:

      • 单线程同步执行:会阻塞IO导致硬件资源和CPU得不到更优的使用
      • 多线程并发执行:会出现死锁、状态同步等问题
    • Node的解决方案
      • 利用单线程,远离多线程的死锁、状态同步等问题;
      • 利用异步I/O,让单线程远离阻塞,更好的利CPU

                         图1.1-异步I/O调用示意图

异步IO实现现状?

I/O的阻塞与非阻塞:IO对于操作系统内核而言,只有阻塞与非阻塞两种方式。阻塞模式的I/O会造成应用程序等待,直到I/O完成。同时操作系统也支持将I/O操作设置为非阻塞模式,这时应用程序的调用将可能在没有拿到真正数据时就立即返回了,为此应用程序需要多次调用才能确认I/O操作完全完成。这种重复调用判断操作是否完成的技术叫做“轮询”。

I/O的同步与异步:I/O的同步与异步出现在应用程序中。如果做阻塞I/O调用,应用程序等待调用的完成的过程就是一种同步状况。相反,I/O为非阻塞模式时,应用程序则是异步的。

  • 调用阻塞IO:会造成CPU等待IO,浪费等待时间,CPU的处理能力不能充分利用:

  • 调用非阻塞IO:虽然不会阻塞IO,但由于IO并没有完成,立即返回的并不是业务层期望的数据,仅仅是当前调用的状态

为了获取完整的数据,应用程序需要重复调用IO操作来确认是否完成,叫做轮询。轮询技术主要有以下这些:

  • read:它是最原始,性能最低的一种。
  • select:在read基础上的改进方案,通过对文件描述符上的事件状态进行判断。
  • poll:较select有所改进,采用链表的方式避免数据长度的限制,其次它能避免不必要的检查。但在文件描述符过多时,性能十分低下。
  • epoll:该方案是Linux下效率最高的IO事件通知机制,具体方法见图。

          

尽管epoll已经利用事件来降低了CPU的耗用,但是休眠期间CPU是闲置的,对于当前线程而言利用率不够。那么,是否有一种理想的异步I/O呢?

答案是当然有!理想的异步I/O实现如下图所示:

聪明的你想到了:我们可以通过信号或回调将数据传递给应用程序啊!

的确,Linux下提供了一种异步IO的方式(AIO),就是通过信号或回调传递数据的。

但不幸的是,只有Linux下有,而且还存在一些如系统缓存无法利用的缺陷。

所以,在现实中的异步IO是这样实现的:

我们利用多线程,主线程负责计算,IO线程负责IO操作,线程间通过信号进行通信。

聪明的你也许会问:Node不是单线程吗,如何实现多线程呢?

其实,Node底层是可以实现多线程的,只是上层提供给用户的JavaScript是单线程的。而这里,我们探讨的正是Node底层是如何实现异步IO的?

再细一点,其实Linux是利用如上的多线程创建线程池实现的,而Windows则是利用IOCP创建的。

在清楚了Node底层对异步IO的实现原理后,我们就可以进一步理解一个Node进程是如何实现异步IO的了。

  • Node的异步I/O模型:

    • 事件循环:进程启动时,Node会创建一个类似while(true)的循环,判断是否有事件需要处理

    • 观察者:观察者是用来判断是否有事件需要处理。事件循环中有一到多个观察者,判断过程会向观察者询问是否有需要处理的事件。这个过程类似于饭店的厨师与前台服务员的关系。厨师每做完一轮菜,就会向前台服务员询问是否有要做的菜,如果有就继续做,没有的话就下班了。这一过程中,前台服务员就相当于观察者,她收到的顾客点单就是回调函数。

           注:事件循环是一个典型的生产者/消费者模型。异步I/O、网络请求是生产者,而事件循环则从观察者那里取出事件并处理。

    • 请求对象:以fs.open( )方法为例,

      fs.open = function(path, flags, mode, callback) {
      
      //...
      
      binding.open(pathModule._makeLong(path),
      
      stringToFlags(flags),
      
      mode,
      
      callback);
      
      };
      

        

      这个函数的作用是根据指定的路径和参数去打开一个文件,从而得到一个文件描述符,是后续所有I/O操作的初始操作。

图1.2-调用示意图

整个调用过程:JavaScript -> Node核心模块 -> C++内建模块 -> libuv系统调用

      在uv_fs_open的调用过程中,Node.js创建了一个FSReqWrap请求对象。从JavaScript传入的参数和当前方法都被封装在这个请求对象中,其中回调函数则被设置在这个对象的oncomplete_sym属性上。

req_wrap->object_->Set(oncomplete_sym, callback);

对象包装完毕后,调用QueueUserWorkItem方法将这个FSReqWrap对象推入线程池中等待执行。

QueueUserWorkItem(&uv_fs_thread_proc, req, WT_EXECUTELONGFUNCTION)

QueueUserWorkItem接受三个参数,第一个是要执行的方法,第二个是方法的上下文,第三个是执行的标志。

至此,由JavaScript层面发起的异步调用第一阶段就此结束。

    • 执行回调:组装好请求对象,送入I/O线程池等待执行,实际上完成了异步I/O的第一部分,回调通知是第二部分。当线程池中有可用线程的时候调用uv_fs_thread_proc方法执行。该方法会根据传入的类型调用相应的底层函数,以uv_fs_open为例,实际会调用到fs__open方法。调用完毕之后,会将获取的结果设置在req->result上。然后调用PostQueuedCompletionStatus通知我们的IOCP*对象操作已经完成,并将线程归还给线程池。
PostQueuedCompletionStatus((loop)->iocp, 0, 0, &((req)->overlapped)

PostQueuedCompletionStatus方法的作用是向创建的IOCP上相关的线程通信,线程根据执行状况和传入的参数判定退出。

在这一过程中,每次事件循环会调用GetQueuedCompletionStatus()方法检查线程池中是否有执行完的请求,若有,会将请求对象加入到I/O观察者的队列中,将其作为事件处理。

I/O观察者回调函数的行为就是取出请求对象的result属性作为参数,取出oncomplete_sym属性作为方法,然后调用执行,以此达到执行回调函数的目的。

注:IOCP是windows下得异步I/O解决方案

总结

JavaScript是单线程的,但Node本身其实是多线程的,除了用户代码无法并行执行外,所有的I/O请求是可以并行执行的。事件循环是Node异步I/O实现的核心,Node通过事件驱动的方式处理请求,使得其无须为每个请求创建额外的线程,省掉了创建和销毁线程的开销。同时也因为线程数较少,不受线程上下文切换的影响,维持了Node的高性能。

时间: 2024-12-19 02:13:24

Node.js异步IO的相关文章

node js异步IO机制

同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行:而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数. 阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间:而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成. node js的异步I/O是它的一个重要功能,为了讲清楚这个机制,先说

深入理解node.js异步编程

1. 概述目前开源社区最火热的技术当属Node.js莫属了,作为使用Javascript为主要开发语言的服务器端编程技术和平台,一开始就注定会引人瞩目. 当然能够吸引众人的目光,肯定不是三教九流之辈,必然拥有独特的优势和魅力,才能引起群猿追逐.其中当属异步IO和事件编程模型,本文据Node.js的异步IO和事件编程做深入分析. ##2. 什么是异步同步和异步是一个比较早的概念,大抵在操作系统发明时应该就出现了.举一个最简单的生活中的例子,比如发短信的情况会比较好说明他们的区别:同步:正在处于苦逼

[转载]使用node.js+socket.io搭建实时消息系统

在开发web应用时,经常会有消息接收需求.例如后台处理完某个任务,需要告知用户等.一个简单的做法,是使用ajax轮询.这样带来的问题一是低效,二是消息触达不够实时.另一个方法是使用websocket来接收消息,但可惜IE不支持这种方式.下面推荐一种既能实时接收消息,又能兼容各种浏览器的方案,那就是node.js+socket.io. node.js的异步非阻塞模型,做消息推送非常合适.socket.io则负责屏蔽浏览器的差异,其会选择性的使用下列方式建立连接:websocket, flash s

Node.js异步处理CPU密集型任务

Node.js擅长数据密集型实时(data-intensive real-time)交互的应用场景.然而数据密集型实时应用程序并不是只有I/O密集型任务,当碰到CPU密集型任务时,比如要对数据加解密(node.bcrypt.js),数据压缩和解压(node-tar),或者要根据用户的身份对图片做些个性化处理,在这些场景下,主线程致力于做复杂的CPU计算,IO请求队列中的任务就被阻塞. Node.js主线程的event loop在处理所有的任务/事件时,都是沿着事件队列顺序执行的,所以在其中任何一

转:Node.js异步处理CPU密集型任务的新思路

原文来自于:http://www.infoq.com/cn/articles/new-idea-of-nodejs-asynchronous-processing-tasks?utm_source=infoq&utm_medium=popular_links_homepage Node.js擅长数据密集型实时(data-intensive real-time)交互的应用场景.然而数据密集型实时应用程序并不是只有I/O密集型任务,当碰到CPU密集型任务时,比如要对数据加解密(node.bcrypt

Node.js与io.js那些事儿

14年12月,多位重量级Node.js开发者不满Joyent对Node.js的管理,自立门户创建了io.js.io.js的发展速度非常快,先是于2015年1月份发布了1.0版本,并且很快就达到了2.0版本,社区非常活跃.而最近io.js社区又宣布,这两个项目将合并到Node基金会下,并暂时由“Node.js和io.js核心技术团队联合监督”运营.本文将聊一聊Node.js项目的一些历史情况,与io.js项目之间的恩怨纠葛,他们将来的发展去向.希望能从历史的层面去了解这个开源项目在运营模式上是如何

Node.js与io.js的那些事儿

转自:http://www.infoq.com/cn/articles/node-js-and-io-js 去年12月,多位重量级Node.js开发者不满Joyent对Node.js的管理,自立门户创建了io.js.io.js的发展速度非常快,先是于2015年1月份发布了1.0版本,并且很快就达到了2.0版本,社区非常活跃.而最近io.js社区又宣布,这两个项目将合并到Node基金会下,并暂时由“Node.js和io.js核心技术团队联合监督”运营.本文将聊一聊Node.js项目的一些历史情况,

谁才是性能之王?( Node.js vs io.js v2.0.0 )

我们注意到 io.js v2.0.0 RC 版公告中有些有趣的内容.这个社区版的 Node.js 同样基于 V8 引擎实现,而且主版本提交的频率远高于它的父项目(Node.js). 就 像我们之前提到的,之前我们热衷于用 Node.js 开发了 Raygun API,以确保在高负载情况下达到尽可能短的响应时间,给我们的用户提供最佳的服务.之前的一片博客比较了一下 Node.js 与 io.js的性能,后来 io.js V2 版本发布了,我们准备重温下不同情况下的性能测试. 配置 像 之前一样,下

用node.js(socket.io)实现数据实时推送

在做商品拍卖的时候,要求在商品的拍卖页面需要实时的更新当前商品的最高价格.实现的方式有很多,比如: 1.setInterval每隔n秒去异步拉取数据(缺点:更新不够实时) 2. AJAX轮询方式方式推送数据(缺点:服务端需要在死循环中反复查询数据库) 3.websocket推送数据(缺点:仅支持html5标准的浏览器) socket.io的简要介绍 所有客户端都通过socket.io挂在nodejs服务器上(注意: 只是挂着,不需要任何循环,因为它是事件驱动的):需要推送消息了,服务器就与nod