”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 使用 React、Node.js 和 MongoDB 构建高性能全栈应用程序:可扩展性、速度和解决方案之旅

使用 React、Node.js 和 MongoDB 构建高性能全栈应用程序:可扩展性、速度和解决方案之旅

发布于2024-11-02
浏览:631

Building High-Performance Full-Stack Apps with React, Node.js & MongoDB: A Journey in Scalability, Speed & Solutions

您打开生产应用程序,发现它正在停止运行。前端没有响应。后端 API 超时。 MongoDB 查询似乎无限期地运行。您的收件箱里充满了用户投诉。你的团队挤在一起试图对情况进行分类。

去过吗?嗯,我也是。

我是一名高级全栈开发人员,我厌倦了那些在您仅作为单个用户使用它们时很好的应用程序,或者当问题空间很简单但在实际流量或任务要求稍高。

请跟着我,我将向您介绍我如何使用 React、Node.js 和 MongoDB 解决这些问题。

我不会只是给你另一个简单的旧教程,我会分享一个故事。一个关于如何解决现实世界问题以及如何构建可以通过时间和任何挑战的快速高可扩展应用程序的故事。

1:当 React 成为瓶颈

我们刚刚在我的工作中推出了使用 React 开发的 Web 应用程序的更新。我们充满信心,相信用户会喜欢这些新功能。

然而,不久之后我们就开始收到投诉:应用程序加载速度极其缓慢,转换时断断续续,用户变得越来越沮丧。尽管知道新功能很有用,但它们无意中导致了性能问题。我们的调查发现了一个问题:该应用程序将其所有组件捆绑到一个包中,这迫使用户每次访问该应用程序时都下载所有内容。

修复:我们实现了一个非常有用的概念,称为延迟加载。我以前曾遇到过这个想法,但这正是我们所需要的。我们彻底修改了应用程序的结构,确保它只在需要时加载必要的组件。

以下是我们如何实施此解决方案的一瞥:

const Dashboard = React.lazy(() => import('./Dashboard'));
const Profile = React.lazy(() => import('./Profile'));

Loading...}>
  

结果:这一变化的影响是非常显着的。我们发现我们的捆绑包缩减了 30%,用户体验到了更快的初始加载速度。最好的部分是用户不知道应用程序的某些部分仍在加载,我们明智地使用了 Suspense 并显示了一个简单的非侵入式加载消息。

2:驯服 React 中的状态管理猛兽

几个月过去了,我们的开发团队正在大踏步前进并推出许多新功能。但随着业务的增长,我们无意中开始构建我所说的更复杂的应用程序。 Redux 很快就成为促进简单交互的负担而不是助手。

因此,我花了一些时间创建 POC 以获得更好的替代方案。我记录了它的原理,并主持了多次知识共享会议,讨论该方法可能是什么样子。我们最终决定作为一个团队尝试使用 React Hooks(特别是 useReducer)作为我们提出的管理状态的解决方案,因为最终我们想要更简单的代码和更少的大量运行时占用空间,而新版本的 Redux 因许多较小的独立组件而导致开销不断增加状态。

接下来的转变堪称革命性的。我们发现自己用简洁、易于理解的钩子逻辑替换了数十行样板代码。以下是我们如何实施这种新方法的说明性示例:

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count   1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

const CounterContext = React.createContext();

function CounterProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    
      {children}
    
  );
}

结果:这一转变的影响是深远的。我们的应用程序变得更加可预测并且更容易推理。代码库现在更精简、更直观,使我们的团队能够以更快的速度进行迭代。也许最重要的是,我们的初级开发人员报告说,他们浏览和理解代码库的能力有了显着提高。最终结果是一个双赢的局面:需要维护的代码更少,需要解决的错误更少,并且开发团队明显更快乐、更高效。

3:征服后端战场 - 优化 Node.js API 以实现最佳性能

虽然我们能够对前端进行大量改进,但不久之后我们在后端遇到了多个问题。我们的 API 性能变得很糟糕,而且几乎没有端点开始表现得很糟糕。这些端点对不同的第三方服务进行一系列调用,并且随着用户群的不断增长,系统无法处理此负载。

这是非常常识性的问题:我们不是平行的!即,对每个端点的请求都是按顺序处理的,即每个下一个调用都将等待上一个调用完成。在这个大规模(数十万个请求)的系统中,事实证明这是灾难性的。

解决方案:为了解决这个问题,我们决定重写大量代码,并使用 Promise.all() 的强大功能以并发方式发出 API 请求。这意味着您启动多个请求,而不必等到每个调用完成才启动下一个请求。

为此,我们不会启动 API 调用,等待其完成,再进行另一个调用,依此类推……

不是简单地使用 Promise.all(),所有内容都会立即启动,而且速度更快。

以下是我们如何实施此解决方案的一瞥:

const getUserData = async () => {
  const [profile, posts, comments] = await Promise.all([
    fetch('/api/profile'),
    fetch('/api/posts'),
    fetch('/api/comments')
  ]);
  return { profile, posts, comments };
};

结果:此优化的影响是直接且巨大的。我们观察到响应时间显着减少了 50%,并且我们的后端在重负载下的弹性显着提高。用户不再遇到令人沮丧的延迟,我们看到服务器超时的数量急剧减少。此增强功能不仅改善了用户体验,还使我们的系统能够在不影响性能的情况下处理更多的请求。

4:MongoDB 探索 — 驯服数据野兽

随着我们的应用程序获得关注并且我们的用户群以数量级增长,我们不得不面对一个新的障碍:如何扩展其数据?当我们不得不处理数百万个文档时,我们曾经响应迅速的 MongoDB 实例开始出现阻塞。过去以毫秒为单位运行的查询需要几秒钟才能完成,或者超时。

我们花了几天时间研究 MongoDB 的性能分析工具,并发现了最大的坏蛋:未索引的查询。我们的一些最常见的查询(例如对用户配置文件的请求)正在扫描整个集合,他们可以使用坚如磐石的索引。

解决方案:根据我们掌握的信息,我们知道我们需要做的就是在那些最需要的字段上创建复合索引,这将永久修复我们的数据库主体查找时间。以下是我们在“用户名”和“电子邮件”字段中的做法。

db.users.createIndex({ "username": 1, "email": 1 });

结果:此优化的影响非常显着。以前需要 2 秒才能执行的查询现在可以在 200 毫秒内完成,性能提高了十倍。我们的数据库恢复了快速响应能力,使我们能够处理显着更高的流量,而不会出现任何明显的速度下降。

然而,我们并没有就此止步。认识到我们的快速增长轨迹可能会持续下去,我们采取了积极措施来确保长期的可扩展性。我们实现了分片来将数据分布到多个服务器上。这一战略决策使我们能够横向扩展,确保我们处理数据的能力与不断扩大的用户群同步增长。

5。拥抱微服务——解决可扩展性难题

随着我们的用户群不断增加,越来越明显的是,我们不仅需要扩展我们的基础设施,而且还必须发展我们的应用程序,以便能够充满信心地扩展。当我们是一个较小的团队时,单体架构非常适合我们,但随着时间的推移,它变得相当麻烦。我们知道我们需要迈出一大步,开始构建微服务架构——这对任何工程团队来说都是一项艰巨的任务,但具有很大的可扩展性和可靠性优势。

最大的问题之一是服务之间的通信。 HTTP 请求确实不适合我们的情况,它给我们的系统留下了又一个瓶颈,因为大量的操作都在焦躁地等待响应,并且在需要时杀死程序,因为有太多事情要做。此时我们意识到使用 RabbitMQ 是显而易见的答案,因此我们没有考虑太多就应用了它。

以下是我们如何实施此解决方案的一瞥:

const amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', (err, conn) => {
  conn.createChannel((err, ch) => {
    const queue = 'task_queue';
    const msg = 'Hello World';

    ch.assertQueue(queue, { durable: true });
    ch.sendToQueue(queue, Buffer.from(msg), { persistent: true });
    console.log(`Sent ${msg}`);
  });
});

结果:从我们的角度来看,过渡本身以及通过 RabbitMQ 进行的通信看起来就像魔法一样……数字证实了这一点!我们幸运地成为松散耦合微服务的所有者,其中每个服务都可以自行扩展。突然间,具体 dns 区域上的真实流量峰值并不涉及系统崩溃的担忧(因为无论哪个服务操作都会提出相同的要求,因为它们总是级联),而是工作得很好,因为其余部分/操作只是平静地举起手说“我可以睡觉了,亲爱的。”维护也变得更容易,问题更少,同时添加新功能或更新更快,操作更有信心。

结论:规划未来创新路线

这段激动人心的旅程中的每一步都是一个教训,提醒我们全栈开发不仅仅是编写代码。它理解并解决复杂的相互关联的问题——从让我们的前端更快、构建后端以承受故障,到处理随着用户群爆炸而扩展的数据库。

展望 2024 年下半年及以后,对 Web 应用程序的需求增长不会放缓。如果我们继续专注于构建可扩展、性能优化和架构良好的应用程序,那么我们就有能力解决今天的任何问题,并在未来利用这些其他挑战。这些现实生活中的经历极大地影响了我进行全栈开发的方式——我迫不及待地想看看这些影响将继续推动我们的行业走向何方!

但是你呢?您是否遇到过类似的障碍,或者是否有幸通过其他创造性的方法来克服这些问题?我很想听听您的故事或见解 - 请在评论中告诉我或与我联系!

版本声明 本文转载于:https://dev.to/mukhilpadmanabhan/building-high-performance-full-stack-apps-with-react-nodejs-mongodb-a-journey-in-scalability-speed-solutions-3fk0?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 字符串与字符串
    字符串与字符串
    细绳 小写字符串是JavaScript中的原始数据类型。 用这种类型创建的字符串不是对象,但 JavaScript 会自动用 String 对象包装它们(这称为“装箱”)。 let imAString = "hello"; console.log(typeof imAString)...
    编程 发布于2024-11-08
  • 如何在 C++ 和 Python 中右对齐输出字符串?
    如何在 C++ 和 Python 中右对齐输出字符串?
    右对齐格式化输出字符串处理文本文件时,一致地对齐数据可以增强可读性和分析能力。在 C 中,出现了一个问题:如何才能格式化右对齐的输出字符串?使用 Python 的格式化语法,解决方案很简单:line_new = '{:>12} {:>12} {:>12}...
    编程 发布于2024-11-08
  • 黑客啤酒节第二周
    黑客啤酒节第二周
    第二周,我不得不花更少的时间寻找问题。这次是 Mattermost 文档网站在深色模式下发生的错误,其中特定按钮的颜色不正确。 我知道 Docusaurus,因为我看过其他文档和基于它构建的一些课程材料网站。它在底层使用了 React,所以我很熟悉它。但这个文档网站是用 Python 编写的。虽然我...
    编程 发布于2024-11-08
  • Python 中的 Try/Except 与 If/Else:哪种方法更有效?
    Python 中的 Try/Except 与 If/Else:哪种方法更有效?
    Try/Except 与 If/Else:哪种方法更可取?在 Python 中处理异常时,开发人员经常面临困境是否测试有效性或尝试某项操作并处理任何产生的异常。本文深入探讨了每种方法的细微差别,为决策提供指导和示例。尝试/除外:拥抱例外根据 PEP 20,错误除非明确地保持沉默,否则绝不应悄无声息地...
    编程 发布于2024-11-08
  • Git 远程分支
    Git 远程分支
    本周我们必须向另一个人的存储库添加一个功能。具体来说,我们需要在用户的 $HOME 文件夹中添加一个默认配置 TOML 文件,其中包含 CLI 工具的默认参数。我已为 readMeMaker 存储库做出了贡献: https://github.com/jadorotan/readMeMaker.git...
    编程 发布于2024-11-08
  • 对于简单的操作,流总是比传统集合慢吗?
    对于简单的操作,流总是比传统集合慢吗?
    Java 8 流性能对比传统集合您最近涉足 Java 8 并进行了非正式基准测试,以将其 Stream API 与经典集合的性能进行比较。您的测试涉及过滤整数列表、提取偶数的平方根并将结果存储在 Double 列表中。然而,您质疑测试的有效性,并渴望澄清真正的性能影响。评估基准测试您的初步结果,表明...
    编程 发布于2024-11-08
  • 语言 API,允许您添加您的母语。
    语言 API,允许您添加您的母语。
    早在 2016 年 4 月,我就有了为一个我非常喜欢的部落“Igede Language”创建一个字典项目的想法,我决定将其称为“Igede Dictionary”,尽管我不是“ t 是母语人士。 这让我撰写并翻译了 5,000 多个单词,从 Igede 语言翻译成英语。毫无疑问,这是我曾经研究过...
    编程 发布于2024-11-08
  • 使用 Playwright、TypeScript 和 JavaScript 进行自动化
    使用 Playwright、TypeScript 和 JavaScript 进行自动化
    剧作家与 TypeScript | JavaScript 安装 Playwright 是 Microsoft 与 Puppeteer 团队合作推出的基于 Web 的现代 API 自动化工具,Puppeteer 是一个 JavaScript 库,它提供高级 API 来通过 DevTools 协议或 W...
    编程 发布于2024-11-08
  • 为什么使用 Z-Index 时我的伪元素出现在标题元素上方?
    为什么使用 Z-Index 时我的伪元素出现在标题元素上方?
    Z-Index 和伪元素:案例研究在 CSS 中,z-index 属性指定元素的堆叠顺序页面,确定哪些元素出现在其他元素“前面”或“后面”。然而,当涉及到伪元素时,例如 ::before 或 ::after,它们与 z-index 的交互有时可能不那么简单。考虑一个场景,我们使用::before 伪...
    编程 发布于2024-11-08
  • 如何在剥离标签之前删除顽固的 HTML 特殊字符?
    如何在剥离标签之前删除顽固的 HTML 特殊字符?
    去除顽固的 HTML 特殊字符strip_tags 函数虽然擅长删除 HTML 标签,但无法处理讨厌的 HTML 特殊字符,例如用于不间断空格或 © 用于版权符号。这可能是创建干净 RSS 源的绊脚石。要解决此问题,请考虑使用以下策略之一:HTML 实体解码:在字符串经过 strip_tags 之前...
    编程 发布于2024-11-08
  • 如何在 Go 中解密 AES ECB 模式加密?
    如何在 Go 中解密 AES ECB 模式加密?
    Go 中的 AES ECB 加密AES ECB 模式加密,其中每个明文块都独立加密,是一种简单但可能不安全的加密方法。在Go中,可以使用以下代码执行AES ECB解密:package main import ( "crypto/aes" "fmt&quo...
    编程 发布于2024-11-08
  • PHP 会话管理中的 session_unset() 和 session_destroy() 有什么区别?
    PHP 会话管理中的 session_unset() 和 session_destroy() 有什么区别?
    揭示 PHP 中 session_unset() 和 session_destroy() 的独特作用在 PHP 会话管理领域,出现了两个关键函数:session_unset() 和 session_destroy()。虽然它们似乎都围绕会话数据操作,但它们的功能和效果却显着不同。1。理解差异根据 P...
    编程 发布于2024-11-08
  • 以下是一些标题选项,请记住问题格式和文章的重点是控制选择框选项宽度:

**选项 1(更多技术性):**
* **如何控制Sele的宽度
    以下是一些标题选项,请记住问题格式和文章的重点是控制选择框选项宽度: **选项 1(更多技术性):** * **如何控制Sele的宽度
    如何控制选择框选项的宽度当选择框中的选项超出框的宽度时,可能会造成混乱以及笨拙的外观。为了解决这个问题,我们可以同时使用 CSS 和 JavaScript 来自定义选项的宽度并截断任何多余的文本。CSS 方法:虽然单独使用 CSS 是不行的足以设置选项的宽度,我们可以利用它来固定选择框本身的宽度。通...
    编程 发布于2024-11-08
  • C++ 异常说明符值得这么麻烦吗?
    C++ 异常说明符值得这么麻烦吗?
    C 中的异常说明符:你应该使用它们吗?C 中的异常说明符允许您指示函数是否可能抛出特定的异常类型。然而,由于担心 Visual Studio .NET 中的编译器执行、程序终止和非标准行为,人们对其实际用途产生了疑问。为什么不使用异常说明符:有限执行:编译器不严格执行异常说明符,从而减少了它们提供的...
    编程 发布于2024-11-08
  • 使用 .EJS 模板配置 Express
    使用 .EJS 模板配置 Express
    通常,我使用经典的入门版。 Expressjs.com const express = require('express') const app = express() const port = 3000 app.set('view engine', 'ejs') app.use(express.u...
    编程 发布于2024-11-08

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3