前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入浅出NodeJS随记 (一)

深入浅出NodeJS随记 (一)

原创
作者头像
邱邱邱邱yf
修改2021-12-10 16:39:55
5800
修改2021-12-10 16:39:55
举报

yiuanli最近在研读书籍 深入浅出nodejs , 随手写下的一些笔记, 和大家分享~ 如有错误,欢迎指正~

  • Node简介 特点:事件驱动(在浏览器端广泛成熟的机制), 异步I/O 基于Chrome的V8,保持了单线程的特点(好处是不需要考虑状态同步,线程锁之类的问题, 问题是无法利用多核CPU, 异常报错健壮性待考验,大量计算任务可能影响到异步I/O)推出了child_process来解决。 跨平台,依赖于libuv(一个高性能的,事件驱动的异步I/O库,它本身是由C语言编写的,具有很高的可移植性)
  • Node模块机制(CommonJS模块规范, 编译加载机制, 循环引用问题) js天然缺乏(es6才推出了import/export的模块机制) 引用 require()函数 定义 挂载到module对象上的exports属性 标识 简单来说就是require()函数的参数 引入模块的三个步骤
    1. 路径分析
      1. 核心模块:速度最快,随着node源代码编译已编译为二进制,速度仅次于缓存
      2. 路径文件:会转换为真实路径,并且作为索引放入缓存,略慢于核心模块
      3. 自定义:npm安装(module.paths数组输出 从当前文件目录下的node_modules一直到根目录的node_modules,链式的)越深速度最慢
    2. 文件定位
      1. 扩展名分析, 不含扩展名则依次 .js, .json, .node补足。过程中fs同步阻塞判断文件是否存在,建议带上扩展名。
      2. 目录分析和包 通过JSON.parse()读取package.json取出main属性进行定位(缺少扩展名就同上),如果没有p.json文件或没有main属性,默认使用index。如果失败链式向上查询。
    3. 编译执行
      1. 首先确定扩展名,fs同步读取文件然后根据扩展名调用方式不同
        1. js模块的编译: 读取文件内容,然后在头尾进行包装(function(exports, requie, module, _fileame, _dirname){ /** jscode */ }) 作用域隔离, 然后各种信息作为参数传递给函数执行
        2. c/c++ 模块编译: 不需要编译, node直接调用process.dlopen() 方法加载执行。执行效率很高
        3. json: fs同步读取, 然后JSON.parse()得到对象, 赋值给exports、

      注意:Node对引入过的模块都会进行缓存以路径为索引(Module._cache对象),就是编译执行以后的对象(缓存优先,核心模块先于文件模块)

    前端模块规范: AMD(define显式定义模块, node是隐式包装的。声明时指定所有依赖, 形参传入), CMD(require函数作为参数,需要使用时require动态引入)

  • 异步I/O
    1. 阻塞与非阻塞
      1. 操作系统内核对于I/O只有阻塞与非阻塞: (操作系统将所有输入输出设备抽象为文件, 内核在进行文件I/O时,通过文件描述符进行管理。 应用程序需要进行I/O调用需要先打开文件描述符,然后再去读写。阻塞与非阻塞的区别在于是否完成整个获取数据的过程,非阻塞直接不带数据返回,获取数据需要通过文件描述符再次获取)
      2. 非阻塞返回后,CPU时间片可以处理其他事物。 问题是: 由于I/O没有完成, 需要反复调用I/O(其实就是轮询)来确认是否完成了。
      3. 阻塞造成CPU等待浪费, 非阻塞轮询浪费
      4. 轮询方式:
        1. read 最原始,反复调用检查i/o状态,性能最低
        2. select read的改进,通过对文件描述符事件上的事件状态来判断 成功后再read,用数组储存状态,最多同时1024个文件描述符
        3. poll select的改进,用链表代替数组
        4. epoll Linux下效率最高的I/O事件通知机制,会进行休眠,知道事件唤醒。利用事件通知机制。
        5. select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
        6. 本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
      5. 多进程带来的可能性
    2. Node的异步I/O
      1. Node 单线程仅仅只是js执行在单线程,内部完成I/O任务还是另有线程池的。
      2. 事件循环
        1. 一个大循环,每次循环体为一个tick,查看是否有事件需要处理
      3. 观察者
        1. 每次Tick如何判断是否有事件需要处理:每个事件循环有一个或者多个观察者,判断就是询问观察者。(类似于eventListener, 有文件I/O观察者,网络I/O观察者)
        2. 事件循环是典型的生产者/消费者模型。 异步I/O网络请求是事件的生产者,传到对应的观察者那里。事件循环去观察者那里询问。
      4. 请求对象
        1. 组装请求对象送入线程池
          1. 从js发起调用到内核完成I/O操作的过渡过程中,存在一个中间产物叫请求对象
          2. 请求对象是异步I/O过程中的重要中间产物,所有状态都保存在这个对象上。包括送入线程池等待执行以及I/O操作完毕以后的回调处理(在oncomplete_sym属性上)
          3. js->Node核心模块->C++内建模块->libuv进行系统调用 至此js调用立即返回,js线程可以继续执行任务。当前I/O操作在线程池等待执行,无论是否阻塞I/O都不会影响js执行,达到异步目的
        2. 回调通知是第二部分
          1. 线程池异步I/O调用完毕会将结果放在req.result属性上, 然后通知IOCP,告知当前对象操作完成(提交执行状态并将线程归还线程池,状态然后能被 其他函数:就是下一步里Tick检查线程池的方法,获取到)
          2. 其中还动用了事件循环的I/O观察者,每次Tick他会调用方法检查线程池是否有执行完的请求,存在就会把请求对象加入到I/O观察者的队列,然后将其当做事件处理。
          3. I/O观察者回调函数的行为就是去取请求对象的result作为参数,取出oncomplete_sym属性作为方法,调用执行。
      5. 事件循环,观察者,请求对象,I/O线程池构成了Node异步I/O的的基本要素
    3. 非I/O的异步API
      1. 定时器
        1. setTImeout或者setInterval创建的定时器会被插入到定时器观察者内部的一个红黑树。每次Tick执行时会从红黑树中迭代取出定时器对象,检查是否超过定时时间,超过了就会形成事件,调用回调函数。
        2. 定时器的问题在于,他并非精确的(一般应该在容忍范围内)。尽管事件循环十分快,但是如果某一次事件循环占用时间较多,那么可能会影响较大。

  1. 事件驱动
    1. 事件驱动的实质,即通过主循环加事件触发来运行程序
    2. 实质上异步I/O不仅仅运用在文件操作。对于网络套接字,Node也应用到了异步I/O,网络套接字上侦听到的请求都会形成时间交给I/O观察者。事件循环会不断的处理这些网络I/O请求。利用Node构建web服务器也是基于此:
      1. 操作系统内核监听端口,接收网络请求
      2. 将事件放入 I/O 观察者队列中
      3. libuv 中事件循环,询问 I/O 观察者是否有事件
      4. 发现有事件则执行,并且再查看是否有回调函数
      5. 有回调函数则执行,并将控制器转移会 JavaScript 中

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档