1. 程式人生 > >理解 Nginx 和PHP-FPM

理解 Nginx 和PHP-FPM

FastCGI模組模組允許nginx同FastCGI協同工作,並且控制哪些引數將被安全傳遞。

一、CGI和FastCGI簡介

1、什麼是CGI

CGI 是Web 伺服器執行時外部程式的規範介面,按CGI 編寫的程式可以擴充套件伺服器功能。CGI 應用程式能與瀏覽器進行互動,還可通過資料庫API 與資料庫伺服器等外部資料來源進行通訊,從資料庫伺服器中獲取資料。格式化為HTML文件後,傳送給瀏覽器,也可以將從瀏覽器獲得的資料放到資料庫中。幾乎所有伺服器都支援CGI,可用任何語言編寫CGI。

傳統CGI介面方式的主要缺點是效能很差,因為每次HTTP伺服器遇到動態程式時都需要重新啟動指令碼解析器來執行解析,然後將結果返回給HTTP伺服器。這在處理高併發訪問時幾乎是不可用的,另外傳統的CGI介面方式安全性也很差,現在已經很少使用了。

2、什麼是 FastCGI

FastCGI是一個可伸縮地、高速地在HTTP server和動態指令碼語言間通訊的介面。多數流行的HTTP server都支援FastCGI,包括Apache、Nginx和lighttpd等。同時FastCGI也被許多指令碼語言支援,其中就有PHP。FastCGI是從CGI發展改進而來的。
FastCGI介面方式採用C/S結構,可以將HTTP伺服器和指令碼解析伺服器分開,同時在指令碼解析伺服器上啟動一個或者多個指令碼解析守護程序。當HTTP伺服器每次遇到動態程式時,可以將其直接交付給FastCGI程序來執行,然後將得到的結果返回給瀏覽器。
這種方式的優點:可以讓HTTP伺服器專一地處理靜態請求或者將動態指令碼伺服器的結果返回給客戶端,這在很大程度上提高了整個應用系統的效能。

二、Nginx+FastCGI執行原理

1、執行原理

Nginx不支援對外部程式的直接呼叫或者解析,所有的外部程式(包括PHP)必須通過FastCGI介面來呼叫。FastCGI介面在Linux下是socket(這個socket可以是檔案socket,也可以是ip socket)。

wrapper:為了呼叫CGI程式,還需要一個FastCGI的wrapper(wrapper可以理解為用於啟動另一個程式的程式),這個wrapper繫結在某個固定socket上,如埠或者檔案socket。當Nginx將CGI請求傳送給這個socket的時候,通過FastCGI介面,wrapper接收到請求,然後Fork(派生)出一個新的執行緒,這個執行緒呼叫直譯器或者外部程式處理指令碼並讀取返回資料;接著,wrapper再將返回的資料通過FastCGI介面,沿著固定的socket傳遞給Nginx;最後,Nginx將返回的資料(html頁面或者圖片)傳送給客戶端。這就是Nginx+FastCGI的整個運作過程,如圖1-3所示。
在這裡插入圖片描述


所以,我們首先需要一個wrapper,這個wrapper需要完成的工作:
1、通過呼叫fastcgi(庫)的函式通過socket和ningx通訊(讀寫socket是fastcgi內部實現的功能,對wrapper是非透明的)
2、排程thread,進行fork和kill
3、和application(php)進行通訊

Nginx是個輕量級的HTTP server,必須藉助第三方的FastCGI處理器才可以對PHP進行解析,因此其實這樣看來nginx是非常靈活的,它可以和任何第三方提供解析的處理器實現連線從而實現對PHP的解析(在nginx.conf中很容易設定)。
FastCGI介面方式在指令碼解析伺服器上啟動一個或者多個守護程序對動態指令碼進行解析,這些程序就是FastCGI程序管理器,或者稱為FastCGI引擎。 spawn-fcgi與PHP-FPM就是支援PHP的兩個FastCGI程序管理器。因此HTTPServer完全解放出來,可以更好地進行響應和併發處理。

FastCGI 的主要優點是把動態語言和HTTP Server分離開來,所以Nginx與PHP/PHP-FPM經常被部署在不同的伺服器上,以分擔前端Nginx伺服器的壓力,使Nginx專一處理靜態請求和轉發動態請求,而PHP/PHP-FPM伺服器專一解析PHP動態請求。

2、Nginx+PHP-FPM

PHP-FPM是管理FastCGI的一個管理器,它作為PHP的外掛存在,在安裝PHP要想使用PHP-FPM時在老php的老版本(php5.3.3之前)就需要把PHP-FPM以補丁的形式安裝到PHP中,而且PHP要與PHP-FPM版本一致,這是必須的)

PHP-FPM其實是PHP原始碼的一個補丁,旨在將FastCGI程序管理整合進PHP包中。必須將它patch到你的PHP原始碼中,在編譯安裝PHP後才可以使用。

PHP5.3.3已經整合php-fpm了,不再是第三方的包了。PHP-FPM提供了更好的PHP程序管理方式,可以有效控制記憶體和程序、可以平滑過載PHP配置,比spawn-fcgi具有更多優點,所以被PHP官方收錄了。

在./configure的時候帶 –enable-fpm引數即可開啟PHP-FPM。fastcgi已經在php5.3.5的core中了,不必在configure時新增 --enable-fastcgi了。老版本如php5.2的需要加此項。

3、配置資訊

關於fastcgi的配置檔案,目前fastcgi的配置檔案一般放在nginx.conf同級目錄下,配置檔案形式,一般有兩種:fastcgi.conf 和 fastcgi_params。不同的nginx版本會有不同的配置檔案,這兩個配置檔案有一個非常重要的區別:

fastcgi_parames檔案中缺少下列配置:
fastcgi_param SCRIPT_FILENAME document_rootfastcgi_script_name;

我們可以開啟fastcgi_parames檔案加上上述行,也可以在要使用配置的地方動態新增。使得該配置生效。

當我們安裝Nginx和PHP-FPM完後,配置資訊:

PHP-FPM的預設配置php-fpm.conf:

listen_address 127.0.0.1:9000 #這個表示php的fastcgi程序監聽的ip地址以及埠
start_servers
min_spare_servers
max_spare_servers

Nginx配置執行php: 編輯nginx.conf加入如下語句:

location ~ \.php$ { 
    root html; 
    fastcgi_pass 127.0.0.1:9000; #指定了fastcgi程序偵聽的埠,nginx就是通過這裡與php互動的 
    fastcgi_index index.php;
    include fastcgi_params; 
    fastcgi_param SCRIPT_FILENAME /usr/local/nginx/html$fastcgi_script_name; 
}

Nginx通過location指令,將所有以php為字尾的檔案都交給127.0.0.1:9000來處理,而這裡的IP地址和埠就是FastCGI程序監聽的IP地址和埠。
輸入命令 netstat -nlpt|grep php-fpm 會得到:
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN 1057/php-fpm
這裡的127.0.0.1:9000 就是監聽本機9000埠的意思。

4、整體工作流程:

1)、FastCGI程序管理器php-fpm自身初始化,啟動主程序php-fpm和啟動start_servers個CGI 子程序。主程序php-fpm主要是管理fastcgi子程序,監聽9000埠。fastcgi子程序等待來自Web Server的連線。

2)、當客戶端請求到達Web Server Nginx是時,Nginx通過location指令,將所有以php為字尾的檔案都交給127.0.0.1:9000來處理,即Nginx通過location指令,將所有以php為字尾的檔案都交給127.0.0.1:9000來處理。

3)FastCGI程序管理器PHP-FPM選擇並連線到一個子程序CGI直譯器。Web server將CGI環境變數和標準輸入傳送到FastCGI子程序。

4)、FastCGI子程序完成處理後將標準輸出和錯誤資訊從同一連線返回Web Server。當FastCGI子程序關閉連線時,請求便告處理完成。

5)、FastCGI子程序接著等待並處理來自FastCGI程序管理器(執行在 WebServer中)的下一個連線。

簡單來說,當需要處理php請求時,nginx的worker程序會將請求移交給php-fpm的worker程序進行處理,也就是最開頭所說的nginx呼叫了php,其實嚴格得講是nginx間接呼叫php。

5、Nginx和PHP-FPM的通訊方式

Nginx和PHP-FPM的程序間通訊有兩種方式

一種是TCP
一種是UNIX Domain Socket.

其中TCP是IP加埠,可以跨伺服器。而UNIX Domain Socket不經過網路,只能用於Nginx跟PHP-FPM都在同一伺服器的場景。
用哪種取決於你的PHP-FPM配置:

方式1:
php-fpm.conf: listen = 127.0.0.1:9000
nginx.conf: fastcgi_pass 127.0.0.1:9000;
方式2:
php-fpm.conf: listen = /tmp/php-fpm.sock
nginx.conf: fastcgi_pass unix:/tmp/php-fpm.sock;
其中php-fpm.sock是一個檔案,由php-fpm生成,型別是srw-rw----。

UNIX Domain Socket可用於兩個沒有親緣關係的程序,是目前廣泛使用的IPC機制,比如X Window伺服器和GUI程式之間就是通過UNIX Domain Socket通訊的。這種通訊方式是發生在系統核心裡而不會在網路裡傳播。UNIX Domain Socket和長連線都能避免頻繁建立TCP短連線而導致TIME_WAIT連線過多的問題。對於程序間通訊的兩個程式,UNIX Domain Socket的流程不會走到TCP那層,直接以檔案形式,以stream socket通訊。如果是TCP Socket,則需要走到IP層,對於非同一臺伺服器上,TCP Socket走的就更多了。

瀏覽器訪問網頁的過程

  • 請求靜態頁面
    Browser請求http://xxx.com/aa.html -> Web Server(Nginx/Apache)分發 -> 找到aa.html檔案返回給Browser

  • 請求動態頁面
    Browser請求http://xxx.com/bb.php -> Web Server(Nginx/Apache)分發 -> PHP解析器(PHP-CGI程式)-> 返回處理結果給Web Server -> 返回資料給Browser。
    原理:伺服器根據配置檔案,知道這是一個PHP指令碼檔案,需要去找PHP解析器來處理。

PHP解析器會解析php.ini檔案初始化執行環境,然後處理請求,再以標準的資料格式返回處理結果,最後退出程序。

CGI 程式到 FPM 進化史

在這裡插入圖片描述

FastCGI

FastCGI,顧名思義就是更快的CGI程式,用來提高CGI程式效能,它允許在一個程序內處理多個請求,而不是一個請求處理完畢就直接結束程序,效能上有了很大的提高。

  • 提高效能?那麼CGI程式的效能問題在哪呢?

PHP解析器會解析php.ini檔案,初始化執行環境,就是這裡了。
標準的CGI程式對每個請求都會執行這些步驟(不閒累啊!啟動程序很累的說!),所以處理每個請求的時間會比較長。這明顯不合理嘛!

  • 那麼FastCGI是怎麼做的呢?

首先,FastCGI會先啟一個master程序,解析配置檔案,初始化執行環境,然後再啟動多個worker程序。當請求過來時,master會傳遞給一個worker,然後立即可以接受下一個請求。
這樣就避免了重複的勞動,效率自然是高。

而且當worker不夠用時,master可以根據配置預先啟動幾個worker等著。
當然空閒worker太多時,也會停掉一些,這樣就提高了效能,也節約了資源。這就是FastCGI的對程序的管理。
ps:也有一些能夠排程PHP-CGI程序的程式,比如說由lighthttpd分離出來的spawn-fcgi。好了,PHP-FPM也是這麼個東東,在長時間的發展後,逐漸得到了大家的認可(要知道前幾年大家可是抱怨PHP-FPM穩定性太差的),也越來越流行。
在這裡插入圖片描述

PHP-FPM(FastCGI Process Manager)

它是FastCGI協議的一個實現,任何實現了FastCGI協議的伺服器都能夠與之通訊。
FPM之於標準的FastCGI程式,也提供了一些增強功能,具體可以參考官方文件:PHP: FPM Installation http://php.net/manual/en/install.fpm.install.php

  • FPM是一個PHP程序管理器,包含masterworker兩種程序。
    master程序只有一個,負責監聽埠,接收來自伺服器的請求,而worker程序則一般有多個(具體數量根據實際需要配置),每個程序內部都嵌入了一個PHP直譯器,是PHP程式碼真正執行的地方,下面是我本機上FPM的程序情況:1個master程序,2個worker程序。
    在這裡插入圖片描述
  • 從FPM接收到請求,到處理完畢,其具體的流程如下:
  1. FPM的master程序接收到請求。
  2. master程序根據配置指派特定的worker程序進行請求處理,如果沒有可用程序,返回錯誤,這也是我們配合Nginx遇到502錯誤比較多的原因。
  3. worker程序處理請求,如果超時,返回504錯誤。
  4. 請求處理結束,返回結果。
  • FPM從接收到處理請求的流程就是這樣了,那麼Nginx又是如何傳送請求給FPM的呢?
    這就需要從Nginx層面來說明了。
    我們知道,Nginx不僅僅是一個Web伺服器,也是一個功能強大的Proxy伺服器,除了進行http請求的代理,也可以進行許多其他協議請求的代理,包括本文與FPM相關的FastCGI協議。為了能夠使Nginx理解FastCGI協議,Nginx提供了FastCGI模組來將http請求對映為對應的FastCGI請求。
    Nginx的FastCGI模組提供了fastcgi_param指令來主要處理這些對映關係,下面 是Nginx的一個配置檔案例項,其主要完成的工作是將Nginx中的變數翻譯成PHP中能夠理解的變數。
$ cat /usr/local/nginx/conf/fastcgi.conf

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REQUEST_SCHEME     $scheme;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

除此之外,非常重要的就是fastcgi_pass指令了,這個指令用於指定FPM程序監聽的地址,Nginx會把所有的PHP請求翻譯成FastCGI請求之後再發送到這個地址。下面一個簡單的可以工作的Nginx配置檔案:

server {
    listen 80;
    server_name test.me;
    root /usr/local/web/myproject/public;
    index index.php index.html index.htm;

    access_log /usr/local/nginx/logs/test-access.log;
    error_log  /usr/local/nginx/logs/test-error.log;

    location / {
      try_files $uri $uri/ /index.php?$query_string;
    }

    location ~\.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /usr/local/web/myproject/public/$fastcgi_script_name;
        fastcgi_pass unix:/usr/local/php/var/run/php-fpm.sock;
        fastcgi_index index.php;
    }
}

在這個配置檔案中,我們新建了一個虛擬主機,監聽80埠,專案根目錄為 /usr/local/web/myproject/public。然後我們通過location指令,將所有的以.php結尾的請求都交給FastCGI模組處理,從而把所有的PHP請求都交給了FPM處理,從而完成Nginx到FPM的閉環。
如此以來,Nginx與FPM通訊的整個流程應該比較清晰了。

在這裡插入圖片描述

  • 修改了php.ini配置檔案後,使用PHP-FPM為什麼能平滑重啟?
    修改php.ini之後,PHP-CGI程序是沒辦法平滑重啟的。
    PHP-FPM對此的處理機制是新的worker程序用新的配置,已經存在的worker程序處理完手上的活就可以歇著了,通過這種機制來平滑過渡。

原文連結:
https://www.jianshu.com/p/eab11cd1bb28, https://www.jianshu.com/p/da152c6fdfa6