node获取系统时间_Node.js是怎么工作的?Node.js为什么效率高?

53f6732351d1a4e2af2f50928334ad31.png

官方定义摘要概述

  1. Node.js是在 Chrome的V8引擎上构建JavaScript runtime代码的
  2. Node.js使用是的事件驱动、非阻塞I/O模型,使其轻量级和高效。
  3. 有十分丰富的包,也就是生态圈十分好。

我们先介绍第三点,也就是Node.js有什么特性

异步特性

Node.js比较特别的是它的异步性质,它大量使用的异步构造器,例如callback和promise。针对这个特性,我们接下来会详细的描述一下,以便大家了解Node.js一些工作原理。

内核小

Node.js的核心可以被理解为Node.js运行时和内置模块。他遵循的设计原则是拥有尽可能小的功能集,而将其余的功能留给用户去实现,也就是核心以外的模块。这使得Node.js的生态圈十分丰富,而且生态圈发展得十分快。

模块小

Node.js使用模块的概念作为构建程序代码的基本方法。它是创建应用程序和在程序中加入各种库的基础。在Node.js中,其中一个原则是尽量比较小的模块,而且要考虑代码的大小和作用范围。


然后给大家介绍一下非阻塞I/O模型

  • I/O是很慢的

I/O,就是input和output的缩写。访问RAM,也就是内存,它是以纳秒(10E-9秒)为单位,而访问磁盘或网络上的数据则是以毫秒(10E-3秒)为单位。RAM的传输速率始终保持在GB/s的数量级,而磁盘或网络的传输速率从MB/s到GB/s不等,而且GB/s是比较乐观的情况。相当于CPU而言,I/O通常并不贵,但它增加了请求被发送到设备中间的一系列操作之间的延迟。

  • 阻塞性I/O

在传统的阻塞I/O编程中,I/O的请求调用将会阻塞线程的执行,直到操作完成。对于磁盘访问,这可能是几毫秒,对于从用户操作到生成的数据,这可能是几分钟甚至更多。

使用阻塞I/O实现的web服务器将无法在同一线程中处理多个连接。这是因为套接字上的每次I/O操作都会阻塞任何其他连接的处理。解决这个问题的传统方法是使用单独的线程(或进程)来处理每个并发连接。

这样,在I/O操作上阻塞的线程不会影响其他连接的可用性,因为它们是在单独的线程中处理的。

1823f6aca16e5d6ce02366fc7dd513aa.png
阻塞性I/O展示图

图中我们可以看到,多个连接进来我们的服务器,然后服务器开多个线程来处理请求。其实很多时候,线程都是处于空闲,因为要等待对应的连接把数据成功获取。假设我们每一种I/O类型都有可能阻塞到请求,其实大多I/O都是会阻塞的,例如从数据库或者文件系统获取数据,我们会发现,一个线程必须要阻塞等待,主要是为了等待I/O完成操作拿到操作数据以后再进行。因为线程在系统资源消耗会十分大,主要体现在内存消耗和上下文切换,因此为每个连接使用一个长时间运行的线程并在大多数时间不使用它意味着浪费宝贵的内存和CPU周期。图中,灰色的代表空闲时间,就有可能是等待数据各种I/O中获取。

  • 非阻塞性I/O

除了阻塞性I/O,现在大多数操作系统都是支持另外一种获得资源的方式,也就是我们要介绍的非阻塞性I/O。在这种操作模式下,系统调用会立即返回,而无需等待数据的读取或写入完成。如果调用没有任何结果,将会返回一个预设的常量,然后把执行让给别的线程。相当于我经常去问一下你,你获取到数据没有,如果获取到,我就去帮你处理。举个简单的例子,我同时安排3个人帮我去写3篇文章,我需要每次轮流去问这3个人有没有完成,如果我问到其中一个说完成了,我就拿完成得文章去打印。

resources = [socketA, socketB, fileA] while (!resources.isEmpty()) {
for (resource of resources) {
// 尝试读取数据
data = resource.read()
if (data === NO_DATA_AVAILABLE) {
      // 如没有数据读到,就会进入这里
continue
}
if (data === RESOURCE_CLOSED) {
      // 如果是读完,出现关闭资源状态,就把socket从列表中删掉
resources.remove(i) } else {
      // 读到数据,去处理
      consumeData(data)
    }
} }

上面的模拟代码,可以在同一个线程中处理不同的资源,但是它还不是很高效。实际上,在例子中是使用迭代的循环的方式去不断轮询读取,跟我举的例子有点相似,不断地去询问,这样会消耗宝贵的CPU资源。轮询算法通常会浪费大量的CPU时间。

  • 事件多路复用

繁忙的去轮询,我们可以看不到不是一种比较优秀的方式去处理非阻塞资源。其实现在大多数操作系统,都有相关的机制高效去处理非阻塞资源。我们接下来介绍一种基于事件通知,同步阻塞多路复用的方式去处理非阻塞资源的。

如果你不是很了解多路复用,可以想象一下电信号传输,他可以通过多个信号进行传输,然后通过复用技术,把这些信号组合回来。

这里有个事件的说法,可以这样理解,就是我要获取多个资源,如果哪个资源获取完成,就通过事件的形式通知一下我。拿我们写文章打印的例子来讲,就是我安排3个人去写文章,安排完我继续做我自己的事情,谁完成了,就通知一下我,我再拿去打印。可以看下下面的伪代码。

同步阻塞,就限定你这三个人是有顺序的。例如是分别写第一章,第二章,第三章。

watchedList.add(socketA, FOR_READ)                            // (1)
watchedList.add(fileB, FOR_READ)
while (events = demultiplexer.watch(watchedList)) {           // (2)
  // 事件循环
  for (event of events) {                                     // (3)
    data = event.resource.read()
    if (data === RESOURCE_CLOSED) {
      demultiplexer.unwatch(event.resource)
    } else {
      consumeData(data)
    }
  }
}
  1. 把我们想要的资源放到watchedList中,然后再事件中,可以指定对应的操作,如代码中的read操作。
  2. 监听整组资源,一直监听,直到所有资源读取完毕。
  3. 处理每个事件,这时,每个事件的读取操作是不阻塞的,读完就返回,demultiplexer就知道读完了。

39b9e7d5d56df8acbba6d1fd4a3e0085.png
事件同步阻塞多路复用展示图

图中展示了整个过程,多个连接到服务器,在单个线程上按照顺序去分配3个数据的处理。每个数据的处理是非阻塞的。

其实这种操作模型,并不是选择使用单线程唯一的原因。很时候,多线程需要考虑数据竞争的问题,这会涉及锁之类的。单线程就可能很好的处理这种问题。日后文章再介绍。

  • Node.js的reactor模式

Node.js采用的也是事件多路复用技术,具体是通过reactor模式来体现。通过我们callback函数的方式来进行事件回调通知。

f7a14df19acde65e84a478159051330a.png
Node.js的reactor模式展示图

图中展示了整个reactor模式的运作过程。

  1. 应用,也可以说是用户操作应用,产生I/O请求,然后把I/O放到Event Demultiplexer,这里会给每个I/O指定一个handler,也就是我的应用读到数据以后要怎么处理这次I/O。Operation是代表我用什么方式去操作这次I/O,如read,write。
  2. 当这些I/O都完成了操作,Event Demultiplexer就会把这些I/O对应的事件发送到Event Queue中。关于Event Queue和Event Loop,日后文章再详细介绍。
  3. 此时,Event Loop遍历Event Queue的事件,然后对应的handler会被调用。执行完成,将会把控制权交回给Event Loop。如果handler中又出现异步操作,那就会再从第一步开始,把异步操作交给Event Demultiplexer。
  4. Event Queue的事件都执行完,然后Event Loop进入阻塞状态,等待下一次Event Demultiplexer的操作。
  • Node.js的I/O引擎,Libuv
  1. 每个操作系统都有自己的Event Demultiplexer接口。
  2. Linux用的是epoll, macOS用的是kqueue,以及Windows上的I/O completion port(IOCP) 。
  3. 每种操作系统用各自用的模式,根据资源类型的不同,每个I/O操作的行为都有可能存在很大的差异,即使在相同的操作系统中也是。例如,在Unix操作系统中,常规文件系统文件不支持非阻塞操作,因此为了模拟非阻塞行为,有必要在事件循环之外使用单独的线程。
  4. 所有这些跨操作系统和不同操作系统内部的不一致性都需要为Event Demultiplexer构建一个底层抽象系统。这也正是Node.js核心团队创建一个名为libuv的本地库的原因,目的是使Node.js与所有主流操作系统兼容,并规范不同类型资源的非阻塞行为。Libuv代表Node.js的底层I/O引擎,是构建Node.js的最重要的组件。
  5. 除了底层抽象系统之外,libuv还实现了reactor模式,从而提供了用于创建事件循环、管理事件队列、运行异步I/O操作以及对其他类型任务进行排队的API。

写下来发现文章很长,第三点放下部分继续讲

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值