1. 程式人生 > >【思考】PHP——成也Web,敗也Web

【思考】PHP——成也Web,敗也Web

轉載:https://tech.meituan.com/php_success_or_failure_web_dev_is_the_sole_cause.html

背景

早年我並不知道Python寫的Web應用是怎麼部署的,總覺得像PHP、ASP一樣,僅僅提供一個語言級別的執行模組,直接嵌入Web伺服器執行,甚至於直接對外提供帶副檔名的URL都是自然而然的事情。

前一陣學習了Python,總是如別人一樣,不自覺的和PHP進行對比。隨著學習的逐步深入,更發現PHP的發展受限於其Web出身,恐將來難成正經的通用開發語言。

先說說PHP從歷史到今天分別是怎麼執行的:

  • CGI SAPI:由Web伺服器的請求處理程序fork+exec這個CGI,用環境變數單向傳遞Headers給應用程式,應用程式從stdin讀request body,其stdout會被Web伺服器作為response headers和body輸出給客戶端。Web伺服器和PHP CGI的關係是父子程序間通訊,當應用程式的響應速度較慢時(忙於計算、外部I/O等),會阻塞對應的Web伺服器程序/執行緒
  • Apache2Handler等Web伺服器模組類SAPI:Apache2呼叫Handler,並向其傳遞server_context;Handler利用server_context讀到Header、body等內容之後
    構造起PHP指令碼的context
    ,也就是$_SERVER之類,然後開始執行PHP指令碼。而指令碼執行的輸出也直接被Web伺服器收集。PHP和Web伺服器在同一個程序內執行,處於完全從屬的地位。Apache2Handler方式本來可以通過多執行緒等方式讓Apache不受PHP阻塞,但由於PHP社群的堅持,目前常見的Apache2+PHP配置都是prefork MPM的,以至於效能相當一般
  • FastCGI SAPI:原理是把每次fork處理一個請求,變成一個不退出的迴圈,每迴圈一輪就處理一個請求;多程序listen在同一個socket上,實現併發。PHP-FPM是後來的一個民間補丁專案,提供了更好的程序管理和附加功能,處理完License問題之後,FPM合併入PHP主線。Web伺服器和PHP的關係是網路通訊的關係,可以分離到不同伺服器上部署。Web伺服器可以使用多執行緒或者I/O複用技術來處理別的請求,效能會>比前二者好很多。但FPM的實現仍然不夠好。

PHP的各種擴充套件模組,大都是偏重於資料庫、字串處理等應用層面的功能,充分體現了其作為Web開發利器的特點;而對於I/O、POSIX基礎功能,則關注較少。
Web開發之外的領域,用PHP寫獨立執行的伺服器,並不是主流的用法。其依賴的pcntl、pthread等模組也是較晚加進來的,而且也沒啥人用。用以下幾個模組可以很容易的創作一個獨立的HTTP靜態檔案或SOA服務:

  1. PEAR Net_Server(無人維護) 通用伺服器框架
  2. PEAR HTTP2 協議解析庫
  3. PEAR HTTP_Server(無人維護) HTTP伺服器框架
  4. PECL pecl_HTTP(需要編譯) HTTP伺服器框架,我沒編譯出來……
    但如果想在其中執行一般的Web PHP指令碼,就需要用PHP本身實現一個SAPI:
  • 輸入方面是很容易仿製的,除了需要構造Predefined/Reserved Variables(感謝作者,$_SERVER等只是普通陣列,只須填充內容就差不多了,而不像ASP那樣是複雜的Request物件)再寫一下上傳檔案自動存檔的處理就差不多了
  • 輸出方面就比較麻煩,因為“寫”是動作。考慮到PHP深深的“假設stdout”情結,須使用output buffer機制收集輸出內容。另外,header()函式等的實現可能會比較複雜

傳統上,協議解析是SAPI的工作,PHP從屬於Web伺服器,以至於沒積累出什麼好用的協議解析庫;而PHP書寫的Web應用向來都是直接呼叫SAPI提供的函式,積習難改,以至於想用PHP本身來寫Web伺服器就須要去實現一個SAPI,但又會遭遇到這個語言本身的Web烙印太深、假設太多,以及不夠“動態”的問題。
傳統上,網路通訊向來也不須由PHP來處理,以至於也沒積累出什麼好用的通訊框架庫。

最終,PHP成了一個Web only的開發語言。真可謂成也Web、敗也Web。

一些新興的PECL擴充套件提供了高效能I/O框架,比如swoole,但因為上述SAPI的問題,目前尚未出現像Python那樣用PHP本身寫的PHP Web伺服器。

相比之下,不用寫<%的Python語言,其發展道路就更加general purpose一些:

  1. 標準庫中有os、multiprocess、threading、select等模組,一看就是面向系統程式設計而非僅僅針對Web開發
  2. 標準庫中的SocketServer及其MixIn classes、HTTPServer等可用於寫伺服器;CGI模組可用於解析HTTP協議請求
  3. 標準庫中的wsgiref是純Python的。也就是說,用Python語言寫一個WSGI Server很容易且是提倡的做法
  4. yield語法加入語言比較早,可以實現協程
    在Web應用方面,Python的WSGI介面已經基本統一江湖。WSGI不是通訊協議,而是語言級別的呼叫約定(關於這個函式後面帶幾個什麼樣的引數的約定),而WSGI可以用純Python實現,因此不會遇見用PHP實現SAPI的尷尬。由於Python沒有像PHP那樣深深的Web烙印(甚至早年在Web上的部署並不太方便)加上標準庫中很早就帶有各種基礎模組,導向結果就是出現了很多優秀的高效能框架和伺服器軟體。現在流行的組合是用 gunicorn WSGI Server執行WSGI應用程式,管理多個worker充分利用多處理器,用gevent消除I/O等待時的浪費;對外提供HTTP服務,前面套一個nginx提供靜態檔案和訪問控制、rewrite等功能。