开发理念
小核心
Node.js 核心:Node.js 的运行期环境与内置模块
功能尽量少,把没必要放在核心里面的功能划分到用户空间(userland 或 userspace)之中,使得这些模块可以在核心之外形成一套生态系统
小模块
原则:
- Small is beautiful
- Make each program do one thing well
小模块的特性:
- Easier to understand and use (易理解、易用)
- Simpler to test and maintain (易于测试和维护)
- Perfect to share with the browser (完美支持浏览器)
small surface area(暴露需要的接口)
定义 Node 模块时,只暴露一个函数或构造器,不允许扩展或扩展性低,这样子可以减少应用场景,简化实现,便于维护,提高可用性。
simplicity and progmatism(简单实用)
设计必须简单,不管是实现还是接口。实现的简洁比接口的简洁更重要,做设计时最需要考虑的因素就是简单程度
好处:
- 实现起来更容易
- 所需资源更少,传输更快
- 用户更容易适应
- 更容易维护和理解
工作原理
I/O is slow (I/O 是耗时操作)
Blocking I/O (阻塞I/O)
I/O 请求后会阻塞之后的代码运行
所以需要开多进程或多线程各自操作,但这是耗内存的行为// 直到请求完成,数据可用,线程都是阻塞的
data = socket.read()
// 请求完成,数据可用
print(data)
Non-blocking I/O (非阻塞 I/O)
实用轮询算法(polling algorithm)实现,会大量消耗 CPU 时间resources = [socketA, socketB, pipeA]
while (!resources.isEmpty()) {
for (i = 0; i < resources.length; i++) {
resource = resources[i]
// 进行读操作
let data = resource.read()
if (data === NO_DATA_AVAILABLE) {
// 此时还没有数据
continue
}
if (data === RESOURCE_CLOSED) {
// 资源被释放,从队列中移除该链接
resources.remove(i)
} else {
consumeData(data)
}
}
}
Event demultiplexing (事件多路复用)
组件从一系列被监听的资源中收集 I/O 事件并放入队列中,而且会一直处于阻塞状态直到有新的事件可以被处理
wachedList.add(socketA, FOR_READ);
wachedList.add(pipeB, FOR_READ);
while(events = demultiplexer.watch(wachedList)) {
// 事件循环
foreach(event in events) {
// 永远不会阻塞,并且总会有返回值
data = event.resource.read();
if (data === RESOURCE_CLOSED) {
// 资源已经被释放,从观察者队列移除
demultiplexer.unwatch(event.resource);
} else {
// 获得数据进行处理
consumeData(data);
}
}
}
事件多路复用的三个步骤:
- 资源被添加到一个数据结构中,为每个资源关联一个特定的操作,在这个例子中是 read
- 事件通知器由一组被观察的资源组成,一旦事件即将触发,会调用同步的 watch 函数,并返回这个可被处理的事件
- 最后,处理事件多路复用器返回的每个事件,此时,与系统资源相关联的事件将被读并且在整个操作中都是非阻塞的,直到所有事件都被处理完时,事件多路复用器会再次阻塞,然后重复这个步骤,这就是 event loop
reactor 模式
主要思想就是每一个 I/O 操作都有一个 handler 或者称为回调函数(callback),当事件发生并且被事件循环处理后,这个回调函数就会被调用。
- 应用提交一个请求给事件多路复用器,生成 I/O 操作,同时提供事件触发时的 handler,发送请求给事件多路复用器是一个非阻塞的操作,发送后立即返回到应用
- 当一组 I/O 操作完成后,事件多路复用器会将新来的事件添加到事件队列中
- 此时,事件循环会迭代事件队列中的每个事件
- 对于每个事件,对应的 handler 被处理
- handler,是应用程序代码的一部分,handler 执行结束后执行权会交回事件循环,但是,在 handler 执行结束时可能请求新的异步操作,从而新的操作被添加到事件多路复用器
- 当事件队列的全部事件被处理完后,事件多路复用器再次阻塞直到有一个新的事件触发
Node.js 非阻塞 I/O 引擎 —- libuv
使用 libuv 来统一处理 I/O 操作,来达到兼容不同操作系统的目的