在众多高级编程语言或运行平台中,Node是首个将异步做为主要编程方式和设计理念。前端
Node的基调:异步I/O、事件驱动和单线程。编程
Nginx采用纯C编写。后端
Nginx具有面向客户端链接的强大能力,但受限于各类同步的编程语言。数组
Node既能够做为服务器去处理客户端的大量并发请求,也能够做为客户端面向网络中的各个应用进行并发请求。浏览器
前端经过异步能够消除掉UI阻塞的现象,可是前端获取资源的速度也取决于后端的响应速度。服务器
I/O是昂贵的,分布式I/O是更昂贵的。网络
只有后端可以快速响应资源,才能让前端的体验变好。多线程
假设业务场景中有一组互不相关的任务须要执行,主流的解决方案有:并发
添加硬件资源是一种提高服务质量的方式,但并非惟一的方式。异步
单线程同步编程模型会因阻塞I/O致使硬件资源得不到更优的使用;
多线程编程模型也由于编程中的死锁、状态同步等问题让人诟病。
Node的解决方案:
为了弥补单线程没法利用多核CPU的缺点,Node提供了相似前端浏览器中Web Works的子进程,该子进程能够经过工做进程高效地利用CPU和I/O。
操做系统内核对I/O只有两种方式:
阻塞I/O的特色:
阻塞I/O形成了CPU等待I/O,浪费等待时间,CPU的处理能力不能获得充分利用。为了提升性能,内核提供了非阻塞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。
Windows的IOCP:
调用异步方法,等待I/O完成以后的通知,执行回调,用户无须考虑轮询,但内部是线程池的原理,不一样之处在于这些线程池由系统内核接手管理。
Node的libuv:
Node提供了libuv做为抽象封装层,使得全部平台兼容性的判断都由这层来判断,并保证上层的Node与下层的自定义的线程池及ICOP之间各类独立。
完成整个异步I/O环节的有事件循环、观察者和请求对象等。
事件循环:
观察者:
每一个事件循环有一个或者多个观察者,而判断是否有事件要处理的过程就是 向这些观察者询问是否有要处理的事件。
在Node中,事件主要来源于网络请求、文件I/O等这些事件对应的观察者都有文件I/O观察者、网络I/O观察者。
观察者将事件进行了分类,事件循环是个典型的生产者/消费者模型。异步I/O、网络请求等是事件的生产者,源源不断为Node提供不一样类型的事件,这些事件被传递到对应观察者,事件循环从观察者中提取事件并处理。
请求对象是异步I/O过程当中的重要中间产物,全部的状态都保存在这个对象中,包括送入线程池等待执行以及I/O操做完毕后的回调处理。
I/O观察者回调函数的行为就是取出请求对象的result属性做为参数,取出oncomplete_sym属性做为方法,而后调用执行,以达到调用JavaScript中传入的回调函数的目的。
整个I/O的流程:
事件循环、观察者、请求对象、I/O线程池这四者共同构成了Node异步I/O模型的基本要素。
非I/O的异步API:setTimeout()、setInerval()、process.nextTick()和setImmediate()。
setTimeout()和setInerval()与浏览器中的API是一致的,分别用于单次和屡次定时执行任务。
它们的实现原理和异步I/O类似,只是不须要I/O线程池的参与。调用setTimeout()或者setInerval()建立的定时器会被插入到定时器观察者内部的一个红黑树中。每次Tick执行时,会从该红黑树中迭代定时器对象,检查是否超过定时时间,若是超过,就造成一个事件,它的回调函数将当即执行。
定时器的缺点:
定时器并不是是精确的。尽管事件循环十分快,可是若是某一次循环占用的时间较多,那么下次循环时,它也许超时好久了。
每次调用process.nextTick()方法,只会将回调函数放入队列中,在下一轮时取出执行。定时器中采用红黑树的操做时间复杂度为O(lg(n)) ,nextTick () 的时间复杂度为 O(1)。相比之下,process.nextTick()更加高效。
setImmediate()方法和process.nextTick()方法类似,都是将回调函数延迟执行。
可是,process.nextTick()中的回调函数执行的优先级要高于setImmediate()。
缘由在于事件循环对观察者的检查是有前后顺序的,process.nextTick()属于idle观察者,setImmediate()属于check观察者。在每一轮循环检查中,idle观察者先于I/O观察者,I/O观察者先于check观察者。
在具体实现上,process.nextTick()的回调函数保存在一个数组中,setImmediate()的结果保存在链表中;
在行为上,process.nextTick()在每轮循环中会将数组中的回调函数所有执行完,而setImmediate()在每轮循坏中执行链表的每个回调函数。
事件驱动的实质:
经过主循环加事件触发的方式来运行程序。
利用Node构建Web服务器流程图:
服务器模型对比:
Node高性能的缘由: