关于Nodejs

##异步I/O 在操作系统中,程序运行的空间分为内核空间和用户空间。我们常常提起的异 步I/O,其实质是用户空间中的程序不用依赖内核空间中的I/O操作实际完成,即 可进行后续任务。以下伪代码模仿了一个从磁盘上获取文件和一个从网络中获 取文件的操作。异步I/O的效果就是getFileFromNet的调用不依赖于getFile调用 的结束。

getFile("file_path"); 
getFileFromNet("url");

如果以上两个任务的时间分别为m和n。采用同步方式的程序要完成这两个任务 的时间总花销会是m + n。但是如果是采用异步方式的程序,在两种I/O可以并 行的状况下(比如网络I/O与文件I/O),时间开销将会减小为max(m, n)。

##异步I/O的必要性

有的语言为了设计得使应用程序调用方便,将程序设计为同步I/O的模型。这意 味着程序中的后续任务都需要等待I/O的完成。在等待I/O完成的过程中,程序无 法充分利用CPU。为了充分利用CPU,和使I/O可以并行,目前有两种方式可以 达到目的:

  • 多线程单进程
    多线程的设计之处就是为了在共享的程序空间中,实现并行处理任 务,从而达到充分利用CPU的效果。多线程的缺点在于执行时上下文 交换的开销较大,和状态同步(锁)的问题。同样它也使得程序的编 写和调用复杂化。

  • 单线程多进程
    为了避免多线程造成的使用不便问题,有的语言选择了单线程保持调 用简单化,采用启动多进程的方式来达到充分利用CPU和提升总体的 并行处理能力。 它的缺点在于业务逻辑复杂时(涉及多个I/O调用), 因为业务逻辑不能分布到多个进程之间,事务处理时长要远远大于多 线程模式。

前者在性能优化上还有回旋的余地,后者的做法纯粹是一种加三倍服务器的行 为。 而且现在的大型Web应用中,单机的情形是十分稀少的,一个事务往往需要跨 越网络几次才能完成最终处理。如果网络速度不够理想,m和n值都将会变大, 这时同步I/O的语言模型将会露出其最脆弱的状态。 这种场景下的异步I/O将会体现其优势,max(m, n)的时间开销可以有效地缓解m 和n值增长带来的性能问题。而当并行任务更多的时候,m + n + ...与max(m, n, ...)之间的孰优孰劣更是一目了然。从这个公式中,可以了解到异步I/O在分布式 环境中是多么重要,而Node.js天然地支持这种异步I/O,这是众多云计算厂商 对其青睐的根本原因。

##操作系统对异步I/O的支持

我们听到Node.js时,我们常常会听到异步,非阻塞,回调,事件这些词语混合 在一起。其中,异步与非阻塞听起来似乎是同一回事。从实际效果的角度说, 异步和非阻塞都达到了我们并行I/O的目的。但是从计算机内核I/O而言,异步/ 同步和阻塞/非阻塞实际上时两回事。

  • I/O的阻塞与非阻塞
    阻塞模式的I/O会造成应用程序等待,直到I/O完成。同时操作系统也支 持将I/O操作设置为非阻塞模式,这时应用程序的调用将可能在没有拿 到真正数据时就立即返回了,为此应用程序需要多次调用才能确认I/O 操作完全完成。

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

##异步I/O与轮询技术

当进行非阻塞I/O调用时,要读到完整的数据,应用程序需要进行多次轮询,才 能确保读取数据完成,以进行下一步的操作。 轮询技术的缺点在于应用程序要主动调用,会造成占用较多CPU时间片,性能 较为低下。现存的轮询技术有以下这些:

  • read
  • select
  • poll
  • epoll
  • pselect
  • kqueue

read是性能最低的一种,它通过重复调用来检查I/O的状态来完成完整数据读 取。select是一种改进方案,通过对文件描述符上的事件状态来进行判断。操作 系统还提供了poll、epoll等多路复用技术来提高性能。 轮询技术满足了异步I/O确保获取完整数据的保证。但是对于应用程序而言,它 仍然只能算时一种同步,因为应用程序仍然需要主动去判断I/O的状态,依旧花 费了很多CPU时间来等待。

上一种方法重复调用read进行轮询直到最终成功,用户程序会占用较多CPU, 性能较为低下。而实际上操作系统提供了select方法来代替这种重复read轮询进 行状态判断。select内部通过检查文件描述符上的事件状态来进行判断数据是否 完全读取。但是对于应用程序而言它仍然只能算是一种同步,因为应用程序仍 然需要主动去判断I/O的状态,依旧花费了很多CPU时间等待,select也是一种 轮询。

##理想的异步I/O模型

理想的异步I/O应该是应用程序发起异步调用,而不需要进行轮询,进而处理下 一个任务,只需在I/O完成后通过信号或是回调将数据传递给应用程序即可。

幸运的是,在Linux下存在一种这种方式,它原生提供了一种异步非阻塞I/O方式 (AIO)即是通过信号或回调来传递数据的。 不幸的是,只有Linux下有这么一种支持,而且还有缺陷(AIO仅支持内核I/O中 的O_DIRECT方式读取,导致无法利用系统缓存。参见:http://forum.nginx.org/read.php?2,113524,113587#msg-113587 以上都是基于非阻塞I/O进行的设定。另一种理想的异步I/O是采用阻塞I/O,但 加入多线程,将I/O操作分到多个线程上,利用线程之间的通信来模拟异步。 Glibc的AIO便是这样的典型http://www.ibm.com/developerworks/linux/library/l- async/。然而遗憾在于,它存在一些难以忍受的缺陷和bug。可以简单的概述 为:Linux平台下没有完美的异步I/O支持。 所幸的是,libev的作者Marc Alexander Lehmann重新实现了一个异步I/O的库: libeio。libeio实质依然是采用线程池与阻塞I/O模拟出来的异步I/O。 那么在Windows平台下的状况如何呢?而实际上,Windows有一种独有的内核 异步IO方案:IOCP。IOCP的思路是真正的异步I/O方案,调用异步方法,然后 等待I/O完成通知。IOCP内部依旧是通过线程实现,不同在于这些线程由系统 内核接手管理。IOCP的异步模型与Node.js的异步调用模型已经十分近似。 以上两种方案则正是Node.js选择的异步I/O方案。由于Windows平台和*nix平台 的差异,Node.js提供了libuv来作为抽象封装层,使得所有平台兼容性的判断都 由这一层次来完成,保证上层的Node.js与下层的libeio/libev及IOCP之间各自独 立。Node.js在编译期间会判断平台条件,选择性编译unix目录或是win目录下 的源文件到目标程序中。

##总结

Node.js的性能不错。按照创始人Ryan Dahl的说法,性能是Node.js考虑的重要 因素,选择C++和V8而不是Ruby或者其他的虚拟机也是基于性能的目的。 Node.js在设计上也是比较大胆,它以单进程、单线程模式运行(很吃惊,对 吧?这和Javascript的运行方式一致),事件驱动机制是Node.js通过内部单线 程高效率地维护事件循环队列来实现的,没有多线程的资源占用和上下文切换,这意味着面对大规模的http请求,Node.js凭借事件驱动搞定一切,习惯了传统语言的网络服务开发人员可能对多线程并发和协作非常熟悉,但是面对 Node.js,我们需要接受和理解它的特点。由此我们是否可以推测出这样的设计 会导致负载的压力集中在CPU(事件循环处理?)而不是内存(还记得Java虚 拟机抛出OutOfMemory异常的日子吗?),眼见为实,不如来看看淘宝共享数据平台团队对Node.js的性能测试:

  • 物理机配置:RHEL 5.2、CPU 2.2GHz、内存4G

  • Node.js应用场景:MemCache代理,每次取100字节数据

  • 连接池大小:50

  • 并发用户数:100

  • 测试结果(socket模式):内存(30M)、QPS(16700)、 CPU(95%)

  • (优点)因为Node是基于事件驱动和无阻塞的,所以非常适合处理并发请求, 因此构建在Node上的代理服务器相比其他技术实现(如Ruby)的服务器表现 要好得多。此外,与Node代理服务器交互的客户端代码是由javascript语言编写 的,因此客户端和服务器端都用同一种语言编写,这是非常美妙的事情。

  • (缺点)Node是一个相对新的开源项目,所以不太稳定,它总是一直在变,而 且缺少足够多的第三方库支持。看起来,就像是Ruby/Rails当年的样子。

转载于:https://my.oschina.net/huangsz/blog/186678

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值