Nginx與PHP交互詳解——被百度懟了很長時間!!!

Nginx是俄國人最早開發的Webserver,現在已經風靡全球,相信大家並不陌生。PHP也通過二十多年的發展來到了7系列版本,更加關注性能。這對搭檔在最近這些年,叱吒風雲,基本上LNMP成了當下的標配。可是,你用了這麼多年的Nginx+PHP的搭配,你真正知道他們之間是怎麼交互怎麼通信的麼?作爲一道常常用來面試的考題,從過往經驗看,情況並不樂觀。更多的同學是知道PHP-FPM、知道FastCGI,但不曉得Nginx、PHP這對老搭檔具體的交互細節。那麼,今天我們就來一起學習一下,做一回認真的PHP工程師。

前菜

爲了講解的有理有據,我們先來準備一個純淨精簡的Nginx+PHP環境,這裏我們使用Docker拉取Centos最新版本環境,來快速通過編譯安裝方式搭建一個Nginx+PHP環境。(圖1,通過docker啓動一臺CentOS機器並進入)

圖1,通過docker啓動一臺CentOS機器並進入

有了Linux環境,我們來源碼編譯安裝Nginx、PHP,這個過程網絡裏有很多的教程,我們就不細說了。當然你也可以安裝lnmp一鍵安裝包來快速搭建。通過安裝nginx、php,我們的Linux環境裏就有了今天的這兩位主角了。我們稍加配置,讓Nginx可以接收請求並轉發給PHP-FPM,我們目標是輸出一個phpinfo()的信息。(圖2,phpinfo()的輸出內容)
圖2

我們通過對Nginx新增Server配置實現了nginx與PHP的一次通信,配置文件非常簡單,如下圖:(圖3,一份nginx server配置)
圖3,一份nginx

有了上面的一個sample示例,我們開始深入Nginx與FastCGI協議。

主食

從上圖的Nginx配置中可以注意到 fastcgi* 開頭的一些配置,以及引入的 fastcgi.conf 文件。其實在fastcgi.conf中,也是一堆fastcgi*的配置項,只是這些配置項相對不常變,通常單獨文件保管可以在多處引用。(圖4,fastcgi.conf文件中的內容)
圖4,fastcgi.conf文件中的內容

可以看到在fastcgi.conf中,有很多的fastcgi_param配置,結合nginx server配置中的fastcgi_pass、fastcgi_index,通常我們的同學已經能夠想到Nginx與PHP之間打交道就是用的FastCGI,但再深問FastCGI是什麼?它起到銜接Nginx、PHP的什麼作用?等等深入的問題的時候,很多同學就卡殼了。那麼,我們就來一探究竟。

CGI是通用網關協議,FastCGI則是一種常住進程的CGI模式程序。我們所熟知的PHP-FPM的全稱是PHP FastCGI Process Manager,即PHP-FPM會通過用戶配置來管理一批FastCGI進程,例如在PHP-FPM管理下的某個FastCGI進程掛了,PHP-FPM會根據用戶配置來看是否要重啓補全,PHP-FPM更像是管理器,而真正銜接Nginx與PHP的則是FastCGI進程。(圖5,FastCGI在請求流中的位置)
圖5,FastCGI在請求流中的位置

如上圖所示,FastCGI的下游,是CGI-APP,在我們的LNMP架構裏,這個CGI-APP就是PHP程序。而FastCGI的上游是Nginx,他們之間有一個通信載體,即圖中的socket。在我們上文圖3的配置文件中,fastcgi_pass所配置的內容,便是告訴Nginx你接收到用戶請求以後,你該往哪裏轉發,在我們圖3中是轉發到本機的一個socket文件,這裏fastcgi_pass也常配置爲一個http接口地址(這個可以在php-fpm.conf中配置)。而上圖5中的Pre-fork,則對應着我們PHP-FPM的啓動,也就是在我們啓動PHP-FPM時便會根據用戶配置啓動諸多FastCGI觸發器(FastCGI Wrapper)。

對FastCGI在Nginx+PHP的模式中的定位有了一定了解後,我們再來了解下Nginx中爲何能寫很多fastcgi_*的配置項。這是因爲Nginx的一個默認內置module實現了FastCGI的Client。關於Module ngx_http_fastcgi_module的詳細文檔可以查看這裏: http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html 。我們關心一下我們圖4中的這些fastcgi_param都是些什麼吧,詳細描述見下圖。(圖6,nginx模塊中fastcgi_param的介紹)
圖6,nginx模塊中fastcgi_param的介紹

從圖6中可以看到,fastcgi_param所聲明的內容,將會被傳遞給“FastCGI server”,那這裏指的就是fastcgi_pass所指向的server,也就是我們Nginx+PHP模式下的PHP-FPM所管理的FastCGI進程,或者說是那個socket文件載體。這時,有的同學會問:“爲什麼PHP-FPM管理的那些FastCGI進程要關心這些參數呢?”,好問題,我們一起想想我們做PHP應用開發時候有沒有用到 $_SERVER 這個全局變量,它裏面包含了很多服務器的信息,比如包含了用戶的IP地址。同學們不想想我們的PHP身處socket文件之後,爲什麼能得到遠端用戶的IP呢?聰明的同學應該注意到圖4中的一個fastcgi_param配置 REMOTE_ADDR ,這不正是我們在PHP中用 $_SERVER[‘REMOTE_ADDR’] 取到的用戶IP麼。的確,Nginx這個模塊裏fastcgi_param參數,就是考慮後端程序有時需要獲取Webserver外部的變量以及服務器情況,那麼ngx_http_fastcgi_module就幫我們做了這件事。真的是太感謝它啦!

那麼我們已經說清了FastCGI是個什麼東東,並且它在Nginx+PHP中的定位。我們回到前面提出的問題,“它起到銜接Nginx、PHP的什麼作用?”。

對PHP有一定了解的同學,應該會知道PHP提供SAPI面向Webserver來提供擴展編程。但是這樣的方式意味着你要是自主研發一套Webserver,你就需要學習SAPI,並且在你的Webserver程序中實現它。這意味着你的Webserver與PHP產生了耦合。在互聯網的大趨勢下,一般大家都不喜歡看到耦合。譬如Nginx在最初研發時候也不是爲了和PHP組成黃金搭檔而研發的,相信早些年的Nginx後端程序可能是其他語言開發。那麼解決耦合的辦法,比較好的方式是有一套通用的規範,上下游都兼容它。那麼CGI協議便成了Nginx、PHP都願意接受的一種方式,而FastCGI常住進程的模式又讓上下游程序有了高併發的可能。那麼,FastCGI的作用是Nginx、PHP的接口載體,就像插座與插銷,讓流行的WebServer與“世界上最好的語言”有了合作的可能。

有了這些基礎背景知識與他們的緣由,我們就可以舉一反三的做更多有意思的事情。譬如我在前年曾實現了Java程序中按照FastCGI Client的方式(替代Nginx)與PHP-FPM通信,實現Java項目+PHP的一種組合搭配,解決的問題是Java程序一般來說在代碼調整後需要編譯過程,而PHP可以隨時調整代碼隨時生效,那麼讓Java作爲項目外殼,一些易變的代碼由PHP實現,在需要的時候Java程序通過FastCGI與PHP打交道就好。這套想法也是基於對Nginx+PHP交互模式的理解之上想到的。

網絡中也有一些藉助FastCGI的嘗試與實踐,譬如《Writing Hello World in FCGI with C++》這篇文章,用C++實現一個FastCGI的程序,外部依然是某款Webserver來處理HTTP請求,但具體功能則有C++來實現,他們的中間交互同樣適用的FastCGI。同學們有興趣了也可以做些Geek嘗試。(圖7,C++實現一個FastCGI程序)
圖7,C++實現一個FastCGI程序

甜品

通過本文的講解,我們希望讓大家看到,Nginx+PHP的工程模式下,兩位主角分工明確,Nginx負責承載HTTP請求的響應與返回,以及超時控制記錄日誌等HTTP相關的功能,而PHP則負責處理具體請求要做的業務邏輯,它們倆的這種合作模式也是常見的分層架構設計中的一種,在它們各有專注面的同時,FastCGI又很好的將兩塊銜接,保障上下游通信交互,這種通過某種協議或規範來銜接好上下游的模式,在我們日常的PHP應用開發中也有這樣的思想落地,譬如我們所開發的高性能API,具體的Client到底是PC、APP還是某個其他程序,我們不關心,而這些PC、APP、第三方程序也不關心我們的PHP代碼實現,他們按照API的規範來請求做處理即可。同學們是不是發現技術思想是可以在各個環節融會貫通的,是不是很興奮?很刺激?哈,同學們開心就好,祝大家在工作學習過程中,能挖掘到更多的好知識,提升自己的同時造福身邊小夥伴!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章