[譯] 構建高效能和可擴充套件性 Node.js 應用的最佳實踐 [第 3/3 部分]
本系列的頭兩篇文章中我們看到 ofollow,noindex">如何擴充套件一個 Node.js 應用 以及 在應用的程式碼部分應該考慮什麼 才能使其在這個過程中執行如我們所願。在這最後一篇文章中,我們將介紹一些其它實踐,以進一步提高應用執行效率和效能。
Web 和 Worker 程序
就像你可能知道的那樣, Node.js 在實際執行中是單執行緒的 ,因此一個程序例項在同一時間只能執行一個操作。在 Web 應用的執行生命週期中,會執行 很多不同型別的任務 :包括管理 API 呼叫,讀/寫資料庫,與外部網路服務通訊,以及不可避免地執行某些 CPU 密集型工作等。
儘管你使用的是非同步程式設計,但是將所有這些操作都指派給同一個用於響應 API 呼叫的程序真的是一種效率很低的方式。
一種常見的模式是基於組成你應用不同型別程序之間的 責任分離 ,這種情況下程序通常被分為 web 程序和 worker 程序。

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

使用 redis 作為單一事實的來源, 所有程序最終都會同意誰將執行 cron ,並且只有一個工作副本會被放入佇列中。在這之後,所有的 worker 程序都可以像往常一樣選擇是否執行這個工作。
快取 API 呼叫
服務端快取是提高你 API 呼叫 效能和反饋性 一種常用的方式,但這是一個非常廣泛的主題,有很多可能的實現。
在像我們在這個系列所描述的分散式環境中,如果想要所有的節點在處理快取時表現一致,最好的辦法或許是使用 redis 來快取需要的值。
快取所需要考慮最困難的方面就是快取失效。一種快捷實用的解決方案是隻考慮快取時間,這樣快取中的值就會在固定的 TTL 時間後重新整理,這樣做的缺點是我們不得不等到下一次快取重新整理才能看到響應中的更新。
如果你能有更多的時間,最好在應用級別實現失效,即當資料庫中的值更改時手動重新整理 redis 快取中的相關記錄。