Nginx是俄國人最早開發(fā)的Webserver减响,現(xiàn)在已經(jīng)風(fēng)靡全球靖诗,相信大家并不陌生。PHP也通過二十多年的發(fā)展來到了7系列版本支示,更加關(guān)注性能刊橘。這對(duì)搭檔在最近這些年,叱咤風(fēng)云颂鸿,基本上LNMP成了當(dāng)下的標(biāo)配促绵。可是嘴纺,你用了這么多年的Nginx+PHP的搭配败晴,你真正知道他們之間是怎么交互怎么通信的么?作為一道常常用來面試的考題颖医,從過往經(jīng)驗(yàn)看位衩,情況并不樂觀。更多的同學(xué)是知道PHP-FPM熔萧、知道FastCGI,但不曉得Nginx僚祷、PHP這對(duì)老搭檔具體的交互細(xì)節(jié)佛致。那么,今天我們就來一起學(xué)習(xí)一下辙谜,做一回認(rèn)真的PHP工程師俺榆。
前菜
為了講解的有理有據(jù),我們先來準(zhǔn)備一個(gè)純凈精簡的Nginx+PHP環(huán)境装哆,這里我們使用Docker拉取Centos最新版本環(huán)境罐脊,來快速通過編譯安裝方式搭建一個(gè)Nginx+PHP環(huán)境。(圖1蜕琴,通過docker啟動(dòng)一臺(tái)CentOS機(jī)器并進(jìn)入)
有了Linux環(huán)境萍桌,我們來源碼編譯安裝Nginx、PHP凌简,這個(gè)過程網(wǎng)絡(luò)里有很多的教程上炎,我們就不細(xì)說了。當(dāng)然你也可以安裝lnmp一鍵安裝包來快速搭建雏搂。通過安裝nginx藕施、php,我們的Linux環(huán)境里就有了今天的這兩位主角了凸郑。我們稍加配置裳食,讓Nginx可以接收請(qǐng)求并轉(zhuǎn)發(fā)給PHP-FPM,我們目標(biāo)是輸出一個(gè)phpinfo()的信息芙沥。(圖2诲祸,phpinfo()的輸出內(nèi)容)
我們通過對(duì)Nginx新增Server配置實(shí)現(xiàn)了nginx與PHP的一次通信尘盼,配置文件非常簡單,如下圖:(圖3烦绳,一份nginx server配置)
有了上面的一個(gè)sample示例卿捎,我們開始深入Nginx與FastCGI協(xié)議。
主食
從上圖的 Nginx 配置中可以注意到 fastcgi*
開頭的一些配置径密,以及引入的 fastcgi.conf
文件午阵。其實(shí)在 fastcgi.conf
中,也是一堆 fastcgi*
的配置項(xiàng)享扔,只是這些配置項(xiàng)相對(duì)不常變底桂,通常單獨(dú)文件保管可以在多處引用。(圖4惧眠,fastcgi.conf
文件中的內(nèi)容)
可以看到在 fastcgi.conf
中籽懦,有很多的 fastcgi_param
配置,結(jié)合 nginx server 配置中的 fastcgi_pass
氛魁、fastcgi_index
暮顺,通常我們的同學(xué)已經(jīng)能夠想到 Nginx與PHP之間打交道就是用的 FastCGI,但再深問 FastCGI 是什么秀存?它起到銜接Nginx捶码、PHP的什么作用?等等深入的問題的時(shí)候或链,很多同學(xué)就卡殼了惫恼。
那么,我們就來一探究竟澳盐。CGI 是通用網(wǎng)關(guān)協(xié)議祈纯,F(xiàn)astCGI 則是一種常住進(jìn)程的 CGI 模式程序。我們所熟知的 PHP-FPM的全稱是 PHP FastCGI Process Manager叼耙,即 PHP-FPM 會(huì)通過用戶配置來管理一批 FastCGI 進(jìn)程腕窥,例如在PHP-FPM管理下的某個(gè) FastCGI 進(jìn)程掛了,PHP-FPM 會(huì)根據(jù)用戶配置來看是否要重啟補(bǔ)全旬蟋,PHP-FPM更像是管理器油昂,而真正銜接 Nginx 與 PHP 的則是 FastCGI 進(jìn)程。(圖5倾贰,F(xiàn)astCGI在請(qǐng)求流中的位置)
如上圖所示冕碟,F(xiàn)astCGI 的下游,是 CGI-APP匆浙,在我們的 LNMP 架構(gòu)里安寺,這個(gè) CGI-APP 就是 PHP 程序。而 FastCGI 的上游是 Nginx首尼,他們之間有一個(gè)通信載體挑庶,即圖中的 socket言秸。在我們上文圖3的配置文件中,fastcgi_pass
所配置的內(nèi)容迎捺,便是告訴 Nginx 你接收到用戶請(qǐng)求以后举畸,你該往哪里轉(zhuǎn)發(fā),在我們圖3中是轉(zhuǎn)發(fā)到本機(jī)的一個(gè) socket 文件凳枝,這里 fastcgi_pass
也常配置為一個(gè)
http 接口地址(這個(gè)可以在 php-fpm.conf
中配置)抄沮。而上圖5中的 Pre-fork,則對(duì)應(yīng)著我們 PHP-FPM 的啟動(dòng)岖瑰,也就是在我們啟動(dòng)PHP-FPM時(shí)便會(huì)根據(jù)用戶配置啟動(dòng)諸多FastCGI觸發(fā)器(FastCGI Wrapper)叛买。
對(duì) FastCGI 在 Nginx+PHP 的模式中的定位有了一定了解后,我們?cè)賮砹私庀?Nginx 中為何能寫很多 fastcgi_*
的配置項(xiàng)蹋订。這是因?yàn)?Nginx 的一個(gè)默認(rèn)內(nèi)置 module 實(shí)現(xiàn)了 FastCGI 的 Client率挣。關(guān)于 Module ngx_http_fastcgi_module的詳細(xì)文檔可以查看這里: http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html 。我們關(guān)心一下我們圖4中的這些 fastcgi_param
都是些什么吧露戒,詳細(xì)描述見下圖椒功。(圖6,nginx 模塊中 fastcgi_param 的介紹)
從圖6中可以看到玫锋,fastcgi_param所聲明的內(nèi)容蛾茉,將會(huì)被傳遞給“FastCGI server”,那這里指的就是fastcgi_pass所指向的server撩鹿,也就是我們Nginx+PHP模式下的PHP-FPM所管理的FastCGI進(jìn)程,或者說是那個(gè)socket文件載體悦屏。這時(shí)节沦,有的同學(xué)會(huì)問:“為什么PHP-FPM管理的那些FastCGI進(jìn)程要關(guān)心這些參數(shù)呢?”础爬,好問題甫贯,我們一起想想我們做PHP應(yīng)用開發(fā)時(shí)候有沒有用到 $_SERVER 這個(gè)全局變量,它里面包含了很多服務(wù)器的信息看蚜,比如包含了用戶的IP地址叫搁。同學(xué)們不想想我們的PHP身處socket文件之后,為什么能得到遠(yuǎn)端用戶的IP呢供炎?聰明的同學(xué)應(yīng)該注意到圖4中的一個(gè)fastcgi_param配置 REMOTE_ADDR 渴逻,這不正是我們?cè)赑HP中用 $_SERVER[‘REMOTE_ADDR’] 取到的用戶IP么。的確音诫,Nginx這個(gè)模塊里fastcgi_param參數(shù)惨奕,就是考慮后端程序有時(shí)需要獲取Webserver外部的變量以及服務(wù)器情況,那么ngx_http_fastcgi_module就幫我們做了這件事竭钝。真的是太感謝它啦梨撞!
那么我們已經(jīng)說清了FastCGI是個(gè)什么東東雹洗,并且它在Nginx+PHP中的定位。我們回到前面提出的問題卧波,“它起到銜接Nginx时肿、PHP的什么作用?”港粱。
對(duì)PHP有一定了解的同學(xué)螃成,應(yīng)該會(huì)知道PHP提供SAPI面向Webserver來提供擴(kuò)展編程。但是這樣的方式意味著你要是自主研發(fā)一套Webserver啥容,你就需要學(xué)習(xí)SAPI锈颗,并且在你的Webserver程序中實(shí)現(xiàn)它。這意味著你的Webserver與PHP產(chǎn)生了耦合咪惠。在互聯(lián)網(wǎng)的大趨勢(shì)下击吱,一般大家都不喜歡看到耦合。譬如Nginx在最初研發(fā)時(shí)候也不是為了和PHP組成黃金搭檔而研發(fā)的遥昧,相信早些年的Nginx后端程序可能是其他語言開發(fā)覆醇。那么解決耦合的辦法,比較好的方式是有一套通用的規(guī)范炭臭,上下游都兼容它永脓。那么CGI協(xié)議便成了Nginx、PHP都愿意接受的一種方式鞋仍,而FastCGI常住進(jìn)程的模式又讓上下游程序有了高并發(fā)的可能常摧。那么,F(xiàn)astCGI的作用是Nginx威创、PHP的接口載體落午,就像插座與插銷,讓流行的WebServer與“世界上最好的語言”有了合作的可能肚豺。
有了這些基礎(chǔ)背景知識(shí)與他們的緣由溃斋,我們就可以舉一反三的做更多有意思的事情。譬如我在前年曾實(shí)現(xiàn)了Java程序中按照FastCGI Client的方式(替代Nginx)與PHP-FPM通信吸申,實(shí)現(xiàn)Java項(xiàng)目+PHP的一種組合搭配梗劫,解決的問題是Java程序一般來說在代碼調(diào)整后需要編譯過程,而PHP可以隨時(shí)調(diào)整代碼隨時(shí)生效截碴,那么讓Java作為項(xiàng)目外殼梳侨,一些易變的代碼由PHP實(shí)現(xiàn),在需要的時(shí)候Java程序通過FastCGI與PHP打交道就好隐岛。這套想法也是基于對(duì)Nginx+PHP交互模式的理解之上想到的猫妙。
網(wǎng)絡(luò)中也有一些借助FastCGI的嘗試與實(shí)踐,譬如《Writing Hello World in FCGI with C++》這篇文章聚凹,用C++實(shí)現(xiàn)一個(gè)FastCGI的程序割坠,外部依然是某款Webserver來處理HTTP請(qǐng)求齐帚,但具體功能則有C++來實(shí)現(xiàn),他們的中間交互同樣適用的FastCGI彼哼。同學(xué)們有興趣了也可以做些Geek嘗試对妄。(圖7,C++實(shí)現(xiàn)一個(gè)FastCGI程序)
甜品
通過本文的講解敢朱,我們希望讓大家看到剪菱,Nginx+PHP的工程模式下,兩位主角分工明確拴签,Nginx負(fù)責(zé)承載HTTP請(qǐng)求的響應(yīng)與返回孝常,以及超時(shí)控制記錄日志等HTTP相關(guān)的功能,而PHP則負(fù)責(zé)處理具體請(qǐng)求要做的業(yè)務(wù)邏輯蚓哩,它們倆的這種合作模式也是常見的分層架構(gòu)設(shè)計(jì)中的一種构灸,在它們各有專注面的同時(shí),F(xiàn)astCGI又很好的將兩塊銜接岸梨,保障上下游通信交互喜颁,這種通過某種協(xié)議或規(guī)范來銜接好上下游的模式,在我們?nèi)粘5腜HP應(yīng)用開發(fā)中也有這樣的思想落地曹阔,譬如我們所開發(fā)的高性能API半开,具體的Client到底是PC、APP還是某個(gè)其他程序赃份,我們不關(guān)心寂拆,而這些PC、APP抓韩、第三方程序也不關(guān)心我們的PHP代碼實(shí)現(xiàn)漓库,他們按照API的規(guī)范來請(qǐng)求做處理即可。同學(xué)們是不是發(fā)現(xiàn)技術(shù)思想是可以在各個(gè)環(huán)節(jié)融會(huì)貫通的园蝠,是不是很興奮?很刺激痢士?哈彪薛,同學(xué)們開心就好,祝大家在工作學(xué)習(xí)過程中怠蹂,能挖掘到更多的好知識(shí)善延,提升自己的同時(shí)造福身邊小伙伴!