[譯] 構建高性能和可擴展性 Node.js 應用的最佳實踐 [第 3/3 部分]

第三章 — 其它關於 Node.js 應用運行效率和性能的優秀實踐

本系列的頭兩篇文章中咱們看到如何擴展一個 Node.js 應用以及在應用的代碼部分應該考慮什麼才能使其在這個過程當中運行如咱們所願。在這最後一篇文章中,咱們將介紹一些其它實踐,以進一步提升應用運行效率和性能。前端

Web 和 Worker 進程

就像你可能知道的那樣,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

Kue

爲了使 web 進程和 worker 進程能夠相互通訊,使用隊列是一種靈活的方式,它可使你不須要擔憂進程之間的通訊。算法

Kue 是 Node.js 中經常使用的隊列庫,它基於 Redis 而且讓你能夠用徹底一致的方式讓運行在同一臺或不一樣機器上的進程間相互通訊。數據庫

任何類型的進程均可以建立一個工做並將之放入隊列,而後被配置的相應 worker 進程就會從隊列中提取並執行它。每一個工做都提供了大量的可配置選項,如優先級,TTL,延遲等。

你建立的 worker 進程越多,執行這些做業的並行吞吐量也就越大。

Cron

應用程序一般須要按期執行一些任務。一般這種類型的操做,是經過操做系統級別的 cron 工做進行管理,也就是會調用你應用程序以外的一個單獨腳本。

當須要把你的應用部署到新的機器上時,這種方式會須要額外的配置工做,若是你想要自動化部署應用時,它會讓人對其感到不舒服。

咱們可使用 NPM 上的 cron 模塊從而更輕鬆地實現一樣的效果。它容許你在 Node.js 代碼中定義 cron 工做,從而使其免於操做系統的配置。

根據上面所描述的 web/worker 進程模式,worker 進程能夠經過按期調用一個函數把工做放到隊列從而實現建立 cron。

使用隊列可使 cron 的實現更加清晰而且還能夠利用 Kue 所提供的全部功能,如優先級,重試等。

當你的應用有多個 worker 進程時就會出現一個問題,由於同一時間全部 worker 進程的 cron 函數都會喚醒應用把多個一樣重複的工做放入隊列,從而致使同一個工做將會被執行屢次。

爲了解決這個問題,有必要識別將要執行 cron 操做的單個 worker 進程

Leader 選舉和 cron-cluster

這種類型的問題被稱爲 「leader 選舉」,NPM 爲咱們提供了這種特定狀況下的處理方案,有一個叫作 cron-cluster 的包。

它在維持和 cron 模塊一致 API 的同時加強了模塊,可是在啓動過程當中它須要有 redis 鏈接,用於和其它進程間通訊和執行 leader 選舉算法。

使用 redis 做爲單一事實的來源,全部進程最終都會贊成誰將執行 cron,而且只有一個工做副本會被放入隊列中。在這以後,全部的 worker 進程均可以像往常同樣選擇是否執行這個工做。

緩存 API 調用

服務端緩存是提升你 API 調用性能和反饋性一種經常使用的方式,但這是一個很是普遍的主題,有不少可能的實現。

在像咱們在這個系列所描述的分佈式環境中,若是想要全部的節點在處理緩存時表現一致,最好的辦法或許是使用 redis 來緩存須要的值。

緩存所須要考慮最困難的方面就是緩存失效。一種快捷實用的解決方案是隻考慮緩存時間,這樣緩存中的值就會在固定的 TTL 時間後刷新,這樣作的缺點是咱們不得不等到下一次緩存刷新才能看到響應中的更新。

若是你能有更多的時間,最好在應用級別實現失效,即當數據庫中的值更改時手動刷新 redis 緩存中的相關記錄。

結論

在本系列文章中,咱們介紹了有關擴展性和性能的一些主題。在這裏所提供的建議能夠做爲指導,須要根據項目特定的需求進行定製。

請繼續關注關於 Node.js 和 DevOps 主題內的其它文章!


若是你喜歡這篇文章,請多多支持!

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索