什么是 Node.js?

IT技术 javascript node.js v8 evented-io
2021-01-15 14:51:27

我不完全了解Node.js的全部内容。也许是因为我主要是基于 Web 的业务应用程序开发人员。它是什么以及它的用途是什么?

到目前为止,我的理解是:

  1. 编程模型是事件驱动的,尤其是它处理I/O 的方式
  2. 它使用JavaScript,解析器是V8
  3. 它可以很容易地用于创建并发服务器应用程序。

我的理解正确吗?如果是,那么事件化 I/O 的好处是什么,它是否只是为了并发性?另外,Node.js 的发展方向是像基于 JavaScript(基于 V8)的编程模型那样的框架吗?

6个回答

我在工作中使用 Node.js,发现它非常强大。被迫选择一个词来描述 Node.js,我会说“有趣”(这不是一个纯粹的积极形容词)。社区充满活力且不断发展。尽管 JavaScript 很奇怪,但它仍然是一种很好的编码语言。您每天都会重新思考自己对“最佳实践”和结构良好代码模式的理解。现在有大量的想法流入 Node.js,在其中工作会让你接触到所有这些想法 - 伟大的精神举重。

生产中的 Node.js 绝对是可能的,但与文档中似乎Promise的“交钥匙”部署相去甚远。在 Node.js v0.6.x 中,“集群”已集成到平台中,提供了基本构建块之一,但我的“production.js”脚本仍然有大约 150 行逻辑来处理诸如创建日志之类的事情目录、回收死去的工人等。对于“严肃”的生产服务,您还需要准备好限制传入的连接并执行 Apache 为PHP所做的所有事情公平地说,Ruby on Rails确实存在这个问题。它通过两种互补机制解决:1) 将 Ruby on Rails/Node.apache/ Lighttd)。Web 服务器可以有效地提供静态内容、访问日志记录、重写 URL、终止SSL、执行访问规则和管理多个子服务。对于命中实际节点服务的请求,网络服务器会代理该请求。2)使用像Unicorn这样的框架来管理工作进程,定期回收它们等等。我还没有找到一个看起来完全成熟的 Node.js 服务框架;它可能存在,但我还没有找到它,并且仍然在我手工制作的“production.js”中使用了大约 150 行。

阅读像Express这样的框架,似乎标准的做法是通过一个万能的 Node.js 服务来提供一切服务......“app.use(express.static(__dirname + '/public'))” . 对于低负载的服务和开发,这可能没问题。但是,一旦您尝试将大量时间加载到您的服务上并让它 24/7 全天候运行,您就会很快发现推动大型站点拥有完善的、强化的 C 代码(如Nginx位于其站点的前端并处理所有内容的动机)静态内容请求(...直到您设置CDN,如Amazon CloudFront))。对于这个有点幽默和毫不掩饰的负面看法,看看这个人

Node.js 也发现了越来越多的非服务用途。即使您使用其他东西来提供 Web 内容,您仍然可以使用 Node.js 作为构建工具,使用npmmodule来组织您的代码,使用Browserify将其拼接成单个资产,并使用uglify-js将其缩小以进行部署. 对于处理网络,JavaScript 是一种完美的阻抗匹配,并且经常使其成为最简单的攻击途径。例如,如果您想通过一堆JSON响应有效负载进行探索,您应该使用我的underscore-CLImodule,即结构化数据的实用程序带。

优点缺点:

  • 优点:对于服务器人员来说,在后端编写 JavaScript 是学习现代 UI 模式的“门户药物”。我不再害怕编写客户端代码。
  • 优点:倾向于鼓励正确的错误检查(err 由几乎所有回调返回,催促程序员处理它;此外,async.js 和其他库处理“如果这些子任务中的任何一个失败则失败”范例比典型的同步代码要好得多)
  • 优点:一些有趣且通常很困难的任务变得微不足道——比如获取正在执行的任务的状态、工作人员之间的通信或共享缓存状态
  • 优点:基于可靠的包管理器 (npm) 的庞大社区和大量优秀库
  • 缺点:JavaScript 没有标准库。您已经习惯于导入功能,以至于在使用 JSON.parse 或其他不需要添加 npm module的内置方法时感觉很奇怪。这意味着一切都有五个版本。如果您对默认实现不满意,即使 Node.js“核心”中包含的module也有五个变体。这导致了快速演变,但也导致了一定程度的混乱。

与简单的每请求一个进程模型 ( LAMP ):

  • 优点:可扩展到数千个活动连接。非常快速且非常高效。对于网络队列,这可能意味着与 PHP 或 Ruby 相比,所需的盒子数量减少了 10 倍
  • 优点:编写并行模式很容易。想象一下,您需要从Memcached获取三个(或 N 个)blob 在 PHP 中执行此操作...您是否只是编写了获取第一个 blob,然后是第二个,然后是第三个的代码?哇,这很慢。有一个特殊的PECLmodule可以解决 Memcached 的特定问题,但是如果您想与数据库查询并行获取一些 Memcached 数据怎么办?在 Node.js 中,因为范式是异步的,所以让一个 Web 请求并行执行多项操作是很自然的。
  • 缺点:从根本上说,异步代码比同步代码更复杂,如果开发人员没有深入了解并发执行的实际含义,那么前期的学习曲线可能会很困难。尽管如此,它比编写任何类型的带锁定的多线程代码要困难得多。
  • 缺点:例如,如果一个计算密集型请求运行了 100 毫秒,它将停止处理在同一个 Node.js 进程中处理的其他请求......又名,协作多任务这可以通过 Web Workers 模式来缓解(分离出一个子流程来处理昂贵的任务)。或者,您可以使用大量 Node.js 工作人员,并且只让每个工作人员同时处理一个请求(仍然相当有效,因为没有进程回收)。
  • 缺点:运行生产系统比CGI模型(如 Apache + PHP、PerlRuby等)复杂得多。未处理的异常将导致整个过程中断,需要逻辑来重新启动失败的工作程序(请参阅集群)。带有错误本机代码的module可能会导致进程严重崩溃。每当工作人员死亡时,它正在处理的任何请求都会被丢弃,因此一个有缺陷的 API 很容易降低其他共同托管 API 的服务。

与用 Java/C#/C(C?真的?)编写“真正的”服务相比

  • 优点:在 Node.js 中执行异步比在其他任何地方执行线程安全更容易,并且可以说提供了更大的好处。Node.js 是迄今为止我工作过的最不痛苦的异步范式。有了好的库,它只比编写同步代码稍微难一点。
  • 优点:没有多线程/锁定错误。确实,您预先投资编写更详细的代码,以表达没有阻塞操作的正确异步工作流。并且您需要编写一些测试并使其工作(它是一种脚本语言,并且仅在单元测试时才捕获到胖指法变量名称)。但是,一旦你让它工作,heisenbugs 的表面积——在一百万次运行中只出现一次的奇怪问题——表面积要低得多。编写 Node.js 代码的税费在编码阶段很重要。然后你往往会得到稳定的代码。
  • 优点:JavaScript 在表达功能方面要轻得多。很难用文字来证明这一点,但是JSON、动态类型、lambda 符号、原型继承、轻量级module等等……它只是倾向于用更少的代码来表达相同的想法。
  • Con:也许你真的非常喜欢 Java 中的编码服务?

有关 JavaScript 和 Node.js 的另一个观点,请查看从 Java 到 Node.js,这是一篇关于 Java 开发人员学习 Node.js 的印象和经验的博客文章。


module 在考虑 node 时,请记住您选择的 JavaScript 库将定义您的体验。大多数人至少使用两个,一个异步模式助手(Step、Futures、Async)和一个 JavaScript 糖module(Underscore.js)。

助手 / JavaScript 糖:

  • Underscore.js - 使用这个。去做就对了。它使用 _.isString() 和 _.isArray() 之类的东西使您的代码美观且可读。我不太确定你怎么能写出安全的代码。另外,对于增强的命令行功能,请查看我自己的Underscore-CLI

异步模式module:

  • Step - 一种非常优雅的方式来表达串行和并行动作的组合。我个人的推荐。请参阅关于 Step 代码外观的帖子
  • Futures - 更灵活(这真的是一件好事吗?)通过需求表达订购的方式。可以表达诸如“并行启动a、b、c。当A和B完成时,启动AB。当A和C完成时,启动AC”。这种灵活性需要更加小心以避免工作流程中的错误(例如从不调用回调,或多次调用)。请参阅Raynos关于使用期货的帖子这是让我“获得”期货的帖子)。
  • 异步- 更传统的库,每种模式都有一种方法。在我对 step 的宗教转变之前,我从这个开始,随后意识到异步中的所有模式都可以用一个更易读的范式在 Step 中表达。
  • TameJS - 由 OKCupid 编写,它是一个预编译器,它添加了一种新的语言原语“await”,用于优雅地编写串行和并行工作流。该模式看起来很棒,但它确实需要预编译。我还在考虑这个问题。
  • StreamlineJS - TameJS 的竞争对手。我倾向于Tame,但你可以自己决定。

或者要阅读有关异步库的所有信息,请参阅与作者的小组访谈

网络框架:

  • 用于组织网站的Express Great Ruby on Rails-esk 框架。它使用JADE作为 XML/HTML 模板引擎,这使得构建 HTML 的痛苦大大减少,甚至近乎优雅。
  • jQuery虽然在技术上不是一个节点module,但 jQuery 正迅速成为客户端用户界面的事实上的标准。jQuery 提供类似 CSS 的选择器来“查询”然后可以操作的 DOM 元素集(集处理程序、属性、样式等)。同样,Twitter 的Bootstrap CSS 框架、用于MVC模式的Backbone.js用于将所有 JavaScript 文件拼接成单个文件的Browserify.js这些module都已成为事实上的标准,因此如果您还没有听说过它们,至少应该检查一下。

测试:

  • JSHint - 必须使用;我一开始没有使用这个,现在看起来很费解。JSLint 添加了您使用 Java 等编译语言获得的一系列基本验证。不匹配的括号、未声明的变量、多种形状和大小的类型。您还可以打开我称之为“肛门模式”的各种形式,您可以在其中验证空格和诸如此类的样式,如果那是您的那杯茶,那就可以了——但真正的value来自于获得关于确切行号的即时反馈,其中你忘记了一个结束的“)”......而不必运行你的代码并点击违规行。“JSHint”是Douglas CrockfordJSLint 的一个更可配置的变体
  • Mocha是 Vows 的竞争对手,我开始喜欢它。这两个框架都很好地处理了基础知识,但复杂的模式在 Mocha 中往往更容易表达。
  • Vows Vows 真的很优雅。它会打印出一个可爱的报告 (--spec),向您显示哪些测试用例通过/失败。花 30 分钟学习它,您就可以轻松地为您的module创建基本测试。
  • Zombie - 使用JSDom作为虚拟“浏览器”对 HTML 和 JavaScript 进行无头测试很强大的东西。将其与Replay结合使用,可以对浏览器内代码进行闪电般的快速确定性测试。
  • 关于如何“思考”测试的评论:
    • 测试是非可选的。有了这样的JavaScript动态语言,也有少数的静态检查。例如,将两个参数传递给期望 4 的方法在代码执行之前不会中断。在 JavaScript 中创建错误的门槛很低。基本测试对于弥补与编译语言的验证差距至关重要。
    • 忘记验证,只需执行您的代码。对于每种方法,我的第一个验证案例都是“没有任何问题”,这是最常触发的案例。证明您的代码在不抛出的情况下运行可以捕获 80% 的错误,并且可以极大地提高您的代码信心,以至于您会发现自己回去并添加您跳过的细微验证案例。
    • 从小处着手,打破惯性障碍。我们都很懒惰,时间紧迫,很容易将测试视为“额外工作”。所以从小事做起。编写测试用例 0 - 加载您的module并报告成功。如果你强迫自己做这么多,那么测试的惯性障碍就会​​被打破。第一次做这件事不到 30 分钟,包括阅读文档。现在编写测试用例 1 - 调用您的方法之一并验证“没有任何问题”,也就是说,您没有收到错误消息。测试用例 1 应该花不到一分钟的时间。随着惯性的消失,逐步扩展您的测试覆盖范围变得很容易。
    • 现在用您的代码改进您的测试。不要被模拟服务器和所有这些的“正确”端到端测试的样子吓倒。代码从简单开始,然后演变以处理新情况;测试也应该。当您向代码添加新案例和新复杂性时,请添加测试案例来练习新代码。当您发现错误时,添加验证和/或新案例以覆盖有缺陷的代码。当您正在调试并且对一段代码失去信心时,请返回并添加测试以证明它正在执行您认为的操作。捕获示例数据字符串(来自您调用的其他服务、您抓取的网站等)并将它们提供给您的解析代码。这里有几个案例,那里改进了验证,你最终会得到高度可靠的代码。

此外,请查看推荐的 Node.js module官方列表但是,GitHub 的 Node Modules Wiki更加完整,并且是一个很好的资源。


要理解 Node,考虑一些关键的设计选择会很有帮助:

Node.js 是基于事件异步/非阻塞的. 事件(如传入的 HTTP 连接)将触发 JavaScript 函数,该函数执行一些工作并启动其他异步任务,如连接到数据库或从另一台服务器提取内容。一旦这些任务被启动,事件函数就会结束,Node.js 会重新进入睡眠状态。一旦发生其他事情,比如建立数据库连接或外部服务器响应内容,回调函数就会触发,更多的 JavaScript 代码执行,可能会启动更多的异步任务(比如数据库查询)。通过这种方式,Node.js 将愉快地为多个并行工作流交错活动,在任何时间点运行任何不受阻止的活动。这就是 Node.js 在管理数千个并发连接方面做得如此出色的原因。

为什么不像其他人一样为每个连接使用一个进程/线程?在 Node.js 中,一个新的连接只是一个非常小的堆分配。启动一个新进程需要更多的内存,在某些平台上为 MB。但真正的成本是与上下文切换相关的开销。当您有 10^6 个内核线程时,内核必须做很多工作来确定下一个应该执行谁。为 Linux 构建 O(1) 调度程序已经进行了大量工作,但最终,与 10^6 个进程竞争 CPU 时间相比,拥有单个事件驱动进程的效率更高。此外,在过载情况下,多进程模型的表现非常糟糕,关键的管理和管理服务匮乏,尤其是 SSHD(这意味着您甚至无法登录盒子以了解它到底有多糟糕)。

Node.js 是单线程和无锁的Node.js,作为一个非常深思熟虑的设计选择,每个进程只有一个线程。因此,多个线程同时访问数据从根本上是不可能的。因此,不需要锁。线程很难。真的真的很难。如果您不相信这一点,那么您还没有完成足够的线程编程。正确锁定很难,并且会导致很难追踪的错误。消除锁和多线程使最严重的错误类别之一消失了。这可能是 node.js 的最大优势。

但是我如何利用我的 16 核盒子呢?

两种方式:

  1. 对于像图像编码这样的大型繁重计算任务,Node.js 可以启动子进程或向其他工作进程发送消息。在此设计中,您将有一个线程来管理事件流和 N 个进程执行繁重的计算任务并占用其他 15 个 CPU。
  2. 为了扩展 Web 服务的吞吐量,您应该在一个机器上运行多个 Node.js 服务器,每个核心一个,使用集群(使用 Node.js v0.6.x,此处链接的官方“集群”module取代了具有不同的 API)。然后,这些本地 Node.js 服务器可以在套接字上竞争以接受新连接,从而平衡它们之间的负载。一旦连接被接受,它就会与这些共享进程中的一个紧密绑定。理论上,这听起来很糟糕,但在实践中它工作得很好,并且可以让您避免编写线程安全代码的麻烦。此外,这意味着 Node.js 获得了出色的 CPU 缓存亲和力,可以更有效地使用内存带宽。

Node.js 让你可以毫不费力地做一些非常强大的事情。假设您有一个 Node.js 程序,它执行各种任务、侦听TCP端口的命令、编码一些图像等等。使用五行代码,您可以添加一个基于 HTTP 的 Web 管理门户,显示活动任务的当前状态。这很容易做到:

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end(myJavascriptObject.getSomeStatusInfo());
}).listen(1337, "127.0.0.1");

现在您可以点击一个 URL 并检查您正在运行的进程的状态。添加几个按钮,您就有了一个“管理门户”。如果您有一个正在运行的 Perl/Python/Ruby 脚本,那么仅仅“加入管理门户”并不简单。

但是 JavaScript 是不是很慢/不好/邪恶/恶魔之子?JavaScript 有一些奇怪的地方,但是“好的部分”是一种非常强大的语言,无论如何,JavaScript 是客户端(浏览器)上的语言。JavaScript 将继续存在;其他语言将其作为 IL 目标,世界一流的人才正在竞相生产最先进的 JavaScript 引擎。由于 JavaScript 在浏览器中的作用,大量的工程工作被投入到让 JavaScript 快速运行中。 V8是最新最好的 javascript 引擎,至少在这个月是这样。它在效率和稳定性方面击败了其他脚本语言(看着你,Ruby)。并且随着微软、谷歌和 Mozilla 的庞大团队致力于解决这个问题,竞争构建最好的 JavaScript 引擎,它只会变得更好(它不再是一个 JavaScript“解释器”,因为所有现代引擎都做了大量的JIT在引擎盖下编译并解释仅作为执行一次代码的后备)。是的,我们都希望我们可以修复一些奇怪的 JavaScript 语言选择,但这真的没那么糟糕。而且该语言非常灵活,您实际上不是在编写 JavaScript,而是在编写 Step 或 jQuery —— 比任何其他语言都要多,在 JavaScript 中,库定义了体验。无论如何,要构建 Web 应用程序,您几乎都必须了解 JavaScript,因此在服务器上使用它进行编码具有某种技能集协同作用。它让我不再害怕编写客户端代码。

此外,如果你真的讨厌 JavaScript,你可以使用像CoffeeScript这样的语法糖或者任何其他创建 JavaScript 代码的东西,比如Google Web Toolkit (GWT)。

说到 JavaScript,什么是“闭包”?- 几乎是一种奇特的说法,即跨调用链保留词法作用域的变量。;) 像这样:

var myData = "foo";
database.connect( 'user:pass', function myCallback( result ) {
    database.query("SELECT * from Foo where id = " + myData);
} );
// Note that doSomethingElse() executes _BEFORE_ "database.query" which is inside a callback
doSomethingElse();

看看如何只使用“myData”而不用做任何尴尬的事情,比如把它藏在一个对象中?与 Java 不同的是,“myData”变量不必是只读的。这种强大的语言特性使异步编程变得不那么冗长和痛苦。

编写异步代码总是比编写一个简单的单线程脚本更复杂,但是使用 Node.js,它并没有那么难,除了效率和数千个并发连接的可扩展性之外,您还可以获得很多好处。 ..

@Nick - 那是错误的。“并发问题”由于节点是单线程的这一事实而得到缓解。锁定节点根本不存在;在单线程范式中不需要它。
2021-03-15 14:51:27
“'myData' 变量不必是只读的”——似乎您希望在大部分时间保持不变以防止并发问题,对吧?
2021-03-28 14:51:27
@John - 我的调试评论也绝对适用于 vanilla asnyc。为了中断调试,我需要做的就是在“process.nextTick(...)”中包装一些函数调用。然后当它抛出时,我的堆栈跟踪将从 process.nextTick 开始;不是很有帮助。我真正想知道的是:“哪个堆栈调度了 process.nextTick?” 我称该数据为“因果链”,它让我想起 Java 异常处理中的“causedBy”......低级代码抛出,中级代码捕获 LL 异常并抛出 FrameworkMethodFailedException - 没有 'causedBy',来自 LL 的堆栈代码会丢失。
2021-03-30 14:51:27
2021-04-03 14:51:27
ps,旁注 - 我实际上编辑了足够多的时间,以至于帖子变成了“社区维基”(阈值是 10 次编辑)。我错误地认为这是某种荣誉,但实际上它只是阻止了从投票中获得的声誉。最终,一位 CW 前状态的选民点击了“取消投票”(希望是意外:),我失去了声誉......困惑我提交了meta.stackexchange.com/questions/129941/ ......并在“社区”中接受教育维基”的实际意思。Mods 足以删除 CW 状态。:)
2021-04-03 14:51:27

我认为优点是:

  1. 在 VM 上使用动态语言 (JavaScript) 进行 Web 开发,速度非常快 (V8)。它比 Ruby、Python 或 Perl 快得多。

  2. 能够在单个进程上以最小的开销处理数千个并发连接。

  3. JavaScript 非常适合具有一流函数对象和闭包的事件循环。人们已经知道如何以这种方式使用它,在浏览器中使用它来响应用户发起的事件。

  4. 很多人已经知道 JavaScript,即使是那些不自称是程序员的人。它可以说是最流行的编程语言。

  5. 在 Web 服务器和浏览器上使用 JavaScript 可以减少两种编程环境之间的阻抗失配,这两种编程环境可以通过 JSON 通信数据结构,在等式两边工作相同。重复的表单验证代码可以在服务器和客户端等之间共享。

有趣的是,我 10 年前在 ASP 中编写 JScript,所以我可以 (a) 避免 VBScript 的可怕,(b) 在客户端和服务器上使用相同的语言,以及 (c) 重用我自己和其他人的 JS 库用于字符串处理...等。
2021-03-17 14:51:27
@iJK yammer.com 是 node.js 的工作应用程序
2021-03-21 14:51:27
@为什么你只为“旧废话”编辑这篇文章?如果你从一开始就写这篇文章,我认为 212 人不会投票给这篇文章。
2021-03-27 14:51:27
@postfuturist 我认为您已经与 java 6 -Xint 进行了比较。尝试与 java 6 -server 进行比较
2021-03-30 14:51:27
@postfuturist:它实际上在许多替代方案中表现良好。在很多情况下,它也能轻松击败 Java 6。整洁的!
2021-04-06 14:51:27

V8是 JavaScript 的一个实现。它允许您运行独立的 JavaScript 应用程序(除其他外)。

Node.js 只是一个为 V8 编写的库,它执行事件 I/O。这个概念解释起来有点棘手,我相信有人会用比我更好的解释来回答......要点是,与其做一些输入或输出并等待它发生,你只是不要等待让它完成。例如,询问文件的上次编辑时间:

// Pseudo code
stat( 'somefile' )

这可能需要几毫秒,也可能需要几秒钟。使用事件I/O,您只需触发请求,而不是等待您附加一个在请求完成时运行的回调:

// Pseudo code
stat( 'somefile', function( result ) {
  // Use the result here
} );
// ...more code here

这使它很像浏览器中的 JavaScript 代码(例如,具有Ajax样式功能)。

有关更多信息,您应该查看文章Node.js 真的令人兴奋,这是我对库/平台的介绍......我发现它非常好。

如何在不使用锁、使用线程、进程、闭包的情况下实现事件 IO?而且我有一种感觉,这些概念与函数式编程和 Erlang 的概念非常相似。
2021-03-15 14:51:27
“如何在不使用......闭包的情况下实现事件 IO?” JavaScript 支持闭包并且它们一直在 node.js 中使用(此处示例中的匿名函数 ans)。
2021-03-15 14:51:27
@panzi:没有注意到 Jeffrey 在他的“没有”实现 node.js 的事物列表中包含了闭包。显然,javascript 中的每个函数都是围绕其作用域的闭包:)
2021-03-17 14:51:27
node.js 的 IO 事件循环意味着在任何给定的时间点最多只做一件事。我看到了两个显着的好处:没有线程切换的开销,因此 node.js 非常快,其次,Java 臭名昭著的许多典型并发错误是不可能的。
2021-03-23 14:51:27
据我所知,它是作为一个简单的事件循环实现的。v8 已经具有回调/等功能,就像任何 javascript 实现一样。
2021-04-02 14:51:27

Node.js是为服务器端 JavaScript 代码构建的开源命令行工具。您可以下载tarball,编译并安装源代码。它可以让您运行 JavaScript 程序。

JavaScript 由V8执行,V8是谷歌开发的 JavaScript 引擎,用于Chrome浏览器。它使用 JavaScript API 来访问网络和文件系统。

它因其性能和执行并行操作的能力而广受欢迎。

理解 node.js迄今为止我发现的node.js最好的解释

以下是一些关于该主题的好文章。

闭包是在创建代码的上下文中执行代码的一种方式。

这对于并发性意味着您可以定义变量,然后启动一个非阻塞I/O函数,并为其回调发送一个匿名函数。

当任务完成时,回调函数将在带有变量的上下文中执行,这就是闭包。

闭包非常适合编写具有非阻塞 I/O 的应用程序的原因是它非常容易管理异步执行的函数的上下文。