扩展 Node.js 应用程序的 3 个误区、3 个事实和 3 个策略

过去十年来,Node.js 已成为开发人员的首选解决方案,以其处理并发连接和支持高性能应用程序的能力而闻名。根据我使用富文本编辑器处理 Express 项目的经验,我亲眼目睹了 Node.js 如何将内容创建应用程序转变为可扩展、可定制的解决方案。但这里有一个大问题:Node.js 真的能够扩展以支持企业级的数百万用户吗?

答案是肯定的,但实际情况却更加微妙。Node.js 旨在实现扩展,但其扩展性能在很大程度上取决于应用程序架构、优化和管理系统资源的方法。

关于 Node.js 和高流量的误解:哪些是真的,哪些不是?

在处理高流量方面,Node.js 常常受到赞扬和质疑。一些开发人员表示,它改变了实时应用程序的游戏规则,而其他开发人员则认为它在扩展到数百万用户时存在局限性。让我们来看看常见的误解:

误解一:Node.js 无法处理高流量

**现实:** Node.js 建立在事件驱动的非阻塞 I/O 模型上,这实际上使其能够轻松管理数千个并发连接。与传统的服务器架构(Apache、PHP)不同,传统服务器架构会为每个请求创建一个新线程,并迅速耗尽资源,而 Node.js 则在单个线程上运行,使用事件循环异步处理任务。正是这种设计最大限度地减少了资源使用量并提高了可扩展性。

误解二:Node.js 只是 JavaScript,缺乏功能

**现实:**虽然 Node.js 运行在 JavaScript 上,但其强大功能来自 Google 的 V8 JavaScript 引擎,该引擎将 JavaScript 编译为优化的机器代码。这意味着 Node.js 不仅仅是运行脚本,它在许多用例中提供与编译语言相当的性能。

误解三:扩展 Node.js 很容易

**现实:** Node.js 的架构非常适合 I/O 密集型任务,如 API 服务器、聊天应用和实时系统,但要扩展到数百万用户,需要周密的规划和正确的架构。负载平衡、集群和优化系统资源等技术是使其大规模运行的关键。

关于 Node.js 规模化的事实

揭穿谣言后,我们来谈谈事实。Node.js 已证明自己能够支持高性能、可扩展的应用程序,但扩展到数百万用户并非没有挑战。

事实 1:Node.js 依赖于单线程模型

让我们从 Node.js 架构的基础开始。它的单线程、事件驱动模型非常适合 I/O 任务,这使得它可以高效地同时处理多个连接。但是,当涉及到 CPU 密集型操作时,同样的模型可能会成为瓶颈。单个线程上的大量计算可能会阻塞事件循环,导致处理其他请求的延迟。

虽然单线程是一个限制,但我们应该记住,由于 Node.js 具有非阻塞 I/O,它还擅长同时处理多个连接。为了解决单线程模型的局限性,您可以使用工作线程或微服务来卸载 CPU 密集型任务,具体取决于应用程序的架构。

事实 2:内存管理在规模化时至关重要

随着应用程序的增长,管理资源变得越来越重要。事实上,内存泄漏对于不断增长的 Node.js 应用程序来说可能是一个大问题。当资源(如对象或变量)没有得到正确清理时,就会发生这种情况。随着时间的推移,这会减慢一切速度,甚至导致服务器崩溃,尤其是在流量激增时。

阿迪达斯的 Node.js 系统存在内存泄漏问题,随着用户群的增长,这会导致性能问题。阿迪达斯软件工程总监 Aleksandar Mirilovic 在一篇题为 的文章中分享了他的经验。他发现对象不必要地被保存在内存中,导致资源膨胀。

他们是如何解决这个问题的:

**TL;DR:** 在尝试在本地和暂存中重现该问题并失败后,Adidas 直接从生产中捕获了堆快照。根本原因被追溯到 Google reCAPTCHA 库为每个请求创建新的 gRPC 连接而不关闭它们。重构代码以使用单个客户端实例解决了该问题,稳定了内存使用情况并提高了性能。

事实 3:跨 CPU 核心扩展并非自动的

优化 I/O 和内存管理后,还有另一个需要考虑的扩展方面:硬件利用率。默认情况下,Node.js 在单个线程上运行,这意味着它不会自动利用所有可用的 CPU 核心。对于高流量应用程序,这可能是一个问题,因为您的服务器的大量处理能力可能会被闲置。许多开发人员没有意识到这一点,如果不设置类似集群之类的东西,他们就无法充分利用硬件。

您可以使用 Node.js 集群模块运行应用程序的多个实例,每个实例在单独的 CPU 核心上运行。这会将工作负载分配到所有可用核心上,因此您的应用可以处理更多并发用户,并提高性能。

可扩展的策略

扩展 Node.js 来处理数百万用户不仅需要编写高效的代码,还需要构建一个可以随着用户群增长而增长的基础设施。

策略一:负载均衡

单台服务器只能处理这么多数据 — 这是硬件限制。这就是负载平衡的作用所在。通过将流量分散到多台服务器上,您可以避免瓶颈并保持应用响应。如果没有它,在流量高峰期间,您可能会面临停机或性能低下的风险。

想想最近的例子:ChatGPT 用户因崩溃而沮丧,或者亚马逊购物者看到可爱的狗狗图片而不是产品页面。负载平衡可确保在需求激增时更顺畅地运行。NGINX、HAProxy 或 AWS Elastic Load Balancer 等工具可以在 Node.js 实例之间均匀分配请求,从而提高性能并增加冗余度,这样即使服务器出现故障,您的应用也能保持在线。

策略2:缓存

从数据库或外部 API 反复获取相同的数据会降低应用速度并给后端资源造成压力。缓存通过将频繁请求的数据存储在内存中来解决此问题,从而使您的应用能够提供更快的响应并轻松处理更多流量。Redis 和 Memcached 等工具可以改变这一现状,现实世界中的例子表明缓存可以发挥巨大的作用。

Redis 在各个行业的应用:

  • 电子商务:Gap Inc. 通过集成 Redis Enterprise 解决了令购物者感到沮丧的库存更新缓慢问题。这减少了延迟并提供实时库存信息,即使在黑色星期五流量高峰期间也是如此。
  • 欺诈检测:数字身份公司 BioCatch 使用 Redis Enterprise 每月处理 50 亿笔交易。通过缓存行为数据和 API 响应,他们可以在 40 毫秒内检测到欺诈活动,从而领先于网络威胁。
  • 缓存不仅仅与速度有关——它可以提高可靠性,减少后端负载,并防止购物车被放弃。

    策略三:数据库性能

    即使缓存已到位,高流量应用程序中的薄弱环节通常也是数据库操作。低效的查询或设计不良的结构会减慢一切速度,让用户感到沮丧,您的应用程序也难以跟上。缓存非常适合加快频繁请求的速度,但您的数据库仍然需要高效地处理其余工作——尤其是在流量增长的情况下。

    为了更有效地处理高流量,您可以对数据库进行一些关键改进。首先,专注于微调查询 - 这意味着简化 SQL 语句、摆脱不必要的操作并添加索引以加快速度。

    例如,如果您的应用经常搜索 user_id,则为该列添加索引可以使数据库更快地找到它。接下来,减少应用发送的查询数量。不要对用户详细信息和订单发出单独的请求,而是使用连接将它们合并为单个查询。如果您的应用处理大量流量,您需要通过分片(将您的架构架构拆分为更小、更集中的数据块)或设置只读副本来分担繁重的读取操作负载,从而实现扩展。

    仍然想知道 Node.js 是否能够承受压力?

    它已经为世界上一些最大的平台提供支持。LinkedIn 从 Ruby on Rails 过渡到 Node.js,将服务器数量减少了 20 倍,同时支持超过 6 亿用户。Netflix 依靠 Node.js 来管理数百万个并发流并提供更快的加载时间。Uber 的工程堆栈使用其实时功能无缝处理大量乘车请求。沃尔玛也转向使用 Node.js 来确保其系统在黑色星期五的流量激增期间平稳运行。

    借助负载平衡、缓存和数据库优化等策略,Node.js 甚至可以处理最苛刻的工作负载。无论您是构建全球平台还是扩展以满足不断增长的流量,我都愿意打赌,使用 Node.js 您可以真正创建快速、可靠且可扩展的应用程序。