2017-07-16 781 views
1

我想了解與基於線程的方法(如Servlet服務器)相比,nodejs如何實現更高的併發性。NodeJS如何處理高併發請求?

我已經知道在nodejs中「除了你的代碼以外所有東西都是並行運行的」,並且在libuv中還有一個後端線程池來處理通常是瓶頸的文件IO或數據庫調用。

所以,這裏是我的問題:如果nodejs使用線程池來處理數據庫調用,那麼它如何能夠服務更高的併發請求,比Tomcat等Servlet服務器更好,因爲Tomcat也可以使用由epoll/kqueue支持的NIO來實現高併發性?例如,如果有100k個併發請求進入並且每個請求都需要數據庫操作,如果這些100k請求需要同時服務,那麼使用nodejs,我們最終還是會創建100k個線程,這可能會導致內存耗盡,正如Tomcat所做的那樣。是的,100k線程只是一個想象,因爲(我知道)nodejs有一個固定的線程池,不同的操作在事件循環中排隊,但是使用Tomcat它以相同的方式處理事情 - 我們也可以配置線程池在Tomcat中的大小,它也排隊請求。

或者,我錯了,說「nodejs使用libuv中的後端線程池來處理文件IO或數據庫調用」? nodejs是否使用epoll/kqueue來處理數據庫io而沒有單獨的線程?

我在讀this similar question,但仍然沒有得到答案。

回答

0

如果使用的NodeJS線程池來處理數據庫調用

這是一個錯誤的假設。 nodejs通常會使用網絡與運行在不同進程或不同主機上的本地數據庫進行通信。 node.js中的網絡不使用任何類型的線程 - 它使用事件驅動的I/O。數據庫對於線程的功能取決於數據庫並且獨立於node.js,因爲無論您使用的是哪種服務器環境,它都是相同的。

node.js確實使用線程池進行本地磁盤訪問,但是高規模應用程序通常使用數據庫作爲其磁盤訪問的關鍵,它們在單獨的進程中運行,並且擁有自己的I/O優化來處理大量的請求。一個給定的數據庫是如何實現的,但是它不會爲每個請求使用一個nodejs線程。

我試圖理解nodejs如何實現更高的併發性相比,基於線程的方法,如Servlet服務器。

一般概念是node.js中正確編寫的服務器應用程序對所有I/O使用異步I/O(可能啓動代碼只在服務器啓動時運行)。這意味着它可以在同一時間只有一個單獨的Javascript線程的同時有很多請求正在運行,而大多數請求正在等待某種類型的I/O。如果您將同時有很多請求正在進行中,那麼系統可以更有效地執行單個線程的node.js方法,其中所有請求都協作切換到使用操作系統線程,其中每個線程都有與之相關的操作系統開銷,並且每個搶佔線程交換機都有與之相關的操作系統和CPU開銷。

在node-js中,活動請求之間沒有先發制人的切換。一次只能運行一個,它會一直運行,直到完成或命中異步操作,並且在異步I/O操作完成之前沒有別的事情要做。此時,JS引擎返回到事件隊列並挑選一個事件(可能用於其他請求之一)。這種類型的協作交換可以比OS級別的線程更快且更高效。有時候編程成本是因爲node.js開發人員必須使用異步I/O編寫代碼才能利用這一點,它具有學習曲線,以便熟練編寫具有適當錯誤處理的良好,乾淨的代碼,並具有一個用於調試的學習曲線。

例如,如果有即將在10萬併發請求和每個需要的數據庫操作,如果這10萬的請求應同時提供服務,用的NodeJS我們還是最終創造10萬線爲Tomcat的確實這可能會導致內存耗盡。

不,您不會創建100k個線程。在node.js和另一個進程或另一個主機上的實際數據庫代碼之間進行交互的node.js數據庫接口層可能完全用node.js(使用TCP網絡與數據庫交談)編寫,並且根本不會引入新線程或者它可能有一些本地代碼,併爲其本機代碼操作使用少量線程,但它可能只有少量線程,並且每個請求甚至不會接近一個。

或者,我錯了,說「nodejs在libuv中使用後端線程池來處理文件IO或數據庫調用」? nodejs是否使用epoll/kqueue來處理數據庫io而沒有單獨的線程?

對於文件I/O,是的,它使用libuv中的線程池。對於數據庫調用,不 - 雖然細節完全取決於數據庫實現,但通常每個數據庫調用都沒有線程。數據庫通常在另一個進程中,數據庫的nodejs接口庫或者直接使用nodejs TCP與數據庫進行通信(不使用線程),或者它有自己的本地代碼插件,與數據庫進行通信,這可能會使用它的工作線程數量很少,但通常不是每個請求的線程數。

+0

感謝您的全面解釋。當我談到數據庫調用時,主要是指來自客戶端的調用,在這種情況下,我們的js代碼充當數據庫客戶端。是的,數據庫調用最終是網絡套接字io,所以我可以說,在數據庫調用來自客戶端的數據庫調用覆蓋的底層io機制,如epoll/kqueue包裝libuv?否則我無法想象在數據庫調用異步時沒有使用線程的場景。 – davenkin

+0

@davenkin - 您的數據庫評論令我困惑。通常,運行數據庫的實際代碼將與node.js(有時在另一個主機上)處於不同的進程中。當node.js中的Javascript向客戶端調用數據庫時,它將準備與其他進程或其他主機的某些通信。該通信通常不會使用node.js線程,因爲它通常是非阻塞TCP並且node.js不會爲每個TCP連接使用額外的線程。 – jfriend00

+0

@davenkin - 給你一個想法。如果我編寫node.js應用程序來向100個不同的主機發起http API調用,以便所有100個請求同時處於運行狀態並等待響應,但不會在node.js中使用任何其他線程。現在,如果node.js中的數據庫驅動程序具有自己的本機代碼,那麼它可以在其自己的本機代碼中執行任何它想要的操作。有些人可能會使用一些本地線程來跟蹤請求 - 我不知道。但是,根本不需要node.js模型。 – jfriend00