- 原文地址:Good practices for high-performance and scalable Node.js applications [Part 3/3]
- 原文做者:virgafox
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:steinliber
- 校对者:calpa, Augustwuli
本系列的头两篇文章中咱们看到如何扩展一个 Node.js 应用以及在应用的代码部分应该考虑什么才能使其在这个过程当中运行如咱们所愿。在这最后一篇文章中,咱们将介绍一些其它实践,以进一步提升应用运行效率和性能。前端
就像你可能知道的那样,Node.js 在实际运行中是单线程的,所以一个进程实例在同一时间只能执行一个操做。在 Web 应用的运行生命周期中,会执行不少不一样类型的任务:包括管理 API 调用,读/写数据库,与外部网络服务通讯,以及不可避免地执行某些 CPU 密集型工做等。node
尽管你使用的是异步编程,可是将全部这些操做都指派给同一个用于响应 API 调用的进程真的是一种效率很低的方式。android
一种常见的模式是基于组成你应用不一样类型进程之间的责任分离,这种状况下进程一般被分为 web 进程和 worker 进程。ios
Web 进程主要的任务是管理传入的网络调用并尽快将它们分发出去。每当一个非阻塞任务须要被执行时,例如发送电子邮件/通知,写日志,执行一个触发操做,它们都不须要立刻响应 API 调用返回结果,Web 进程会把这些操做委派给 worker 进程。git
web 和 worker 进程之间的通讯能够经过不一样的方式实现。一种常见且有效的解决方案是优先级队列,就像咱们将在下一段描述的 Kue 所实现的那样。github
这种方式有一个很大的优势,不管在同一台仍是不一样机器上其均可以分别独立扩展 web 和 worker 进程。web
例如,若是你的应用请求量很大,相较于 worker 进程你能够部署更多的 web 进程而几乎不会产生任何反作用。而若是请求量不是很大可是有不少的工做须要 worker 进程去处理,你能够据此从新分配相应的资源。redis
为了使 web 进程和 worker 进程能够相互通讯,使用队列是一种灵活的方式,它可使你不须要担忧进程之间的通讯。算法
Kue 是 Node.js 中经常使用的队列库,它基于 Redis 而且让你能够用彻底一致的方式让运行在同一台或不一样机器上的进程间相互通讯。数据库
任何类型的进程均可以建立一个工做并将之放入队列,而后被配置的相应 worker 进程就会从队列中提取并执行它。每一个工做都提供了大量的可配置选项,如优先级,TTL,延迟等。
你建立的 worker 进程越多,执行这些做业的并行吞吐量也就越大。
应用程序一般须要按期执行一些任务。一般这种类型的操做,是经过操做系统级别的 cron 工做进行管理,也就是会调用你应用程序以外的一个单独脚本。
当须要把你的应用部署到新的机器上时,这种方式会须要额外的配置工做,若是你想要自动化部署应用时,它会让人对其感到不舒服。
咱们可使用 NPM 上的 cron 模块从而更轻松地实现一样的效果。它容许你在 Node.js 代码中定义 cron 工做,从而使其免于操做系统的配置。
根据上面所描述的 web/worker 进程模式,worker 进程能够经过按期调用一个函数把工做放到队列从而实现建立 cron。
使用队列可使 cron 的实现更加清晰而且还能够利用 Kue 所提供的全部功能,如优先级,重试等。
当你的应用有多个 worker 进程时就会出现一个问题,由于同一时间全部 worker 进程的 cron 函数都会唤醒应用把多个一样重复的工做放入队列,从而致使同一个工做将会被执行屡次。
为了解决这个问题,有必要识别将要执行 cron 操做的单个 worker 进程。
这种类型的问题被称为 “leader 选举”,NPM 为咱们提供了这种特定状况下的处理方案,有一个叫作 cron-cluster 的包。
它在维持和 cron 模块一致 API 的同时加强了模块,可是在启动过程当中它须要有 redis 链接,用于和其它进程间通讯和执行 leader 选举算法。
使用 redis 做为单一事实的来源,全部进程最终都会赞成谁将执行 cron,而且只有一个工做副本会被放入队列中。在这以后,全部的 worker 进程均可以像往常同样选择是否执行这个工做。
服务端缓存是提升你 API 调用性能和反馈性一种经常使用的方式,但这是一个很是普遍的主题,有不少可能的实现。
在像咱们在这个系列所描述的分布式环境中,若是想要全部的节点在处理缓存时表现一致,最好的办法或许是使用 redis 来缓存须要的值。
缓存所须要考虑最困难的方面就是缓存失效。一种快捷实用的解决方案是只考虑缓存时间,这样缓存中的值就会在固定的 TTL 时间后刷新,这样作的缺点是咱们不得不等到下一次缓存刷新才能看到响应中的更新。
若是你能有更多的时间,最好在应用级别实现失效,即当数据库中的值更改时手动刷新 redis 缓存中的相关记录。
在本系列文章中,咱们介绍了有关扩展性和性能的一些主题。在这里所提供的建议能够做为指导,须要根据项目特定的需求进行定制。
请继续关注关于 Node.js 和 DevOps 主题内的其它文章!
若是你喜欢这篇文章,请多多支持!
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。