天天看點

[譯] 所有你需要知道的關于完全了解 Node.js 事件循環及其度量

Node.js 是一個基于事件的平台。這意味着在 Node 中發生的一切都是基于對事件的反應。通過 Node 的事件處理機制周遊一系列回調。

事件的回調,這一切都由一個名為 libuv 的庫來處理,它提供了一種稱為事件循環的機制。

這個事件循環可能是平台中最被誤解的概念。當我們提及事件循環監測的主題時,我們花了很多精力來正确地了解我們實際監視的内容。

在本文中,我将帶大家重新認知事件循環是如何工作以及它是如何正确地監視。

常見的誤解

Libuv 是向 Node.js 提供事件循環的庫。在 libuv 背後的關鍵人物 Bert Belder 的精彩的演講

Node 互動的主題演講

中,演講開頭他使用 Google 圖像搜尋展示了各種不同方式描述事件循環的圖檔,但是他指出大部分圖檔描繪的都是錯誤的。

[譯] 所有你需要知道的關于完全了解 Node.js 事件循環及其度量

讓我們來看看最流行的誤解。

誤解1:在使用者代碼中,事件循環在單獨的線程中運作

誤解

使用者的 JavaScript 代碼運作在主線程上面,而另開一個線程運作事件循環。每次異步操作發生時,主線程将把工作交給事件循環線程,一旦完成,事件循環線程将通知主線程執行回調。

現實

隻有一個線程執行 JavaScript 代碼,事件循環也運作在這個線程上面。回調的執行(在運作的 Node.js 應用程式中被傳入、後又被調用的代碼都是一個回調)是由事件循環完成地。稍後我們會深入讨論。

誤解2:異步的所有内容都由線程池處理

異步操作,像操作檔案系統,向外發送 HTTP 請求以及與資料庫通信等都是由 libuv 提供的線程池處理的。

Libuv 預設使用四個線程建立一個線程池來完成異步工作。今天的作業系統已經為許多 I/O 任務提供了異步接口(

例子 AIO on Linux

)。

隻要有可能,libuv 将使用這些異步接口,避免使用線程池。

這同樣适用于像資料庫這樣的第三方子系統。在這裡,驅動程式的作者甯願使用異步接口,而不是使用線程池。

簡而言之:隻有沒有其他方式可以使用時,線程池才将會被用于異步 I/O 。

誤解3:事件循環類似棧或隊列

事件循環采用先進先出的方式執行異步任務,類似于隊列,當一個任務執行完畢後調用對應的回調函數。

雖然涉及到類似隊列的結構,事件循環并不是采用棧的方式處理任務。事件循環作為一個程序被劃分為多個階段,每個階段處理一些特定任務,各階段輪詢排程。

了解事件循環周期的階段

為了真正地了解事件循環,我們必須明白各個階段都完成了哪些工作。 希望 Bert Belder 不介意,我直接拿了他的圖檔來說明事件循環是如何工作的:

[譯] 所有你需要知道的關于完全了解 Node.js 事件循環及其度量

事件循環的執行可以分成 5 個階段,讓我們來讨論這些階段。更加深入的解釋見

Node.js 官網

計時器

通過 setTimeout() 和 setInterval() 注冊的回調會在此處處理。

IO 回調

大部分回調将在這部分被處理。Node.js 中大多數使用者代碼都在回調中處理(例如,對傳入的 http 請求觸發級聯的回調)。

IO 輪詢

對接着要處理的的事件進行新的輪詢。

Immediate 設定

此處處理所有由 setImmediate() 注冊的回調。

結束

這裡處理所有‘結束’事件的回調。

監測事件循環

我們看到,事實上在 Node 應用程式中進行的所有事件都将通過事件循環運作。這意味着如果我們可以從中獲得名額,相應地我們可以分析出有關應用程式整體運作狀況和性能的寶貴資訊。

沒有現成的 API 可以從事件循環中擷取運作時名額,是以每個監控工具都提供自己的名額,讓我們來看看都有些什麼。

記錄頻率

每次的記錄數。

記錄持續時間

一個刻度的時間。

由于我們的代理作為本機子產品運作,是以這是比較容易地添加探測器為我們提供這些資訊。

記錄頻率以及記錄持續事件名額

當我們在不同的負載下進行第一次測試時,結果令人驚訝 - 讓我舉例說明一下:

在以下情況下,我正在調用一個 express.js 應用程式,對其他 http 伺服器進行外撥呼叫。

有以下 4 中情況:

  1. Idle

沒有傳入請求

  1. ab -c 5

使用 apache bench 工具我一次建立了 5 個并發請求

  1. ab -c 10

一次 10 個并發請求

  1. ab -c 10 (slow backend)

為了模拟出一個很慢的後端,我們讓被調用的 http 伺服器在 1s 後傳回資料。這樣造成請求等待後端傳回資料,被堆積在 Node 中,産生背壓。

[譯] 所有你需要知道的關于完全了解 Node.js 事件循環及其度量

事件循環執行階段

如果我們看看得到的圖表,我們可以做一個有趣的觀察:

事件循環持續時間和被動态調整頻率

如果應用程式處于空閑狀态,這意味着沒有執行任何任務(定時器、回調等),此時全速運作這些階段是沒有意義的,事件循環就這種情況會在在輪詢階段阻塞一段時間以等待新的外部事件進入。

這也意味着,無負載下的度量(低頻,高持續時間)與在高負載下與慢後端相關的應用程式相似。

我們還看到,該示範應用程式在場景中運作得“最好”的是并發 5 個請求。

是以,标記頻率和标記持續時間需要基于每秒并發請求量進行度量。

雖然這些資料已經為我們提供了一些有價值的見解,但我們仍然不知道在哪個階段花費時間,是以我們進一步研究并提出了另外兩個名額。

工作處理延遲

這個度量衡量線程池處理異步任務所需的時間。

高工作處理的延遲表示一個繁忙/耗盡的線程池。

為了測試這個名額,我建立了一個使用

Sharp

的子產品來處理圖像的 express 路由。 由于圖像處理開銷太大,Sharp 利用線程池來實作。

[譯] 所有你需要知道的關于完全了解 Node.js 事件循環及其度量

通過 Apache bench 發起 5 個并發請求到具有圖像處理功能的路由與沒有使用圖檔處理的路由有很大不同,可以直接從圖表上可以看到。

事件循環延遲

事件循環延遲測量在通過 setTimeout(X) 排程的任務真正得到處理之前需要多長時間。

事件循環高延遲表示事件循環正忙于處理回調。

為了測試這個名額,我建立了一個 express 路由使用了一個非常低效的算法來計算斐波那契。

[譯] 所有你需要知道的關于完全了解 Node.js 事件循環及其度量

運作具有 5 個并發連接配接的 Apache bench,具有計算斐波那契功能的路由顯示此刻回調隊列處于繁忙狀态。

我們清楚地看到,這四個名額可以為我們提供寶貴的見解,并幫助您更好地了解 Node.js 的内部工作。

這些需求仍然需要在更大的圖檔中去觀察,以使其有意義。是以,我們正在收集資訊以将這些資料納入我們的異常檢測。

回到事件循環

當然,在不了解如何從可能的行動中解決問題的情況下,衡量标準本身就不會有太大的幫助。當事件循環快耗盡時,這裡有幾個提示。

[譯] 所有你需要知道的關于完全了解 Node.js 事件循環及其度量

事件循環耗盡

利用所有 CPU

Node.js 應用程式在單個線程上運作。在多核機器上,這意味着負載不會分布在所有核心上。使用 Node 附帶的

cluster module

可以輕松地為每個 CPU 生成一個子程序。每個子程序維護自己的事件循環,主程序在所有子程序之間透明地配置設定負載。

調整線程池

如上所述,libuv 将建立一個大小為 4 的線程池。通過設定環境變量 UV_THREADPOOL_SIZE 可以覆寫線程池的預設大小。

雖然這可以解決 I/O 綁定應用程式上的負載問題,我建議多次負載測試,因為較大的線程池可能仍然耗盡記憶體或 CPU 。

将任務扔給服務程序

如果 Node.js 花費太多時間參與 CPU 繁重的操作,開一些服務程序處理這些繁重任務或者針對某些特定任務使用其它語言編寫服務也是一個可行的選擇。

總結

我們總結一下我們在這篇文章中學到的内容:

  • 事件循環是使 Node.js 應用程式運作的原因
  • 它的功能經常被誤解 - 它有多個階段組成,各階段處理特定任務,階段間輪詢排程
  • 事件循環不提供現成的名額,是以收集的名額在 APM 供應商之間是不同的
  • 這些名額清楚地提供了有關瓶頸的有價值的見解,但對事件循環的深刻了解以及正在運作的代碼才是關鍵
  • 在未來,Dynatrace 将會把事件循環添加到第一檢測要素,進而将事件循環異常與問題相關聯

對我來說,毫無疑問,我們今天剛剛在市場上建構了最全面的事件循環監控解決方案,我非常高興在未來幾個星期内,這個驚人的新功能将推向所有客戶。

最後

我們一流的 Node.js 代理團隊為了做好事件循環監控盡了很大努力。這篇部落格文章中提出的大部分發現都是基于他們對 Node.js 内部運作的深入了解。 我要感謝 Bernhard Liedl ,Dominik Gruber ,GerhardStöbich 和 Gernot Reisinger 所有的工作和支援。

我希望這篇文章使大家在事件循環上有新的認知。請在 Twitter 上關注我

@dkhan

。我很樂意回答您在 Twitter 裡或下面評論區中的提出的一切問題。

最後和以往一樣:

下載下傳免費試用版去監控您的完整堆棧,包括Node.js

作者:

牧雲雲

出處:

http://www.cnblogs.com/MuYunyun/"

本文版權歸作者和部落格園所有,歡迎轉載,轉載請标明出處。

如果您覺得本篇博文對您有所收獲,請點選右下角的 [推薦],謝謝!