使用MongoDB,Redis和Node.js优化应用程序性能的简单步骤

https://getstream.io/try-the-api/

第一件事第一

Stream ,我们为300+百万最终用户提供活动供稿,并热衷于尝试在各种用例中利用我们的产品的方式。 最近,我们构建了Winds,这是一个开源RSS和Podcast应用程序,重点关注UI和UX。

在2018年5月下旬启动第二次Winds迭代后不久,我们开始看到用户注册量大幅增加。 当然,大多数示例应用程序并不会获得这么多的流量。 但是,Winds Winds在Hacker NewsProduct Hunt风靡一时 。 到那时,示例应用程序代码还没有非常优化。 这篇博客文章涵盖了一些快速的初学者提示,可以帮助您提高应用程序的性能。

在本文中,我们将讨论如何通过优化数据库架构和添加缓存来减少API调用延迟,以及如何实现和改进其他工具。

https://getstream.io/winds

可视化和优化API调用

对问题进行分类的第一步是了解潜在问题。 如果您没有使用正确的工具来帮助您可视化代码库中发生的事情,那么这可能是一项艰巨的任务。 对于Winds,我们利用工具的组合来更好地了解生产环境中发生的事情,从而使我们能够确定延迟问题和错误。 以下是我们使用哪些工具以及我们为什么喜欢它们的简要概述。

新遗物

New Relic提供了有关我们的请求和响应基础结构的全面视图,使我们的团队可以更好地了解和查明慢速路线。 使用Node.js(我们选择的语言)和其他语言,实现非常容易-只需导入包并放入密钥即可; New Relic从那里拿走它。

下面是New Relic的野生的截图(你会发现,我们还没有优化了POST / RSS端点尚未 -新文物如何让我们看到了哪些终端需要优化的一个很好的例子)。

https://newrelic.com

在上方屏幕截图的“交易”下,您会看到路线列表。 这些是API路由,是从应用程序到API,数据库再返回的完整往返时间(以毫秒为单位)。 了解隔离所需的时间对于隔离缓慢的响应时间非常重要。

维克多

这个工具很棒VictorOps允许我们的团队将其他团队成员分配给“待命”,并且,如果/如果出现问题(例如API出现故障),VictorOps将在Slack上对用户执行ping操作并发送SMS消息以引起他们的注意。 此过程加快了解决时间,并使团队中的每个人都知道应用程序是否存在问题。 以下是我们的帐户信息中心的屏幕截图:

https://victorops.com

统计数据

StatsD最初由Etsy编写,是一组工具,可用于从您的应用程序发送,收集和汇总自定义指标。 该名称既指原始守护程序中使用的协议,也指实现该协议的软件和服务的集合。

StatsD系统需要三个组件:客户端,服务器和后端。 客户端是一个在我们的Winds应用程序代码中调用的库,用于发送指标。 这些指标由StatsD服务器收集。

服务器会汇总这些指标,然后定期将汇总的数据发送到后端。 然后,后端使用我们的数据执行各种任务-例如, Grafana (在Grafana部分中显示)用于查看Winds 工人的实时图(RSS,播客,开放图等)和其他重要的基础结构指标。

为了收集数据,我们使用一个称为node-statsd的节点库。 使用该库,我们创建了一个帮助程序文件以保持我们的代码干净:

我们可以这样调用util(在所有进行statsd注释的地方):

请务必注意,您可以在StatsD中使用以下指标类型

  • 计数器 -它们代表某个时间的值或某个事件发生的次数(您可以设置或递增/递减)。 StatsD将计算平均值/平均值,百分位数等)
  • 量表 -与计数器类似,但代表的不是时间值,而是一个值(可用的最新数据点)
  • 计时器 -类似于计数器,但用于测量操作花费的时间而不是操作发生的次数

石墨

石墨由三个软件组件组成:Carbon,Whisper和Graphite Web。 Carbon是一项高性能服务,可监听StatsD发出的时间序列数据。 Whisper是一个用于存储时间序列数据的简单数据库库,而石墨网是Graphite的用户界面和API,用于渲染图形和仪表板(尽管我们不使用Graphite Web进行项目,因为我们使用Grafana)。

更明确地说,来自StatsD的度量标准通过Carbon服务馈送到堆栈中,该服务将数据写到Whisper数据库中以进行长期存储。 然后,我们使用Graphite Web提供的API来可视化Grafana中的数据。

可以在此处找到有关Graphite的详细实现和其他信息。

https://graphiteapp.org/

格拉法纳

Grafana是用于时间序列分析的领先开源软件,这就是为什么我们选择将其用于Winds以及通常在Stream上使用的原因。 Grafana允许我们的团队查询,可视化,警告和了解从Graphite检索的Winds指标。 它充满了很棒的可视化工具(如下面的屏幕快照所示),使我们的团队可以选择要显示数据的方式。

https://grafana.com/

优化我们的MongoDB数据库

对于我们的主要数据存储,我们使用MongoDB Atlas -MongoDB 提供的 MongoDB托管版本。 这使Winds无需管理即可拥有完整的副本集(3个服务器)。 服务器会自动升级并自动打补丁,这非常方便。 MongoDB Atlas还执行常规备份,并提供副本集的时间点备份。 不幸的是,这种奢侈是有代价的。 但是,这是我们愿意承担的,因此我们可以专注于改善Winds的用户体验。

尽管MongoDB Atlas负责繁重的数据库管理。 这并不意味着它可以解决所有问题。 文档需要适当地组织和建立索引,并且我们需要确保将读写操作保持在最低限度,以便不使用每秒分配的所有输入/输出操作(IOPS)和连接。

https://cloud.mongodb.com

索引用法

索引不正确或根本没有索引通常是导致应用程序延迟增加的主要原因。 没有索引,MongoDB必须执行集合扫描,即扫描集合中的每个文档,以选择与查询语句匹配的那些文档。

如MongoDB所述,索引是特殊的数据结构,以易于遍历的形式存储集合数据集的一小部分。 索引存储一个特定字段或一组字段的值,按该字段的值排序。 索引条目的排序支持有效的相等匹配和基于范围的查询操作。 另外,MongoDB可以使用索引中的顺序返回排序的结果。

下图说明了使用索引选择和排序匹配文档的查询:

散列索引

在Winds中,我们几乎拥有所有适当的索引,但是,我们未能认识到MongoDB中的一项主要要求/约束​​。 索引条目的总大小必须小于1024个字节。 并不是很难确保...对吗? 嗯,事实是,我们为URL编制索引以加快查找过程,并且进入Winds API的某些URL超过1024个字节。 这导致了各种意外错误。

经过大量研究,我们找到了一个解决方案- 哈希索引 。 散列索引使用索引字段值的散列来维护条目。 这意味着我们可以像以前一样在相同的URL上进行查找,而不必担心其长度并仍保持完整性。

注意:使用哈希索引解析查询时,MongoDB自动计算哈希值。 应用程序不需要计算哈希。

单一索引

除默认的_id索引外,MongoDB还支持在文档的单个字段上创建用户定义的升序/降序索引。 这将是您最常用的索引类型。

对于单字段索引和排序操作,索引键的排序顺序(即升序或降序)无关紧要,因为MongoDB可以沿任一方向遍历索引。

复合索引

与其他数据库类似,MongoDB支持复合索引,其中单个索引结构包含对多个字段的引用。

注意:您不能创建具有哈希索引类型的复合索引。 如果尝试创建包含哈希索引字段的复合索引,则会收到错误消息。

要创建复合索引,请使用类似于以下内容的操作:

或者,如果您使用的是Mongoose,则需要在架构级别定义索引。 这是一个例子:

注意:上面显示了添加复合索引的示例,类似于添加单个索引。 唯一的区别是,对于单个索引,您只有一个键,其值为1或-1。

索引中字段的值描述了该字段的索引类型。 例如,值1指定一个索引,该索引按升序对项目进行排序。 值-1指定一个索引,该索引按降序对项目进行排序。

注意:如果您想深入了解MongoDB中的索引, 这里是一个很好的资源 。 关于性能索引,还有很棒的SlideShare

确保指标

MongoDB提供的一项功能是createIndex()操作。 这样做是遍历数据库中的每个文档,并“确保”它具有正确的索引。 许多人没有意识到这是数据库上非常繁重的工作,占用了宝贵的CPU和内存,从而降低了数据库的速度。

如果您对索引进行更新,则需要运行createIndex()操作; 但是,我们建议您在非高峰时段执行此操作,因为您的数据库不会受到大量请求/秒的影响。

要在Mongoose中启用此功能,请查看此处的文档。

注意:如果您想阅读createIndex()操作,MongoDB拥有关于该主题的大量文档

通过查询变得聪明

减少对数据库的查询是您可以提高的第一性能。 使用New Relic,我们发现用户注册路线非常慢。 单个注册API调用转化为对Stream API的约60个请求,以及对数据库的几次调用,以获取兴趣。 在调查了这个问题之后,我们意识到我们可以做两件事来减少请求总数:

  1. 在Redis中缓存兴趣(接下来要介绍),而不是针对每个用户注册对数据库进行N次API调用
  2. 对第三方服务(例如Stream)的批处理API调用

另一个常见错误是执行N次简单查询,而不是一次读取所有必需记录的查询。 从Stream读取提要时,我们只需使用MongoDB $ in运算符即可检索所有ID值存储在Stream中的文章。

精益查询

对于我们的ODM,我们使用了Mongoose,Mongoose是功能强大且易于使用的功能,位于MongoDB驱动程序之上。 这使我们能够执行对象模型验证,转换和业务逻辑,从而在整个API代码库中提供一致性。 唯一的缺点是,每个查询都会返回一个带有getter / setter方法以及我们有时不需要的其他Mongoose魔术的沉重的Mongoose对象。

为了避免有时在查询响应中看到额外的开销,我们使用了lean() 一些查询的选项。 这产生了一个普通的JavaScript对象,然后可以将其转换为JSON作为响应有效负载。 就像执行以下操作一样简单:

解释查询

$ explain运算符非常方便。 顾名思义,它返回一个文档,该文档描述了用于返回查询的过程和索引。 这在尝试优化查询时提供了有用的见解(例如,添加哪些索引以加快查询速度)。

$ explain运算符上的此文档指定了如何实现查询。

雷迪斯

Redis是一个内存中的数据结构存储。 它可以用作数据库,缓存和消息代理。 在我看来,Redis的是最容易被忽视的数据存储在那里的一个-只要它是你可以存放什么非常灵活。 对于Winds,我们将其广泛用作缓存,以避免不必要的数据库查找。

例如,在Winds的注册步骤中,我们会询问您您的兴趣是什么,以便我们可以使用机器学习来根据您的喜好打造用户体验。 此步骤曾经要求调用数据库以获取数据库ID,以便随后将其存储。 现在,我们仅将兴趣存储为JSON,而是对Redis进行查找。 由于Redis将所有内容存储在内存中,因此这是一项非常轻量级的任务,几乎没有增加延迟。

https://winds.getstream.io

这是我们如何在Winds中存储兴趣的示例:

您的应用程序的一些关键要点:

将Redis用于以下用途

  • 存储流行且经常查询的数据以使数据库查询保持关闭状态(查询内存比让数据库对磁盘进行查询要高效得多)。
  • 将数据缓存有效的时间(例如,对于Winds,为60分钟)并使用自动过期(默认情况下)。
  • 将您的缓存密钥与您的应用程序版本号相关联(例如, interest:v2.0.0 )。 当您将更新部署到应用程序时,这将强制重新缓存,从而避免潜在的错误和缓存不匹配。
  • 对数据进行字符串化处理,以确保可以将其存储在Redis中(仅是键值,并且两者都必须是字符串。

公牛队

Bull是位于Redis之上的出色的排队系统。 我们使用Bull作为Winds中的工作人员基础结构,并使用几个队列来处理(抓取)数据:

  • 的RSS
  • 播客
  • 打开图

最重要的是,我们选择Bull的原因如下

  • 免轮询设计,CPU使用率最低
  • 基于Redis的稳健设计
  • 延迟工作
  • 限速器
  • 重试
  • 优先
  • 并发
  • 每个队列有多种作业类型
  • 线程化(沙盒)处理功能
  • 从进程崩溃中自动恢复

我们对提要和开放式图表抓取使用排队机制,因为它将流程分开,因此在没有API性能的情况下它们不会冲突。 通常,这是最佳做法,因为您不希望在频繁解析10,000个或更多供稿时挂起API。

在构建API或应用服务器时,重要的是退后一步并询问任务是否会妨碍您的响应时间。 响应时间最多为250毫秒,即使这个数字也很慢。 如有疑问,请将其放入队列并在单独的线程上进行处理。

注意:这是我们队列的快速屏幕截图。 这显示了队列中活动RSS,Podcast和Open Graph作业的数量及其状态。 Bull通过API提供了所有这些数据,使我们能够更好地了解幕后发生的事情。 您还可以使用其他第三方UI进行监视,例如TaskforceArena

使用专用解决方案

与Bull减轻处理负担的方式类似,使用专用解决方案加速您的应用程序可能具有不可思议的价值。 例如,我们使用我们自己的服务Stream ,以确保用户关注必要的提要并在将它们放入系统时接收更新。 从头开始构建它可能要花费数月,甚至数年。 但是,通过使用服务,我们能够在数小时内实施。

我们喜欢的另一个第三方服务是Algolia 。 Algolia在Winds中为我们的搜索功能提供了强大的支持,使查找闪电般快(少于50毫秒的查找时间)。 尽管我们可以使用专用终结点和精美的MongoDB查询构建自己的搜索,但我们无法扩展搜索功能并保持Algolia提供的相同速度,并且在用户被使用时会消耗掉宝贵的API资源。执行搜索。

这两种解决方案均远胜于您可以使用通用数据库在内部构建的解决方案。

最后的想法

我们希望您喜欢此基本提示列表,以改善Node应用程序的性能。 大多数现实世界中的性能问题都是由简单的事情引起的。 这篇文章列出了一些常见的解决方案:

  • APM工具,例如New Relic,StatsD和Grafana
  • 索引使用和查询优化
  • Redis和缓存
  • 异步任务
  • 使用专门的数据存储进行搜索和提要

如果您正在寻找更高级的主题,请查看有关Stream如何使用RocksDB,Go和Raft为超过3亿用户提供提要的帖子。

如果您要构建需要新闻源或活动源的应用程序,请查看Stream。 我们有一个5分钟的教程,它将带您逐步了解我们涵盖的各种用例。 API教程可在此处找到。

如果您有任何疑问,请在下面的评论中将其删除。 如果您有兴趣在Twitter上关注我,请使用 @nickparsons

编码愉快!

From: https://hackernoon.com/simple-steps-to-optimize-your-app-performance-5700d8b58f58

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值