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

使用 react、nodejs 和 mongodb 构建高性能全栈应用程序:可扩展性、速度和解决方案之旅

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

去过吗?是的,我也是。

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

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

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

1:当 react 成为瓶颈

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

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

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

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

const dashboard = react.lazy(() => import('./dashboard'));const profile = react.lazy(() => import('./profile'));<suspense fallback={
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 应用程序的需求增长不会放缓。如果我们继续专注于构建可扩展、性能优化和架构良好的应用程序,那么我们就有能力解决今天的任何问题,并在未来利用这些其他挑战。这些现实生活中的经历极大地影响了我进行全栈开发的方式——我迫不及待地想看看这些影响将继续推动我们的行业走向何方!

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

以上就是使用 React、Nodejs 和 MongoDB 构建高性能全栈应用程序:可扩展性、速度和解决方案之旅的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1493098.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
JavaScript ESelease 笔记:释放现代 JavaScript 的力量
上一篇 2025年12月19日 15:24:03
掌握 JavaScript Promise:Polyfill 和高级技术指南
下一篇 2025年12月19日 15:24:14

相关推荐

  • Go语言mgo查询构建:深入理解bson.M与日期范围查询的正确实践

    本文旨在解决go语言mgo库中构建复杂查询时,特别是涉及嵌套`bson.m`和日期范围筛选的常见错误。我们将深入剖析`bson.m`的类型特性,解释为何直接索引`interface{}`会导致“invalid operation”错误,并提供一种推荐的、结构清晰的代码重构方案,以确保查询条件能够正确…

    2026年5月10日
    100
  • Python中怎样使用pymongo?

    在python中使用pymongo可以轻松地与mongodb数据库进行交互。1)安装pymongo:pip install pymongo。2)连接到mongodb:from pymongo import mongoclient; client = mongoclient(‘mongod…

    2026年5月10日
    000
  • Golang空接口如何应用在项目中

    空接口可用于接收任意类型值,常见于日志函数、通用数据结构、JSON动态解析及配置驱动逻辑,提升代码灵活性,但需配合类型断言确保安全,避免滥用以降低维护成本。 空接口 interface{} 在 Go 语言中是一个非常灵活的类型,它可以存储任何类型的值。虽然它牺牲了一部分类型安全,但在实际项目中合理使…

    2026年5月10日
    100
  • Go语言接口与切片:如何识别和操作[]interface{}

    本文将深入探讨Go语言中如何识别和操作`[]interface{}`类型的切片。我们将介绍类型断言(Type Assertion)的关键作用,并通过`switch`语句演示如何安全地检测`[]interface{}`类型,并进而遍历其内部元素。文章旨在提供清晰的示例代码和专业指导,帮助开发者有效地处…

    2026年5月10日
    000
  • JavaScript计算器开发:解决数值显示与初始化问题

    本教程深入探讨了使用JavaScript构建计算器时常见的数值显示异常问题,特别是由于类属性未初始化导致的`Cannot read properties of undefined`错误。我们将详细分析问题根源,并通过在构造函数中调用初始化方法来解决该问题,同时优化显示逻辑,确保计算器功能稳定且界面显…

    2026年5月10日
    000
  • JavaScript动态下拉菜单:实现日期选项与价格计算关联

    在现代web应用中,动态生成表单元素并使其具备交互逻辑是常见的需求。特别是在需要根据用户选择调整价格或服务参数的场景下,下拉菜单()常被用来展示一系列选项。本教程将指导您如何利用javascript动态生成一个包含日期选项的下拉菜单,并为每个选项关联一个具体的数值(如剩余天数),进而实现一个基于用户…

    2026年5月10日
    000
  • 怎么安装html5_HTML5开发环境安装与配置详细步骤

    答案是配置HTML5开发环境需三步:1. 安装VS Code等编辑器并配置插件;2. 使用Chrome或Firefox测试页面;3. 可选搭建本地服务器,如Live Server或http-server;最后创建index.html文件验证环境。 安装HTML5开发环境其实并不复杂,因为HTML5本…

    2026年5月10日
    000
  • Go语言中实现策略模式:灵活处理多源数据与格式转换

    本文探讨了如何在go语言中实现策略模式,以优雅地处理多源数据收集与多格式数据转换的场景。通过定义清晰的接口和具体的策略实现,结合go语言简洁的特性,展示了两种将策略集成到工作流中的方法,强调了go中接口驱动的灵活性。 在软件开发中,我们经常面临需要处理多种算法或行为,并根据具体情况选择其中之一的场景…

    2026年5月10日
    000
  • JS如何实现策略模式

    策略模式通过封装算法使其可互换,JavaScript中利用函数作为一等公民实现,适用于表单验证等场景,结合工厂模式提升灵活性,但应避免过度设计。 策略模式的核心在于定义一系列算法,并将每一个算法封装起来,使它们可以相互替换。这使得算法可以在不影响客户端的情况下发生变化。在JS中,这可以通过函数作为一…

    2026年5月10日
    000
  • 优化React-Redux应用中的用户与受保护数据按需加载

    本教程旨在解决React-Redux应用中用户数据和受保护API密钥在用户未登录时仍被请求,导致401错误的问题。通过引入条件性Redux状态初始化和动作分发逻辑,确保只有在用户被认为已认证时才发起相关的API请求,从而优化应用性能,减少不必要的网络流量和控制台错误。 在构建现代Web应用时,尤其是…

    2026年5月10日
    000
  • Go语言并发二叉树遍历:通道关闭与等价性判断的优雅方案

    本文探讨了在Go语言中并发遍历二叉树时,如何正确处理通道(channel)的关闭时机问题,尤其是在递归函数中。通过结合defer语句和闭包(closure)的巧妙运用,提供了一种优雅且健壮的解决方案,确保通道在所有值发送完毕后才被关闭,进而实现两个二叉树的等价性判断。 1. 并发遍历二叉树的需求与挑…

    2026年5月10日
    000
  • Golang环境变量配置自动化脚本方法

    答案:通过编写Shell脚本自动化配置Go环境变量,可实现GOROOT、GOPATH、GOBIN及PATH等变量的自动设置,提升开发效率。具体做法是创建setup_go_env.sh脚本,定义GOROOT为Go安装路径(如/usr/local/go),GOPATH为工作区(如~/go_project…

    2026年5月10日
    100
  • 深入理解Go语言中的短声明:=与长声明var

    Go语言提供了两种主要的变量声明和初始化方式:短声明:=和长声明var。:=主要用于函数内部,实现变量的声明与初始化,并常用于控制流语句中以限制变量作用域,例如在if语句中处理错误。而var则更为通用,可用于包级别或函数内部,支持显式类型声明、不带初始化的声明以及批量声明,提供了更大的灵活性。 1.…

    2026年5月10日
    000
  • 异步与延迟:脚本加载的简单说明

    在网站中加载 javascript 时,了解不同的加载方法如何影响网站的性能和行为非常重要。 javascript 可以通过多种方式加载,主要使用默认加载方法、async 和 defer。这些方法中的每一种都有其自己的特点和用例。在这篇文章中,我们将探讨这三种方法,以帮助您为项目做出明智的决策。 默…

    2026年5月10日
    000
  • .NET中的仓储模式(Repository Pattern)是什么?如何解耦业务逻辑和数据访问?

    仓储模式是.NET中用于分离业务逻辑与数据访问的抽象层,通过定义如IUserRepository接口并结合依赖注入,实现对数据访问的具体技术解耦;业务逻辑仅依赖接口,可通过SqlUserRepository等具体实现操作数据库,而无需知晓底层细节;该模式提升可维护性、支持单元测试、降低耦合,并可配合…

    2026年5月10日
    000
  • JavaScript实现多币种价格转换教程

    本教程详细讲解如何使用JavaScript实现多币种价格转换功能。文章将涵盖从远程API获取汇率数据、处理页面上多个价格元素的转换,以及如何避免重复转换导致的错误。核心在于利用`querySelectorAll`选取所有相关元素,并维护原始价格值以确保每次转换都基于准确的初始数据,从而实现稳定、准确…

    2026年5月10日
    000
  • c++如何实现一个单例模式_c++设计模式之单例模式实现方法

    单例模式确保一个类仅有一个实例并提供全局访问点。C++中常见实现包括:懒汉式(线程不安全,延迟创建但多线程下可能重复实例化);加锁的懒汉式(线程安全但性能开销大);双重检查锁定(减少锁开销,需注意内存模型和原子性);局部静态变量(C++11起线程安全、简洁、自动管理内存,推荐方式)。选择依据为线程安…

    2026年5月10日
    000
  • Golang初学者如何用flag包开发一个功能完整的命令行工具

    Go语言flag包可用于解析命令行参数,支持布尔、字符串、整数等类型,通过flag.Type或flag.TypeVar定义参数,结合flag.Parse实现输入解析。示例中定义了-name和-v参数,运行时输出问候语和详细信息。支持多种定义方式:flag.Type返回指针,flag.TypeVar绑…

    2026年5月10日
    000
  • Golang如何构建简易的笔记应用

    答案是一个基于Golang的简易笔记应用,通过结构体定义笔记并以JSON格式存储;实现添加、列出和搜索笔记功能,结合标准库进行文件操作与命令行解析,支持后续扩展如删除、数据库升级等。 用Golang构建一个简易的笔记应用并不复杂,重点在于设计清晰的结构和使用标准库高效处理文件操作与命令行交互。下面是…

    2026年5月10日
    000
  • Golang构建HTTP服务步骤 net/http包基础用法

    Go语言通过net/http包可快速构建HTTP服务,核心步骤为:定义处理器函数处理请求、使用http.HandleFunc注册路由、调用http.ListenAndServe启动服务。处理器通过检查r.Method区分GET、POST等请求方法,利用r.URL.Query()获取查询参数,读取r.…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信