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

LeviDing發表於2019-03-03

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

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

本系列的頭兩篇文章中我們看到如何擴充套件一個 Node.js 應用以及在應用的程式碼部分應該考慮什麼才能使其在這個過程中執行如我們所願。在這最後一篇文章中,我們將介紹一些其它實踐,以進一步提高應用執行效率和效能。

Web 和 Worker 程式

就像你可能知道的那樣,Node.js 在實際執行中是單執行緒的,因此一個程式例項在同一時間只能執行一個操作。在 Web 應用的執行生命週期中,會執行很多不同型別的任務:包括管理 API 呼叫,讀/寫資料庫,與外部網路服務通訊,以及不可避免地執行某些 CPU 密集型工作等。

儘管你使用的是非同步程式設計,但是將所有這些操作都指派給同一個用於響應 API 呼叫的程式真的是一種效率很低的方式。

一種常見的模式是基於組成你應用不同型別程式之間的責任分離,這種情況下程式通常被分為 web 程式和 worker 程式。

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

Web 程式主要的任務是管理傳入的網路呼叫並儘快將它們分發出去。每當一個非阻塞任務需要被執行時,例如傳送電子郵件/通知,寫日誌,執行一個觸發操作,它們都不需要馬上響應 API 呼叫返回結果,Web 程式會把這些操作委派給 worker 程式。

web 和 worker 程式之間的通訊可以通過不同的方式實現。一種常見且有效的解決方案是優先順序佇列,就像我們將在下一段描述的 Kue 所實現的那樣。

這種方式有一個很大的優點,無論在同一臺還是不同機器上其都可以分別獨立擴充套件 web 和 worker 程式

例如,如果你的應用請求量很大,相較於 worker 程式你可以部署更多的 web 程式而幾乎不會產生任何副作用。而如果請求量不是很大但是有很多的工作需要 worker 程式去處理,你可以據此重新分配相應的資源。

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 選舉演算法。

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

使用 redis 作為單一事實的來源,所有程式最終都會同意誰將執行 cron,並且只有一個工作副本會被放入佇列中。在這之後,所有的 worker 程式都可以像往常一樣選擇是否執行這個工作。

快取 API 呼叫

服務端快取是提高你 API 呼叫效能和反饋性一種常用的方式,但這是一個非常廣泛的主題,有很多可能的實現。

在像我們在這個系列所描述的分散式環境中,如果想要所有的節點在處理快取時表現一致,最好的辦法或許是使用 redis 來快取需要的值。

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

如果你能有更多的時間,最好在應用級別實現失效,即當資料庫中的值更改時手動重新整理 redis 快取中的相關記錄。

結論

在本系列文章中,我們介紹了有關擴充套件性和效能的一些主題。在這裡所提供的建議可以作為指導,需要根據專案特定的需求進行定製。

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


如果你喜歡這篇文章,請多多支援!

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


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

相關文章