对于Node.js的全面认识我还不够,只知道它用了谷歌的v8引擎,能做到高并发,(谷歌浏览器用的是v8引擎,这款浏览器对于脚本的执行性能是秒杀了其它浏览器)。下面我们来全面了解下。
先放图
从图中可以看出,node.js主要由三部分构成
V8 javascript引擎
Libuv线程池
第三方库
我们来看看Libuv线程池(核心)
libuv使用异步,事件驱动的编程方式,核心是提供i/o的事件循环和异步回调。libuv的API包含有时间,非阻塞的网络,异步文件操作,子进程等等。(跨平台的异步IO库,但它提供的功能不仅仅是IO,还 包括进程、线程、信号、定时器、进程间通信,线程池等。)
1.什么是异步I/O、事件循环?
1.1 异步I/O(输入输出input/output)
首先,node的异步I/O不等于非阻塞I/O。非阻塞I/O调用后会虽然也是立即返回,但是应用层会不断的重复I/O操作去轮询系统是否完成数据读取,让CPU处理状态判断,对CPU造成资源的浪费。
然而在异步I/O里,基于多子线程的方式去解决了非阻塞I/O的问题,应用层(主线程)发起I/O请求后,就不再过问情况了。然后让子线程来完成数据获取,当读写完成后通知主线程。
1.2 事件循环
当子线程完成并通知主线程后,主线程在什么时机去调用呢?这时候就得说到事件循环。事件循环类似于while(true)的一个程序,在每次循环的时候,都会去检查一下是否有事件待处理,如果有就取出执行,否则继续下一次循环。
那主线程是去哪里检查呢,这里就要引入观察者这个概念,每次事件循环中有一个或者多个观察者,按照优先级依次进行询问和处理。
所以,事件循环是一个典型的生产/消费模型,异步I/O、网络请求等是事件的生产者,这些事件被传递到对应的观察者那里,等待事件循环取出并处理。
1.2.1 请求对象
当调用异步I/O时,会创建一个请求对象,该对象包含了传入的参数和当前方法上下文,以及我们最关注的回调函数。创建完成后,将其推入线程池中等待执行。至此,主线程的调用立即返回,继续执行当前任务的后续操作。
1.2.2 执行回调
当线程池中有可用线程池时,会对请求对象进行调用,执行完毕后,会将执行结果记录在对象中,归还线程,并且告诉观察者自己已经ready,然后观察者会把它放入队列中。
当主线程来询问观察者时,就可以取出请求对象,并且执行上面的回调函数。至此,整个异步I/O的流程就结束了。
2.为什么设计成单线程(单主线程)?
目前业内存在的几种web服务器模型有同步、多进程和多线程,每个都存在一些问题:
- 同步:一次只能处理一个请求,造成CPU浪费,总处理耗时久。
- 多进程、多线程:虽然大幅度的解决了同步存在问题,但是当遇到大并发请求时,资源会很快用光,只能通过增加系统资源来扩展。
反而基于单主线程的方式,也就是Node的异步I/O+事件循环,无须为每一个请求创建额外的对应线程,可以省掉创建和销毁线程的开销,同时操作系统在调度任务时因为线程较少,上下文切换的代价很低。