先來看一下搭建好PHP執行環境的Nginx配置檔案。
非常重要的就是 fastcgi_pass 指令了,這個指令用於指定 fpm 程式監聽的地址,Nginx 會把所有的 php 請求翻譯成 fastcgi 請求之後再傳送到這個地址。
看了上面的圖,接下來我們開始深入Nginx與FastCGI協議
從上圖的Nginx配置中可以注意到 fastcgi* 開頭的一些配置,以及引入的 fastcgi.conf 檔案。其實在fastcgi.conf中,也是一堆fastcgi*的配置項,只是這些配置項相對不常變,通常單獨檔案保管可以在多處引用。在這個配置檔案中,我們新建了一個虛擬主機,監聽在 80 埠,Web 根目錄為 /data/wwwroot/default。然後我們通過 location 指令,將所有的以 .php 結尾的請求都交給 fastcgi 模組處理,從而把所有的 php 請求都交給了 fpm 處理,從而完成 Nginx 到 fpm 的閉環。
可以看到在fastcgi.conf中,有很多的fastcgi_param配置,結合nginx server配置中的fastcgi_pass、fastcgi_index,通常我們的同學已經能夠想到Nginx與PHP之間打交道就是用的FastCGI,但再深問FastCGI是什麼?它起到銜接Nginx、PHP的什麼作用?慢慢了解
要說 Nginx 與 PHP 是如何協同工作的,首先得說 CGI (Common Gateway Interface) 和 FastCGI 這兩個協議。
CGI 是 Web Server 與後臺語言互動的協議,有了這個協議,開發者可以使用任何語言處理 Web Server 發來的請求,動態的生成內容。但 CGI 有一個致命的缺點,那就是每處理一個請求都需要 fork 一個全新的程式,隨著 Web 的興起,高併發越來越成為常態,這樣低效的方式明顯不能滿足需求。就這樣,FastCGI 誕生了,CGI 很快就退出了歷史的舞臺。FastCGI,顧名思義為更快的 CGI,它允許在一個程式內處理多個請求,而不是一個請求處理完畢就直接結束程式,效能上有了很大的提高。
至於 FPM (FastCGI Process Manager),它是 FastCGI 的實現,任何實現了 FastCGI 協議的 Web Server 都能夠與之通訊。
FPM 是一個 PHP 程式管理器,包含 master 程式和 worker 程式兩種程式:master 程式只有一個,負責監聽埠,接收來自 Web Server 的請求,而 worker 程式則一般有多個 (具體數量根據實際需要配置),每個程式內部都嵌入了一個 PHP 直譯器,是 PHP 程式碼真正執行的地方,下圖是我本機上 fpm 的程式情況,1一個 master 程式,好多個 worker 程式:
從 FPM 接收到請求,到處理完畢,其具體的流程如下:
- FPM 的 master 程式接收到請求
- master 程式根據配置指派特定的 worker 程式進行請求處理,如果沒有可用程式,返回錯誤,這也是我們配合 Nginx 遇到502錯誤比較多的原因。
- worker 程式處理請求,如果超時,返回504錯誤
- 請求處理結束,返回結果
FPM 從接收到處理請求的流程就是這樣了,那麼 Nginx 又是如何傳送請求給 fpm 的呢?這就需要從 Nginx 層面來說明了。
我們知道,Nginx 不僅僅是一個 Web 伺服器,也是一個功能強大的 Proxy 伺服器,除了進行 http 請求的代理,也可以進行許多其他協議請求的代理,包括本文與 fpm 相關的 fastcgi 協議。為了能夠使 Nginx 理解 fastcgi 協議,Nginx 提供了 fastcgi 模組來將 http 請求對映為對應的 fastcgi 請求。
Nginx+PHP的工程模式下,兩位主角分工明確,Nginx負責承載HTTP請求的響應與返回,以及超時控制記錄日誌等HTTP相關的功能,而PHP則負責處理具體請求要做的業務邏輯,它們倆的這種合作模式也是常見的分層架構設計中的一種,在它們各有專注面的同時,FastCGI又很好的將兩塊銜接,保障上下游通訊互動。
Nginx+php-fpm實現原理
Nginx本身不會對PHP進行解析,終端對PHP頁面的請求將會被Nginx交給FastCGI程式監聽的IP地址及埠,由php-fpm作為動態解析伺服器處理,最後將處理結果再返回給nginx。其實,Nginx就是一個反向代理伺服器。Nginx通過反向代理功能將動態請求轉向後端php-fpm,從而實現對PHP的解析支援,這就是Nginx實現PHP動態解析的原理。
Nginx不支援對外部程式的直接呼叫或者解析,所有的外部程式(包括PHP)必須通過FastCGI介面來呼叫。FastCGI介面在Linux下是socket(這個socket可以是檔案socket,也可以是ip socket)。為了呼叫CGI程式,還需要一個FastCGI的wrapper(wrapper可以理解為用於啟動另一個程式的程式),這個wrapper繫結在某個固定socket上,如埠或者檔案socket。當Nginx將CGI請求傳送給這個socket的時候,通過FastCGI介面,wrapper接收到請求,然後派生出一個新的執行緒,這個執行緒呼叫直譯器或者外部程式處理指令碼並讀取返回資料;接著,wrapper再將返回的資料通過FastCGI介面,沿著固定的socket傳遞給Nginx;最後,Nginx將返回的資料傳送給客戶端。
Nginx 簡單配置
location ~ \.php$ {
root /data/wwwroot/default/nginx/html/;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /home/admin/web/nginx/html/$fastcgi_script_name;
include fastcgi_params;
}
FastCGI原理
FastCGI是一個運用於Http Server和動態指令碼語言間通訊的介面,多數流行的Http Server都支援FastCGI,包括Apache、Nginx和lighttpd等。同時,FastCGI也被許多指令碼語言支援,其中就有PHP。
FastCGI介面方式採用C/S結構,可以將HttP伺服器和指令碼解析伺服器分開,同時在指令碼解析伺服器上啟動一個或者多個指令碼解析守護程式。當HttP伺服器每次遇到動態程式時,可以將其直接交付給FastCGI程式來執行,然後將得到的結果返回給客戶端。這種方式可以讓HttP伺服器專一地處理靜態請求或者將動態指令碼伺服器的結果返回給客戶端,這在很大程度上提高了整個應用系統的效能。
當nginx接收到一個http請求時,通過配置檔案找到對應的server。然後匹配server中的所有location,找到最匹配的。而在location中的命令會啟動不同的模組去完成工作,比如rewrite模組、index模組。因此在nginx中模組可以看作真正的勞動工作者。nginx的模組是被編譯到nginx中的,屬於靜態方式。啟動nginx時,模組被自動載入。不像apache,把模組單獨編譯成so檔案,在配置檔案中指定是否載入。所以,單比模組載入方面,nginx也比apache速度上有提升。
那nginx是怎麼呼叫php的呢?先看下面的nginx中關於php的配置
location ~ \.php$ {
root /webpath;
fastcgi_pass 127.0.0.1:9000;
....
}
這個location指令把以php為檔案字尾的請求,交給127.0.0.1:9000處理。我想你看到這個應該猜到了,這是一個C/S架構東西。 而這裡的IP地址和埠(127.0.0.1:9000)就是fastcgi程式監聽的IP地址和埠。fastcgi是一個可伸縮地、高速地在http server和動態指令碼語言間通訊的介面。多數流行的http server都支援fastcgi,包括apache、nginx和lighttpd等。同時,fastcgi也被許多指令碼語言支援,其中就有php。
那這個fastcgi的配置IP和埠從何而來呢?在php-fpm.conf中可以看到如下:
listen = 127.0.0.1:9000 #這個表示php的fastcgi程式監聽的ip地址以及埠
pm.start_servers = 2
php-fpm作為fastcgi的程式管理器,可以有效控制記憶體和程式,並且平滑過載php配置。php5.3以後,php-fpm被整合到php的core中,預設安裝,無須配置。
fastcgi程式管理器php-fpm自身初始化,啟動主程式php-fpm和啟動start_servers個fastcgi子程式。主程式php-fpm主要是管理fastcgi子程式,監聽9000埠,fastcgi子程式等待請求。當客戶端請求到達nginx時,nginx通過location指令,將所有以php為字尾的檔案都交給 127.0.0.1:9000 來處理。php-fpm選擇並連線到一個fastcgi子程式,並將環境變數和標準輸入傳送到fastcgi子程式。fastcgi子程式完成處理後將標準輸出和錯誤資訊返回。當fastcgi子程式關閉連線時,請求便告處理完成,等待下次處理。