你需要了解的有关 Node.js 的所有信息

发表于 3年以前  | 总阅读数:240 次

Node.js 是当前用来构建可扩展的、高效的 REST API's 的最流行的技术之一。它还可以用来构建混合移动应用、桌面应用甚至用于物联网领域。

我真的很喜欢它,我已经使用 Node.js 工作了 6 年。这篇文章试图成为了解 Node.js 工作原理的终极指南。

Node.js 之前的世界

多线程服务器

Web 应用程序是用一个 client/server(客户端/服务器)模式所编写的,其中 client 将向 server 请求资源并且 server 将会根据这个资源以响应。server 仅在 client 请求时做出响应,并在每次响应后关闭连接。

这种模式是有效的,因为对服务器的每一个请求都需要时间和资源(内存、CPU 等)。服务器必须完成上一个请求,才能接受下一个请求。

所以,服务器在一定的时间内只处理一个请求?这不完全是,当服务器收到一个新请求时,这个请求将会被一个线程处理。

简而言之,线程是 CPU 为执行一小段指令所花费的时间和资源。话虽如此,服务器一次要处理多个请求,每个线程一个(也可以称为 thread-per-request 模式)。

注:thread-per-request 意为每一个请求一个线程

要同时处理 N 个请求,服务器就需要 N 个线程。如果现在有 N+1 个请求,它就必须等待,直到 N 个线程中的任何一个可用。

在多线程服务器示例中,服务器同时最多允许 4 个请求(线程)当接下来收到 3 个请求时,这些请求必须等待直到这 4 个线程中的任何一个可用。

解决此限制的一种方法是向服务器添加更多资源(内存,CPU内核等),但这可能根本不是一个好主意...

当然,会有技术限制。

阻塞 I/O

服务器中的线程数不仅仅是这里唯一的问题。也许你想知道为什么一个线程不能同时处理 2 个或更多的请求?这是因为阻塞了 Input/Output 操作。

假设你正在开发一个在线商店应用,并且它需要一个页面,用户可以在其中查看您的所有产品。

用户访问 http://yourstore.com/products 服务器将从数据库中获取你的全部产品来呈现一个 HTML 文件,这很简单吧?

但是,后面会发生什么?...

  • 1. 当用户访问 /products 时,需要执行特定的方法或函数来满足请求,因此会有一小段代码来解析这个请求的 url 并定位到正确的方法或函数。线程正在工作。✔️
  • 2. 该方法或函数以及第一行将被执行。线程正在工作。✔️
  • 3. 因为你是一名优秀的开发者,你会保存所有的系统日志在一个文件中,要确保路由执行了正确的方法/函数,你的日志要增加一个字符串 “Method X executing!!”(某某方法正在执行),这是一个阻塞的 I/O 操作。线程正在等待。❌
  • 4. 日志已被保存并且下一行将被执行。线程正在工作。✔️
  • 5. 现在是时候去数据库并获取所有产品了,一个简单的查询,例如 SELECT * FROM products 操作,但是您猜怎么着?这是一个阻塞的 I/O 操作。线程正在等待。❌
  • 6. 你会得到一个所有的产品列表,但要确保将它们记录下来。线程正在等待。❌
  • 7. 使用这些产品,是时候渲染模版了,但是在渲染它之前,你应该先读取它。线程正在等待。❌
  • 8. 模版引擎完成它的工作,并将响应发送到客户端。线程再次开始工作。✔️
  • 9. 线程是自由的(空闲的),像鸟儿一样。️

I/O 操作有多慢?这得需要看情况。

让我们检查以下表格:

操作 CPU 时钟周期数
CPU 寄存器 3 ticks
L1 Cache(一级缓存) 8 ticks
L2 Cache(二级缓存) 12 ticks
RAM(随机存取存储器) 150 ticks
Disk(磁盘) 30,000,000 ticks
Network(网络) 250,000,000 ticks

译者备注:时钟周期也称(tick、clock cycle、clock period 等),指一个硬件在被使用过程中,被划分为多个时间周期,当我们需要比较不同硬件的性能时,就在不同硬件之上测试同一个软件,观察它们的时钟周期时间和周期数,如果时钟周期时间越长、周期数越多,就意味着这个硬件需要的性能较低。

磁盘和网络操作太慢了。您的系统进行了多少次查询或外部 API 调用?

在恢复过程中,I/O 操作使得线程等待且浪费资源。

C10K 问题

早在 2000 年代初期,服务器和客户端机器运行缓慢。这个问题是在一台服务器机器上同时运行 10,000 个客户端链接。

为什么我们传统的 “thread-per-request” 模式不能够解决这个问题?现在让我们做一些数学运算。

本地线程实现为每个线程分配大约 1 MB 的内存,所以 10K 线程就需要 10GB 的 RAM,请记住这仅仅是在 2000 年代初期!!

如今,服务器和客户端的计算能力比这更好,几乎任何编程语言和框架都解决了这个问题。实际,该问题已更新为在一台服务器上处理 10 million(1000 万) 个客户端链接(也称 C10M 问题)。

JavaScript 进行救援?

剧透提醒 !!

Node.js 解决了这个 C10K 问题... 但是为什么?

JavaScript 服务端早在 2000 年代并不是什么新鲜事,它基于 “thread-per-request” 模式在 Java 虚拟机之上有一些实现,例如,RingoJS、AppEngineJS。

但是,如果那不能解决 C10K 问题,为什么 Node.js 可以?好吧,因为它是单线程的。

Node.js 和 Event Loop

Node.js

Node.js 是一个构建在 Google Chrome's JavaScript 引擎(V8 引擎)之上的服务端平台,可将 JavaScript 代码编译为机器代码。

Node.js 基于事件驱动、非阻塞 I/O 模型,从而使其轻巧和高效。它不是一个框架,也不是一个库,它是一个运行时。

一个简单的例子:

// Importing native http module
const http = require('http');

// Creating a server instance where every call
// the message 'Hello World' is responded to the client
const server = http.createServer(function(request, response) {
  response.write('Hello World');
  response.end();
});

// Listening port 8080
server.listen(8080);

非阻塞 I/O

Node.js 是非阻塞 I/O,这意味着:

  • 主线程不会在 I/O 操作中阻塞。
  • 服务器将会继续参加请求。
  • 我们将使用异步代码。

让我们写一个例子,在每一次 /home 请求时,服务器将响应一个 HTML 页面,否则服务器响应一个 'Hello World' 文本。要响应 HTML 页面,首先要读取这个文件。

home.html

<html>
  <body>
    <h1>This is home page</h1>
  </body>
</html>

index.js

const http = require('http');
const fs = require('fs');

const server = http.createServer(function(request, response) {
  if (request.url === '/home') {
    fs.readFile(`${ __dirname }/home.html`, function (err, content) {
      if (!err) {
        response.setHeader('Content-Type', 'text/html');
        response.write(content);
      } else {
        response.statusCode = 500;
        response.write('An error has ocurred');
      }

      response.end();
    });
  } else {
    response.write('Hello World');
    response.end();
  }
});

server.listen(8080);

如果这个请求的 url 是 /home,我们使用 fs 本地模块读取这个 home.html 文件。

传递给 http.createServer 和 fs.readFile 的函数称为回调。这些功能将在将来的某个时间执行(第一个功能将在收到一个请求时执行,第二个功能将在文件读取并且缓冲之后执行)。

在读取文件时,Node.js 仍然可以处理请求,甚至再次读取文件,all at once in a single thread... but how?!

The Event Loop(事件循环)

事件循环是 Node.js 背后的魔力,简而言之,事件循环实际上是一个无限循环,并且是线程里唯一可用的。

Libuv 是一个实现此模式的 C 语言库,是 Node.js 核心模块的一部分。阅读关于 Libuv 的更多内容 here。

事件循环需要经历 6 个阶段,所有阶段的执行被称为 tick。

  • timers:这个阶段执行定时器 setTimeout() 和 setInterval() 的回调函数。
  • pending callbacks:几乎所有的回调在这里执行,除了 close 回调、定时器 timers 阶段的回调和 setImmediate()。
  • idle, prepare: 仅在内部应用。
  • poll:检索新的 I/O 事件;适当时 Node 将在此处阻塞。
  • check:setImmediate() 回调函数将在这里执行。
  • close callbacks: 一些准备关闭的回调函数,如:socket.on('close', ...)。

好的,所以只有一个线程并且该线程是一个 EventLoop,但是 I/O 操作由谁来执行呢?

注意 !!!

当 Event Loop 需要执行 I/O 操作时,它将从一个池(通过 Libuv 库)中使用系统线程,当这个作业完成时,回调将排队等待在 “pending callbacks” 阶段被执行。

那不是很完美吗?

CPU 密集型任务问题

Node.js 似乎很完美,你可以用它来构建任何你想要的东西。

让我们构建一个 API 来计算质数。

质数又称素数。一个大于 1 的自然数,除了 1 和它自身外,不能被其他自然数整除的数叫做质数;

给一个数 N,这个 API 必须计算并在一个数组中返回 N 个自然数。

primes.js

function isPrime(n) {
  for(let i = 2, s = Math.sqrt(n); i <= s; i++)
    if(n % i === 0) return false;
  return n > 1;
}

function nthPrime(n) {
  let counter = n;
  let iterator = 2;
  let result = [];

  while(counter > 0) {
    isPrime(iterator) && result.push(iterator) && counter--;
    iterator++;
  }

  return result;
}

module.exports = { isPrime, nthPrime };

index.js

const http = require('http');
const url = require('url');
const primes = require('./primes');

const server = http.createServer(function (request, response) {
  const { pathname, query } = url.parse(request.url, true);

  if (pathname === '/primes') {
    const result = primes.nthPrime(query.n || 0);
    response.setHeader('Content-Type', 'application/json');
    response.write(JSON.stringify(result));
    response.end();
  } else {
    response.statusCode = 404;
    response.write('Not Found');
    response.end();
  }
});

server.listen(8080);

primes.js 是质数功能实现,isPrime 检查给予的参数 N 是否为质数,如果是一个质数 nthPrime 将返回 n 个质数

index.js 创建一个服务并在每次请求 /primes 时使用这个库。通过 query 传递参数。

获取 20 前的质数,我们发起一个请求 http://localhost:8080/primes?n=2

假设有 3 个客户端访问这个惊人的非阻塞 API:

  • 第一个每秒请求前 5 个质数。
  • 第二个每秒请求前 1,000 个质数
  • 第三个请求一次性输入前 10,000,000,000 个质数,但是...

当我们的第三个客户端发送请求时,客户端将会被阻塞,因为质数库会占用大量的 CPU。主线程忙于执行密集型的代码将无法做其它任何事情。

但是 Libuv 呢?如果你记得这个库使用系统线程帮助 Node.js 做一些 I/O 操作以避免主线程阻塞,那你是对的,这个可以帮助我们解决这个问题,但是使用 Libuv 库我们必须要使用 C++ 语言编写。

值得庆祝的是 Node.js v10.5 引入了工作线程。

工作线程

如文档所述:

工作线程对于执行 CPU 密集型的 JavaScript 操作非常有用。它们在 I/O 密集型的工作中用途不大。Node.js 的内置的异步 I/O 操作比工作线程效率更高。

修改代码

现在修复我们的初始化代码:

primes-workerthreads.js

const { workerData, parentPort } = require('worker_threads');

function isPrime(n) {
  for(let i = 2, s = Math.sqrt(n); i <= s; i++)
    if(n % i === 0) return false;
  return n > 1;
}

function nthPrime(n) {
  let counter = n;
  let iterator = 2;
  let result = [];

  while(counter > 0) {
    isPrime(iterator) && result.push(iterator) && counter--;
    iterator++;
  }

  return result;
}

parentPort.postMessage(nthPrime(workerData.n));

index-workerthreads.js

const http = require('http');
const url = require('url');
const { Worker } = require('worker_threads');

const server = http.createServer(function (request, response) {
  const { pathname, query } = url.parse(request.url, true);

  if (pathname === '/primes') {
    const worker = new Worker('./primes-workerthreads.js', { workerData: { n: query.n || 0 } });

    worker.on('error', function () {
      response.statusCode = 500;
      response.write('Oops there was an error...');
      response.end();
    });

    let result;
    worker.on('message', function (message) {
      result = message;
    });

    worker.on('exit', function () {
      response.setHeader('Content-Type', 'application/json');
      response.write(JSON.stringify(result));
      response.end();
    });
  } else {
    response.statusCode = 404;
    response.write('Not Found');
    response.end();
  }
});

server.listen(8080);

index-workerthreads.js 在每个请求中将创建一个 Worker 实例,在一个工作线程中加载并执行 primes-workerthreads.js 文件。当这个质数列表计算完成,这个 message 消息将会被触发,接收信息并赋值给 result。由于这个 job 已完成,将会再次触发 exit 事件,允许主线程发送数据给到客户端。

primes-workerthreads.js 变化小一点。它导入 workerData(从主线程传递参数),parentPort 这是我们向主线程发送消息的方式。

现在让我们再次做 3 个客户端例子,看看会发生什么:

主线程不再阻塞 !!!!!

它的工作方式与预期的一样,但是生成工作线程并不是最佳实践,创建新线程并不便宜。一定先创建一个线程池。

结论

Node.js 是一项功能强大的技术,值得学习。

我的建议总是很好奇,如果您知道事情的进展,您将做出更好的决定。

伙计们,到此为止。希望您对 Node.js 有所了解。

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/JooBfQ7bZ6QT2uwRU9SB4w

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录