开发理念

小核心

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 (完美支持浏览器)

DRY(Don’t Repeat YourSelf) 原则

small surface area(暴露需要的接口)

定义 Node 模块时,只暴露一个函数或构造器,不允许扩展或扩展性低,这样子可以减少应用场景,简化实现,便于维护,提高可用性。

simplicity and progmatism(简单实用)

设计必须简单,不管是实现还是接口。实现的简洁比接口的简洁更重要,做设计时最需要考虑的因素就是简单程度

好处:

  • 实现起来更容易
  • 所需资源更少,传输更快
  • 用户更容易适应
  • 更容易维护和理解

    工作原理

    I/O is slow (I/O 是耗时操作)

    Blocking I/O (阻塞I/O)

    I/O 请求后会阻塞之后的代码运行
    1. // 直到请求完成,数据可用,线程都是阻塞的
    2. data = socket.read()
    3. // 请求完成,数据可用
    4. print(data)
    所以需要开多进程或多线程各自操作,但这是耗内存的行为

    Non-blocking I/O (非阻塞 I/O)

    实用轮询算法(polling algorithm)实现,会大量消耗 CPU 时间
    1. resources = [socketA, socketB, pipeA]
    2. while (!resources.isEmpty()) {
    3. for (i = 0; i < resources.length; i++) {
    4. resource = resources[i]
    5. // 进行读操作
    6. let data = resource.read()
    7. if (data === NO_DATA_AVAILABLE) {
    8. // 此时还没有数据
    9. continue
    10. }
    11. if (data === RESOURCE_CLOSED) {
    12. // 资源被释放,从队列中移除该链接
    13. resources.remove(i)
    14. } else {
    15. consumeData(data)
    16. }
    17. }
    18. }

    Event demultiplexing (事件多路复用)

    组件从一系列被监听的资源中收集 I/O 事件并放入队列中,而且会一直处于阻塞状态直到有新的事件可以被处理

  1. wachedList.add(socketA, FOR_READ);
  2. wachedList.add(pipeB, FOR_READ);
  3. while(events = demultiplexer.watch(wachedList)) {
  4. // 事件循环
  5. foreach(event in events) {
  6. // 永远不会阻塞,并且总会有返回值
  7. data = event.resource.read();
  8. if (data === RESOURCE_CLOSED) {
  9. // 资源已经被释放,从观察者队列移除
  10. demultiplexer.unwatch(event.resource);
  11. } else {
  12. // 获得数据进行处理
  13. consumeData(data);
  14. }
  15. }
  16. }

事件多路复用的三个步骤:

  1. 资源被添加到一个数据结构中,为每个资源关联一个特定的操作,在这个例子中是 read
  2. 事件通知器由一组被观察的资源组成,一旦事件即将触发,会调用同步的 watch 函数,并返回这个可被处理的事件
  3. 最后,处理事件多路复用器返回的每个事件,此时,与系统资源相关联的事件将被读并且在整个操作中都是非阻塞的,直到所有事件都被处理完时,事件多路复用器会再次阻塞,然后重复这个步骤,这就是 event loop

image.png

reactor 模式

主要思想就是每一个 I/O 操作都有一个 handler 或者称为回调函数(callback),当事件发生并且被事件循环处理后,这个回调函数就会被调用。

image.png

  1. 应用提交一个请求给事件多路复用器,生成 I/O 操作,同时提供事件触发时的 handler,发送请求给事件多路复用器是一个非阻塞的操作,发送后立即返回到应用
  2. 当一组 I/O 操作完成后,事件多路复用器会将新来的事件添加到事件队列中
  3. 此时,事件循环会迭代事件队列中的每个事件
  4. 对于每个事件,对应的 handler 被处理
  5. handler,是应用程序代码的一部分,handler 执行结束后执行权会交回事件循环,但是,在 handler 执行结束时可能请求新的异步操作,从而新的操作被添加到事件多路复用器
  6. 当事件队列的全部事件被处理完后,事件多路复用器再次阻塞直到有一个新的事件触发

    Node.js 非阻塞 I/O 引擎 —- libuv

    使用 libuv 来统一处理 I/O 操作,来达到兼容不同操作系统的目的

Node.js 的全套结构

image.png

  • 一套绑定机制,以封装 libuv 及其他底层功能,令其可以与 JS 对接
  • V8 引擎
  • 核心 JS 库,用以实现高层 Node.js API

    参考资料

  1. 读书笔记
  2. 读书笔记