为什么我会使用 Node.js? 个案教程

已发表: 2022-03-11

介绍

JavaScript 的日益流行带来了很多变化,如今 Web 开发的面貌已经大不相同。 如今,我们可以通过在服务器和浏览器中运行 JavaScript 在 Web 上做的事情,在几年前是难以想象的,或者被封装在 Flash 或 Java Applets 等沙盒环境中。

在深入研究 Node.js 解决方案之前,您可能想了解在堆栈中使用 JavaScript 的好处,它统一了语言和数据格式 (JSON),允许您以最佳方式重用开发人员资源。 由于这是 JavaScript 比 Node.js 更具体的好处,所以我们不会在这里讨论太多。 但这是将 Node 整合到您的堆栈中的一个关键优势。

正如维基百科所说:“Node.js 是 Google 的 V8 JavaScript 引擎、libuv 平台抽象层和一个核心库的打包编译,它本身主要是用 JavaScript 编写的。” 除此之外,值得注意的是,Node.js 的创建者 Ryan Dahl 的目标是创建具有推送功能的实时网站,“受到 Gmail 等应用程序的启发”。 在 Node.js 中,他为开发人员提供了一种在非阻塞、事件驱动的 I/O 范例中工作的工具。

经过 20 多年基于无状态请求-响应范式的无状态 Web,我们终于有了具有实时双向连接的 Web 应用程序。

一句话:Node.js 在使用推送技术而不是 websockets 的实时 Web 应用程序中大放异彩。 这有什么革命性的? 嗯,经过 20 多年基于无状态请求-响应范式的无状态 Web,我们终于有了具有实时双向连接的 Web 应用程序,客户端和服务器都可以发起通信,允许它们自由交换数据. 这与典型的 Web 响应范式形成鲜明对比,后者总是由客户端发起通信。 此外,它全部基于在标准端口 80 上运行的开放式 Web 堆栈(HTML、CSS 和 JS)。

有人可能会争辩说,我们多年来一直以 Flash 和 Java Applet 的形式使用它——但实际上,这些只是使用 Web 作为传输协议交付给客户端的沙盒环境。 另外,它们是独立运行的,并且经常在非标准端口上运行,这可能需要额外的权限等。

凭借其所有优势,Node.js 现在在许多依赖其独特优势的知名公司的技术堆栈中发挥着关键作用。 Node.js 基金会在一个简短的演示文稿中整合了所有关于为什么企业应该考虑 Node.js 的最佳想法,该演示文稿可以在 Node.js 基金会的案例研究页面上找到。

在本 Node.js 指南中,我不仅会讨论这些优势是如何实现的,还会讨论为什么您可能想要使用 Node.js 以及为什么不使用一些经典的 Web 应用程序模型作为示例。

它是如何工作的?

Node.js 的主要思想:使用非阻塞、事件驱动的 I/O 来保持轻量级和高效,以应对跨分布式设备运行的数据密集型实时应用程序。

那是一口。

它真正的意思是 Node.js不是一个将主宰 Web 开发世界的灵丹妙药的新平台。 相反,它是一个满足特定需求的平台。
鸣叫

真正的意思是 Node.js不是一个将主宰 Web 开发世界的灵丹妙药的新平台。 相反,它是一个满足特定需求的平台。 理解这一点是绝对必要的。 您绝对不想将 Node.js 用于 CPU 密集型操作; 事实上,将它用于繁重的计算将消除它几乎所有的优势。 Node 真正闪耀的地方在于构建快速、可扩展的网络应用程序,因为它能够以高吞吐量处理大量同时连接,这等同于高可扩展性。

它是如何在引擎盖下工作的非常有趣。 与每个连接(请求)产生一个新线程、占用系统 RAM 并最终最大化可用 RAM 量的传统 Web 服务技术相比,Node.js 在单线程上运行,使用非阻塞 I/ O 调用,允许它支持事件循环中保持的数万个并发连接。

传统与 Node.js 服务器线程的关系图

快速计算:假设每个线程可能附带 2 MB 内存,在具有 8 GB RAM 的系统上运行,我们理论上最多可以有 4,000 个并发连接(计算取自 Michael Abernethy 的文章“Just what is Node .js?”,于 2011 年在 IBM developerWorks 上发表;不幸的是,这篇文章不再可用) ,加上线程之间的上下文切换成本。 这就是您在传统 Web 服务技术中通常处理的场景。 通过避免所有这些,Node.js 实现了超过 1M 并发连接和超过 600k 并发 websockets 连接的可扩展性水平。

当然,在所有客户端请求之间共享单个线程存在问题,这是编写 Node.js 应用程序的潜在陷阱。 首先,繁重的计算可能会阻塞 Node 的单线程并导致所有客户端出现问题(稍后会详细介绍),因为传入的请求将被阻塞,直到所述计算完成。 其次,开发人员需要非常小心,不要让异常冒泡到核心(最顶层)Node.js 事件循环,这将导致 Node.js 实例终止(实际上使程序崩溃)。

用于避免异常冒泡的技术是将错误作为回调参数传回给调用者(而不是像在其他环境中那样抛出它们)。 即使一些未处理的异常设法冒泡,已经开发了一些工具来监视 Node.js 进程并执行崩溃实例的必要恢复(尽管您可能无法恢复用户会话的当前状态),最常见的是 Forever 模块,或者使用与外部系统工具upstartmonit不同的方法,甚至只是 upstart。

NPM:节点包管理器

在讨论 Node.js 时,绝对不应该忽略的一件事是使用 NPM 对包管理的内置支持,这是每个 Node.js 安装默认附带的工具。 NPM 模块的想法与Ruby Gems的想法非常相似:一组公开可用、可重用的组件,可通过在线存储库轻松安装,并具有版本和依赖关系管理。

可以在 npm 网站上找到打包模块的完整列表,或者使用随 Node.js 自动安装的 npm CLI 工具访问。 模块生态系统对所有人开放,任何人都可以发布自己的模块,这些模块将列在 npm 存储库中。

今天一些最有用的 npm 模块是:

  • express - Express.js - 或简称 Express - 一个受 Sinatra 启发的 Node.js Web 开发框架,也是当今大多数 Node.js 应用程序的事实上的标准。
  • hapi - 一个非常模块化且易于使用的以配置为中心的框架,用于构建 Web 和服务应用程序
  • connect - Connect 是 Node.js 的可扩展 HTTP 服务器框架,提供了一组称为中间件的高性能“插件”; 作为 Express 的基础。
  • socket.iosockjs - 当今两个最常见的 websockets 组件的服务器端组件。
  • pug (以前称为Jade )- 受 HAML 启发的流行模板引擎之一,这是 Express.js 中的默认值。
  • mongodbmongojs - MongoDB 包装器,为 Node.js 中的 MongoDB 对象数据库提供 API。
  • redis - Redis 客户端库。
  • lodash(下划线,lazy.js) - JavaScript 实用工具带。 Underscore 发起了游戏,但被两个对手之一推翻,主要是由于更好的性能和模块化实施。
  • 永远- 可能是确保给定节点脚本连续运行的最常见实用程序。 面对任何意外故障,让您的 Node.js 进程在生产中保持正常运行。
  • bluebird - 功能齐全的 Promises/A+ 实现,性能非常好
  • moment - 用于解析、验证、操作和格式化日期的 JavaScript 日期库。

名单还在继续。 那里有很多非常有用的软件包,所有人都可以使用(对我在这里省略的那些没有冒犯)。

应在何处使用 Node.js 的示例

聊天

聊天是最典型的实时、多用户应用程序。 从 IRC(过去),到在非标准端口上运行的许多专有和开放协议,再到在 Node.js 中实现一切的能力,以及在标准端口 80 上运行的 websocket。

聊天应用程序确实是 Node.js 的最佳示例:它是跨分布式设备运行的轻量级、高流量、数据密集型(但处理/计算量低)应用程序。 它也是一个很好的学习用例,因为它很简单,但它涵盖了您将在典型的 Node.js 应用程序中使用的大多数范例。

让我们尝试描述它是如何工作的。

在最简单的示例中,我们在网站上有一个聊天室,人们可以在其中以一对多(实际上是全部)的方式交换消息。 例如,假设网站上有三个人都连接到我们的留言板。

在服务器端,我们有一个简单的 Express.js 应用程序,它实现了两件事:

  1. 一个GET /请求处理程序,它为包含消息板和“发送”按钮的网页提供服务,以初始化新的消息输入,以及
  2. 一个 websockets 服务器,用于侦听 websocket 客户端发出的新消息。

在客户端,我们有一个 HTML 页面,其中设置了几个处理程序,一个用于“发送”按钮单击事件,它接收输入消息并将其发送到 websocket,另一个用于侦听新的传入消息在 websockets 客户端上(即,其他用户发送的消息,服务器现在希望客户端显示)。

当其中一位客户发布消息时,会发生以下情况:

  1. 浏览器通过 JavaScript 处理程序捕获“发送”按钮单击,从输入字段中获取值(即消息文本),并使用连接到我们服务器的 websocket 客户端(在网页初始化时初始化)发出 websocket 消息。
  2. websocket 连接的服务器端组件接收消息并使用广播方法将其转发给所有其他连接的客户端。
  3. 所有客户端都通过在网页中运行的 websockets 客户端组件将新消息作为推送消息接收。 然后,他们获取消息内容并通过将新消息附加到板上来就地更新网页。

Node.js 应用程序中的客户端和服务器 websocket 示意图

这是最简单的例子。 对于更强大的解决方案,您可以使用基于 Redis 存储的简单缓存。 或者在一个更高级的解决方案中,一个消息队列用于处理将消息路由到客户端和一个更强大的传递机制,它可以覆盖临时连接丢失或在注册客户端离线时为注册客户端存储消息。 但无论您做出何种改进,Node.js 仍将按照相同的基本原则运行:对事件做出反应、处理许多并发连接以及保持用户体验的流畅性。

对象数据库之上的 API

尽管 Node.js 确实在实时应用程序中大放异彩,但它非常适合公开来自对象 DB(例如 MongoDB)的数据。 JSON 存储的数据允许 Node.js 在没有阻抗不匹配和数据转换的情况下运行。

例如,如果您使用 Rails,您可以从 JSON 转换为二进制模型,然后在 Backbone.js、Angular.js 等,甚至是普通的 jQuery AJAX 使用数据时,通过 HTTP 将它们作为 JSON 公开来电。 使用 Node.js,您可以简单地使用 REST API 公开您的 JSON 对象以供客户端使用。 此外,在从数据库读取或写入数据时(如果您使用的是 MongoDB),您无需担心在 JSON 和其他任何内容之间进行转换。 总之,您可以通过在客户端、服务器和数据库中使用统一的数据序列化格式来避免多次转换。

排队输入

如果您收到大量并发数据,您的数据库可能会成为瓶颈。 如上所述,Node.js 可以自己轻松处理并发连接。 但是因为数据库访问是一个阻塞操作(在这种情况下),我们遇到了麻烦。 解决方案是在数据真正写入数据库之前确认客户端的行为。

使用这种方法,系统可以在重负载下保持其响应能力,这在客户端不需要确认数据写入成功时特别有用。 典型的例子包括:用户跟踪数据的记录或写入,分批处理,直到以后才使用; 以及不需要立即反映的操作(例如更新 Facebook 上的“喜欢”计数),最终一致性(在 NoSQL 世界中经常使用)是可以接受的。

数据通过某种缓存或消息队列基础设施(如 RabbitMQ 或 ZeroMQ)进行排队,并由单独的数据库批量写入进程或计算密集型处理后端服务进行消化,这些服务编写在用于此类任务的性能更好的平台中。 类似的行为可以用其他语言/框架实现,但不能在相同的硬件上以相同的高、保持吞吐量来实现。

带有消息队列的 Node.js 中的数据库批量写入图

简而言之:使用 Node,您可以将数据库注销放在一边,稍后再处理它们,就好像它们成功一样继续进行。

数据流

在更传统的 Web 平台中,HTTP 请求和响应被视为孤立事件; 事实上,它们实际上是流。 这一观察可以在 Node.js 中用于构建一些很酷的特性。 例如,可以在文件仍在上传的同时对其进行处理,因为数据是通过流进入的,我们可以以在线方式对其进行处理。 这可以用于实时音频或视频编码,以及不同数据源之间的代理(参见下一节)。

代理

Node.js 很容易用作服务器端代理,它可以以非阻塞方式处理大量同时连接。 它对于代理具有不同响应时间的不同服务或从多个源点收集数据特别有用。

一个例子:考虑一个服务器端应用程序与第三方资源通信,从不同来源提取数据,或将图像和视频等资产存储到第三方云服务。

尽管确实存在专用代理服务器,但如果您的代理基础设施不存在或者您需要本地开发解决方案,则使用 Node 可能会有所帮助。 我的意思是,您可以使用 Node.js 开发服务器构建客户端应用程序,用于资产和代理/存根 API 请求,而在生产中,您将使用专用代理服务(nginx、HAProxy 等)处理此类交互.)。

经纪业务 - 股票交易者的仪表板

让我们回到应用程序级别。 另一个桌面软件占主导地位但可以很容易地被实时网络解决方案取代的例子是经纪人的交易软件,用于跟踪股票价格、执行计算/技术分析以及创建图形/图表。

切换到基于 Web 的实时解决方案将允许经纪人轻松切换工作站或工作场所。 很快,我们可能会开始在佛罗里达的海滩上看到它们……或伊维萨岛……或巴厘岛。

应用程序监控仪表板

Node-with-web-sockets 非常适合的另一个常见用例:跟踪网站访问者并实时可视化他们的交互。

您可以从您的用户那里收集实时统计数据,甚至可以通过在访问者到达您漏斗中的特定点时打开一个沟通渠道来引入与访问者的有针对性的互动,从而将其提升到一个新的水平。 (如果你有兴趣,这个想法已经被 CANDDi 产品化了。)

想象一下,如果您能实时了解访问者在做什么——如果您可以可视化他们的交互,您将如何改善您的业务。 有了 Node.js 的实时双向套接字,现在您可以了。

系统监控仪表板

现在,让我们来看看基础设施方面。 例如,想象一个 SaaS 提供商想要为其用户提供服务监控页面,例如 GitHub 的状态页面。 使用 Node.js 事件循环,我们可以创建一个强大的基于 Web 的仪表板,以异步方式检查服务的状态并使用 websockets 将数据推送到客户端。

使用该技术可以实时实时报告内部(公司内部)和公共服务的状态。 进一步推进这个想法,并尝试想象电信运营商、云/网络/托管提供商或某些金融机构中的网络运营中心 (NOC) 监控应用程序,所有这些都运行在由 Node.js 和 websockets 支持的开放 Web 堆栈上而不是 Java 和/或 Java Applet。

注意:不要尝试在 Node 中构建硬实时系统(即需要一致响应时间的系统)。 对于这类应用程序,Erlang 可能是更好的选择。

可以在哪里使用 Node.js

服务器端 Web 应用程序

带有 Express.js 的 Node.js 也可用于在服务器端创建经典的 Web 应用程序。 然而,尽管可能,Node.js 将携带呈现的 HTML 的这种请求-响应范式并不是最典型的用例。 有支持和反对这种方法的论据。 以下是一些需要考虑的事实:

优点:

  • 如果您的应用程序没有任何 CPU 密集型计算,您可以使用 Javascript 从上到下构建它,如果您使用像 MongoDB 这样的 JSON 存储对象数据库,甚至可以构建到数据库级别。 这大大简化了开发(包括招聘)。
  • 爬虫会收到完全渲染的 HTML 响应,这比在 Node.js 上运行的单页应用程序或 websockets 应用程序对 SEO 更友好。

缺点:

  • 任何 CPU 密集型计算都会阻止 Node.js 的响应能力,因此线程平台是更好的方法。 或者,您可以尝试扩展计算 [*]。
  • 将 Node.js 与关系数据库一起使用仍然很痛苦(有关详细信息,请参见下文)。 如果您尝试执行关系操作,请帮自己一个忙,并选择 Rails、Django 或 ASP.Net MVC 等任何其他环境。
[*] 这些 CPU 密集型计算的替代方法是创建一个高度可扩展的 MQ 支持的环境,该环境具有后端处理,以保持 Node 作为前端的“职员”来异步处理客户端请求。

不应该使用 Node.js 的地方

服务器端 Web 应用程序,背后有一个关系数据库

例如,将 Node.js 与 Express.js 与 Ruby on Rails 进行比较,在访问 PostgreSQL、MySQL 和 Microsoft SQL Server 等关系数据库时,曾经有一个明确的决定支持后者。

Node.js 的关系数据库工具仍处于早期阶段。 另一方面,Rails 自动提供开箱即用的数据访问设置以及 DB 模式迁移支持工具和其他 Gems(双关语)。 Rails 及其对等框架具有成熟且经过验证的 Active Record 或 Data Mapper 数据访问层实现。[*]

但事情发生了变化。 Sequelize、TypeORM 和 Bookshelf 在成为成熟的 ORM 解决方案方面已经走了很长一段路。 如果您希望从 GraphQL 查询生成 SQL,也可能值得查看 Join Monster。

[*] 将 Node 单独用作前端,同时保持 Rails 后端及其对关系数据库的轻松访问是可能的,而且这种情况并不少见。
相关:后端:使用 Gatsby.js 和 Node.js 进行静态站点更新

重型服务器端计算/处理

谈到繁重的计算,Node.js 并不是最好的平台。 不,您绝对不想在 Node.js 中构建斐波那契计算服务器。 一般来说,任何 CPU 密集型操作都会取消 Node 通过其事件驱动的非阻塞 I/O 模型提供的所有吞吐量优势,因为任何传入的请求都会在线程被数字运算占用时被阻塞——假设你正在尝试在您响应请求的同一节点实例中运行您的计算。

如前所述,Node.js 是单线程的,并且只使用一个 CPU 内核。 在多核服务器上添加并发时,Node 核心团队正在以集群模块的形式进行一些工作 [参考:http://nodejs.org/api/cluster.html]。 您还可以通过 nginx 在反向代理后面非常轻松地运行多个 Node.js 服务器实例。

使用集群,您仍然应该将所有繁重的计算卸载到在更合适的环境中编写的后台进程,并让它们通过像 RabbitMQ 这样的消息队列服务器进行通信。

即使您的后台处理最初可能在同一台服务器上运行,这种方法也具有非常高的可扩展性。 这些后台处理服务可以轻松地分发到单独的工作服务器,而无需配置前端 Web 服务器的负载。

当然,您也可以在其他平台上使用相同的方法,但是使用 Node.js,您可以获得我们已经讨论过的高请求/秒吞吐量,因为每个请求都是非常快速有效地处理的小任务。

结论

我们已经从理论到实践讨论了 Node.js,从它的目标和抱负开始,到它的最佳点和陷阱结束。 当人们遇到 Node 问题时,几乎总是归结为阻塞操作是万恶之源——99% 的 Node 滥用都是直接后果。

在 Node 中,阻塞操作是万恶之源——99% 的 Node 滥用都是直接后果。
鸣叫

请记住:Node.js 从未被创建来解决计算扩展问题。 它的创建是为了解决 I/O 扩展问题,它做得非常好。

为什么使用 Node.js? 如果您的用例不包含 CPU 密集型操作或访问任何阻塞资源,您可以利用 Node.js 的优势并享受快速且可扩展的网络应用程序。 欢迎来到实时网络。