淺談 Node.js 和 PHP 進程管理

jopen 9年前發布 | 24K 次閱讀 Node.js PHP開發

 

淺談 Node.js 和 PHP 進程管理

眾所周知,PHP 占據了服務端編程語言的半壁江山,正如汪峰在音樂圈的地位一般。隨著 Node.js 逐漸走上服務端編程的舞臺,關于 PHP 和 Node.js 孰優孰劣的爭論也不曾間斷。

壟斷性的市場份額足以佐證 PHP 的優秀。并且 HHVM 虛擬機、PHP 7 的革新,也給 PHP 帶來了跨越式的性能突破。然而,當我們為語言層面的性能差異喋喋不休時,卻往往忽略了 Web 模型在性能表現中的權重。

從 CGI 到 FastCGI

早期的 Web 服務,是基于傳統的 CGI 協議實現的。每個發送到服務器的請求,都需要經過啟動進程、處理請求、結束進程三個步驟,以至于訪問量增大時,系統資源(如內存、CPU 等)開銷也巨大,導致服務器性能下降甚至服務中斷。

淺談 Node.js 和 PHP 進程管理

圖 1:簡單的 CGI 流程示意

在 CGI 協議下,解析器的反復加載是性能低下的主要原因。如果讓解析器進程長駐內存,那么它只需啟動一次,就可以一直執行著,不必每次都重新 fork 進程,這就有了后來的 FastCGI 協議。

如果 FastCGI 僅僅做到這樣,那么和 Node.js 單進程單線程的模型是基本一致的:Node.js 進程啟動后保持持續運行,所有的請求都由這個進程接收和處理,當某個請求引起未知錯誤時,才可能致使進程退出。

事實上 FastCGI 并沒有那么簡單,為了保證服務的穩定性,他被設計成了多進程調度的模式:

淺談 Node.js 和 PHP 進程管理

圖 2:Nginx + FastCGI 執行過程

這個過程同樣可以描述為三個步驟:

  • 首先,初始化 FastCGI 進程管理器,并啟動多個 CGI 解釋器子進程;
  • 接著,當請求到達 Web 服務器時,進程管理器選擇并連接一個子進程,將環境變量和標準輸入發送給它,處理完成后將標準輸出和錯誤信息返還給 Web 服務器;
  • 最終,子進程關閉連接,繼續等待下一個請求的到來;

從 child_process 到 cluster

我們回過頭來看看 Node.js 的進程管理方式。

原生 Node.js 的單進程單線程模型是一個極易被噴的槽點。這種機制也決定了 Node.js 天生只支持單核 CPU,無法有效地利用多核資源,一旦進程崩潰,還會導致整個 Web 服務的土崩瓦解。

淺談 Node.js 和 PHP 進程管理

圖 3:簡單的 Node.js 的請求模型

和 CGI 一樣,單一進程始終面臨著可靠性低、穩定性差的問題,當真正服務于生產環境時,這樣的弱點相當致命。如果代碼本身足夠健壯,倒可以在一定程度上避免出錯, 但同時也對測試工作提出了更高要求。現實中我們無法避免代碼 100% 不出紕漏,有些東西容易編寫測試用例,有些東西卻只能依靠人肉目測。

所幸 Node.js 提供了child_process模塊,通過簡單 fork 即可隨意創建出子進程。如果為每個 CPU 分別指派一個子進程,多核利用就完美實現了。于此同時,由于child_process模塊本身繼承自EventEmitter這個基礎類,事件驅動使得進程間的通信非常高效。

淺談 Node.js 和 PHP 進程管理

圖 4:簡單的 Node.js master-worker 模型(扒的淘杰老濕的圖)

為了簡化龐雜的父子進程模型實現,Node.js 緊接著又封裝了cluster模塊,不論是負載均衡、資源回收,還是進程守護,它都會像保姆一樣幫你默默地搞定一切。具體技術細節可以參考淘杰老濕的 《當我們談論 cluster 時我們在談論什么(上)》《當我們談論 cluster 時我們在談論什么(下)》 ,里面有所有關于cluster方案的推演和實現,這里不再贅述。

在 Node.js 里,要讓應用跑在多核集群上,只需寥寥幾行代碼就萬事大吉了:

var cluster = require('cluster');
var os = require('os');

if (cluster.isMaster) {
  for (var i = 0, n = os.cpus().length; i < n; i ++) {
    cluster.fork();
  }
} else {
  // 啟動應用...
}

那么反觀 FastCGI 協議,它又是如何處理這種模型的呢?

PHP-FPM 的天生缺陷

PHP-FPM 是 PHP 針對 FastCGI 協議的具體實現,也是 PHP 在多種服務器端應用編程端口(SAPI:cgi、fast-cgi、cli、isapi、apache)里使用最普遍、性能最佳的一款進程管理器。它同樣 實現了類似 Node.js 的父子進程管理模型,確保了 Web 服務的可靠性和高性能。

PHP-FPM 這種模型是非常典型的多進程同步模型,意味著一個請求對應一個進程線程,并且 IO 是同步阻塞的。所以盡管 PHP-FPM 維護著獨立的 CGI 進程池、系統也可以很輕松的管理進程的生命周期,但注定無法像 Node.js 那樣,一個進程就可以承擔巨大的請求壓力。

受制于服務器的硬件設施,PHP-FPM 需要指定合理的 php-fpm.conf 配置:

var cluster = require('cluster');
var os = require('os');

if (cluster.isMaster) {
  for (var i = 0, n = os.cpus().length; i < n; i ++) {
    cluster.fork();
  }
} else {
  // 啟動應用...
}

和 JS 不一樣的是,PHP 進程本身并不存在內存泄露的問題,每個進程完成請求處理后會回收內存,但是并不會釋放給操作系統,這就導致大量內存被 PHP-FPM 占用而無法釋放,請求量升高時性能驟降。

所以 PHP-FPM 需要控制單個子進程請求次數的閾值。很多人會誤以為max_requests控制了進程的并發連接數,實際上 PHP-FPM 模式下的進程是單一線程的,請求無法并發。這個參數的真正意義是提供請求計數器的功能,超過閾值數目后自動回收,緩解內存壓力。

或許你已經發現了問題的關鍵:盡管 PHP-FPM 架構卓越,但還是卡在單一進程的性能上了。

Node.js 天生沒有這個問題,而 PHP-FPM 卻無法保證,它的穩定性受制于硬件設施和配置文件的契合度,以及 Web 服務器(通常是 Nginx)對 PHP-FPM 服務的負載調度能力。

ReactPHP,事件驅動,異步執行,非阻塞 IO

對 PHP 7 的狂熱掩蓋了 Node.js 帶來的猛烈沖擊。當大家還沉醉在如何選擇 HHVM 還是 PHP 7 的時候,ReactPHP 也在茁壯成長,它徹徹底底拋棄了 nginx + php-fpm 的傳統架構,轉而模仿并接納了 Node.js 的事件驅動和非阻塞 IO 模型,甚至連副標題,都起得一毛一樣:

Event-driven, non-blocking I/O with PHP. 

鑒于大家都比較了解 Node.js,對 ReactPHP 的原理就不再贅述了,我們可以認為它就是個 PHP 版的 Node.js。拿它和傳統架構(Nginx + PHP-FPM,公平起見,PHP-FPM 只開一個進程)去做對比,結果是這樣的:

淺談 Node.js 和 PHP 進程管理

圖 5:輸出“Hello World”時的 QPS 曲線

淺談 Node.js 和 PHP 進程管理

圖 6:查詢 SQL 時的 QPS 曲線

我們可以看到,當事件驅動、異步執行、非阻塞 IO 被移植嫁接到 PHP 上后,即便沒了 PHP-FPM 支撐,QPS 曲線依然不錯,在 IO 密集型的場景下,性能甚至得到了成倍成倍的提升。

事件和異步回調機制真是太贊了,它巧妙地將大規模并發、大吞吐量時的擁堵化解為一個異步事件隊列,然后挨個解決阻塞(如文件讀取,數據庫查詢等)。

針對單進程模型的吐槽,或許有些偏激。不過顯而易見的事實是,單進程模型的可靠性,在 Web 服務器和進程管理器層面是有很大的優化空間的,而高并發的處理能力取決于語言特性,說白了就是事件和異步的支持。

這兩點想必是讓 Node.js 天生驕傲的事情,但在 PHP 里沒有得到原生支持,只能通過模擬步進操作的方式來支持類似 Node.js 的事件機制,所以 ReactPHP 其實也并沒有想象中那么完美。

結束語

大部分時候,當我們比較語言優劣,容易局限在語言本身,而忽視了配套的一些關鍵因素。

就拿 PHP 來說,這兩年聽到了太多關于即時編譯器(JIT)、opcode 緩存、抽象語法樹(AST)、HHVM 等等之類的話題。當這些優化逐步完備,語言層面的問題,早已不再是 Web 性能的短板了。如果實在不行,我們還可以把復雜任務交給 C 和 C++,以 Node.js addon 或者 PHP 擴展的形式,輕輕松松就搞定了。

都說 PHP 是“世界上最好的語言”,既然如此,也是時候學習下 Node.js 事件驅動和異步回調,考慮考慮如何對 PHP-FPM 進行大刀闊斧的革新。畢竟不管是 Node.js 還是 PHP,我們所擅長的地方,終將還是 Web,高性能的 Web。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!