NodeJS架構 - 單線程事件循環模型

Event Loop

這篇譯章探究了NodeJS的架構和單線程事件循環模型。咱們將在本文中討論「NodeJS如何在底層工做,它遵循什麼類型的處理模型,NodeJS如何使用單線程模型處理併發請求」等內容。node

NodeJS 單線程事件循環模型

正如咱們剛纔說的,NodeJS使用的是「單線程事件循環模型」的架構去處理多個併發的客戶端請求的。數據庫

有許多Web應用程序技術,如JSP,Spring MVC,ASP.NET等。但全部這些技術都遵循「多線程請求 - 響應」架構來處理多個併發客戶端。服務器

咱們已經熟悉「多線程請求 - 響應」架構,由於它被大多數Web應用程序框架使用。 可是爲何NodeJS選擇了不一樣的架構來開發Web應用程序。多線程和單線程事件循環體系結構之間的主要區別是什麼?多線程

NodeJS

NodeJS使用「單線程事件循環模型」架構來處理多個併發客戶端。然而它是如何真正處理併發客戶端請求且不使用多個線程。什麼是事件循環模型?咱們將逐一討論這些概念。架構

在討論「單線程事件循環」架構以前,首先咱們將介紹著名的「多線程請求 - 響應」架構。併發

傳統的Web應用處理模型

任何非NodeJS開發的Web應用程序一般都遵循「多線程請求 - 響應」模型。咱們能夠將此模型稱爲請求/響應模型。框架

客戶端向服務器發送請求,而後服務器根據客戶端請求進行一些處理,準備響應並將其發送回客戶端。函數

該模型使用HTTP協議。因爲HTTP是無狀態協議,所以該請求/響應模型也是無狀態模型。因此咱們能夠將其稱爲請求/響應無狀態模型。oop

可是,此模型使用多線程來處理併發客戶端請求。 在討論這個模型內部以前,首先要看下面的內容。spa

請求/響應模型處理的步驟:

  • 客戶端發送一個請求到Web服務器
  • Web服務器內部維護一個有限的線程池,以便在客戶端請求提供服務
  • Web服務器處於無限循環中並等待客戶端傳入請求
  • Web服務器處理請求步驟:

    • 接收到一個客戶端請求
    • 從線程池中選擇一個線程
    • 將此線程分配給客戶端請求
    • 此線程讀取客戶端請求,處理客戶端請求,執行阻塞的IO操做(若是須要)和準備響應
    • 此線程將準備好的請求發送回Web服務器
    • Web服務器又將此響應發送到相應的服務器

服務器爲全部客戶端執行以上步驟,爲每個客戶端請求建立一個線程。

請求/響應模型

圖表說明:

  • Client-1, Client-2, ..., Client-n是同時發送請求到Web服務器的客戶端應用
  • Web服務器內部維護着一個有限的線程池,線程池中線程數量爲m個
  • Web服務器逐個接收這些請求:

    • Web服務器拾取Client-1的請求Request-1,從線程池中拾取一個線程T-1並將此請求分配給線程T-1

      • 線程T-1讀取Client-1的請求Request-1, 並處理該請求
      • 該請求無阻塞IO處理
      • 處理完必要的步驟後準備將Response-1發送回客戶端
      • Web服務器又將此Response-1發送到Client-1
    • Web服務器拾取Client-2的請求Request-2,從線程池中拾取一個線程T-2並將此請求分配給線程T-2

      • 線程T-2讀取Client-2的請求Request-2, 並處理該請求
      • 該請求無阻塞IO處理
      • 處理完必要的步驟後準備將Response-2發送回客戶端
      • Web服務器又將此Response-2發送到Client-2
    • Web服務器拾取Client-n的請求Request-n,從線程池中拾取一個線程T-n並將此請求分配給線程T-n

      • 線程T-n讀取Client-n的請求Request-n, 並處理該請求
      • Request-n須要大量的阻塞IO和計算操做
      • 線程T-n須要更多時間與外部系統(SQL, File System)交互,執行必要步驟並準備Response-n並將其發送回服務器
      • Web服務器又將此Response-n發送到Client-n

若是'n'大於'm'(大多數時候,它是真的),則在使用完全部的m個線程以後,剩餘的客戶端請求會在隊列中等待。

若是這些線程中有大量的阻塞IO操做(例如:和數據庫、文件系統、外部服務等交互),那麼剩餘的客戶端也會等待更長的時間。

  • 一旦線程池中的線程空閒且可用於下一個任務,服務器就會拾取這些線程並將它們分配給剩餘的客戶端請求。
  • 每一個線程都會使用到許多資源,如內存等。所以,在將這些線程從忙狀態轉到等待狀態以前,它們應該釋放全部獲取的資源。

請求/響應無狀態模型的缺點:

  • 在處理愈來愈多的併發客戶端請求時會變得棘手
  • 當客戶端請求增長時,線程也會愈來愈多,最後它們會佔用更多內存。
  • 客戶端可能須要等待服務器釋放可用的線程去處理其請求
  • 處理阻塞式的IO任務時浪費時間

NodeJS的架構 - 單線程事件循環

NodeJS不遵循請求/響應多線程無狀態模型。 它採用單線程與事件循環模型。 NodeJS的處理模型主要基於Javascript基於事件的模型和Javascript回調機制。

由於NodeJS遵循的架構,它能夠很是輕鬆地處理愈來愈多的併發客戶端請求。 在討論這個模型內部以前,首先要看下面的圖表。

我試圖設計這個圖來解釋NodeJS內部的每一點。

NodeJS的處理模型主要核心是「事件循環(Event Loop)」。若是咱們理解這一點,那麼很容易理解NodeJS的內部架構的。

單線程事件循環模型的處理步驟

  • 客戶端發送請求到Web服務器
  • NodeJS的Web服務器在內部維護一個有限的線程池,以便爲客戶端請求提供服務
  • NodeJS的Web服務器接收這些請求並將它們放入隊列中。 它被稱爲「事件隊列」
  • NodeJS的Web服務器內部有一個組件,稱爲「事件循環」,它使用無限循環來接收請求並處理它們。
  • 事件循環只使用到了一個線程,它是NodeJS的處理模型的核心
  • 事件循環回去檢查是否有客戶端的請求被放置在事件隊列中。若是沒有,會一直等待事件隊列中存在請求。
  • 若是有,則會從事件隊列中拾取一個客戶端請求:

    • 開始處理客戶端請求
    • 若是該客戶端請求不須要任何阻塞IO操做,則處理全部內容,準備響應並將其發送回客戶端
    • 若是該客戶端請求須要一些阻塞IO操做,例如與數據庫,文件系統,外部服務交互,那麼它將遵循不一樣的方法:

      • 從內部線程池檢查線程可用性
      • 獲取一個線程並將此客戶端請求分配給該線程
      • 該線程負責接收該請求,處理該請求,執行阻塞IO操做,準備響應並將其發送回事件循環
      • 事件循環依次將響應發送到相應的客戶端

單線程事件循環模型

圖表說明:

  • Client-1, Client-2, ..., Client-n是同時發送請求到Web服務器的客戶端應用
  • Web服務器內部維護着一個有限的線程池,線程池中線程數量爲m個
  • NodeJS的Web服務器接收到Client-1, Client-2, ..., Client-n的請求後,將請求放入到事件隊列中
  • NodeJS的事件循環從隊列中開始拾取這些請求:

    • 事件循環拾取Client-1的請求Request-1

      • 檢查Client-1 Request-1是否確實須要任何阻塞IO操做,或者須要更多時間來執行復雜的計算任務
      • 因爲此請求是簡單計算和非阻塞IO任務,所以不須要單獨的線程來處理它
      • 事件循環處理該請求所須要的操做,準備其響應Response-1
      • 事件循環發送Response-1到Client-1
    • 事件循環拾取Client-2的請求Request-2

      • 檢查Client-2 Request-2是否須要任何阻塞IO操做或花費更多時間來執行復雜的計算任務
      • 因爲此請求是簡單計算和非阻塞IO任務,所以不須要單獨的線程來處理它
      • 事件循環處理該請求所須要的操做,準備其響應Response-2
      • 事件循環發送Response-2到Client-2
    • 事件循環拾取Client-n的請求Request-n

      • 檢查Client-n Request-n是否須要任何阻塞IO操做或花費更多時間來執行復雜的計算任務
      • 因爲此請求有很是複雜的計算或阻塞IO任務,所以事件循環不會處理此請求
      • 事件循環從內部線程池中獲取線程T-1,並將此Client-n Request-n分配給線程T-1
      • 線程T-1讀取並處理Request-n,執行必要的阻塞IO或計算任務,最後準備響應Response-n
      • 線程T-1將此Response-n發送到事件循環

事件循環依次將此Response-n發送到Client-n

此處客戶端請求是對一個或多個JavaScript函數的調用,由於JavaScript函數能夠調用其餘函數或能夠利用其回調函數性質。

此因此每一個客戶端的請求處理都看起來向這樣:

回調函數調用

例如:

function1(function2,callback1);
function2(function3,callback2);
function3(input-params);

NodeJS的單線程事件循環的優點

  • 處理愈來愈多的併發客戶端請求很是容易
  • 由於事件循環的存在,即便咱們的NodeJS應用接收到了愈來愈多的併發請求,咱們也不須要去新建不少的線程
  • NodeJS使用到了較少的線程,因此資源和內存的使用較少


原文地址: NodeJS Architecture – Single Threaded Event Loop
相關文章
相關標籤/搜索