PHP運行原理解析

前言

一直對php的運行原理一知半解,某個週末查詢了網上的資料,不才有了一些淺顯的理解。特此記錄下來。文章主要分爲兩個模塊進行介紹:php代碼內部運行原理php運行模式。本文原理介紹的大部分文字是直接引用他人文章,本文是按照個人理解的邏輯將收集的資料重新組織了起來,並添加了個人的一些理解。

php內部運行原理

這一塊的內容暫時理解的不是很深入,直接引用他人的解釋。
什麼是 Zend ? 什麼是 PHP ?
Zend是語言引擎,PHP內核。PHP是從外層展現的完整系統。咋一聽似乎有點模糊不清,但是其實並不複雜( 看下面).爲了實現一個 web 腳本解釋器,你需要三個部分:

  1. 第一:解釋器部分分析輸入代碼,翻譯代碼,然後執行代碼。
  2. 第二:功能部分 完成語言的功能(函數,等等)。
  3. 第三:接口部分與web通信,等等。

Zend完全參與第一部分,部分參與第二部分;PHP參與第二部分和三部分.他們一起構成完整的PHP包。實際上Zend自己僅僅構成語言核心,用預定義函數實現 PHP 非常基礎部分。而 PHP 包含所有的實際形成語言突出能力的所有模塊。
php基本運行原理
關於php的擴展等知識以後瞭解後再補充,這裏理解到這個程度就足夠了。

php運行模式

本文所講的運行模式特指使用Apache和nginx等服務器協作的php項目。這裏討論的簡單來說是nginx,Apache是怎麼和php通信的這個問題
這裏我將收集到的邏輯畫成了一張圖:
nignx和apache調用php的方式對比
對上圖的解釋如下:

一切的開始:SAPI

SAPI(Server Application Programming Interface)指的是PHP具體應用的編程接口, 就像PC一樣,無論安裝哪些操作系統,只要滿足了PC的接口規範都可以在PC上正常運行, PHP腳本要執行有很多種方式,通過Web服務器,或者直接在命令行下,也可以嵌入在其他程序中。

通常,我們使用Apache或者Nginx這類Web服務器來測試PHP腳本,或者在命令行下通過PHP解釋器程序來執行。 腳本執行完後,Web服務器應答,瀏覽器顯示應答信息,或者在命令行標準輸出上顯示內容。

我們很少關心PHP解釋器在哪裏。雖然通過Web服務器和命令行程序執行腳本看起來很不一樣, 實際上它們的工作流程是一樣的。命令行參數傳遞給PHP解釋器要執行的腳本, 相當於通過url請求一個PHP頁面。腳本執行完成後返回響應結果,只不過命令行的響應結果是顯示在終端上。

腳本執行的開始都是以SAPI接口實現開始的。只是不同的SAPI接口實現會完成他們特定的工作, 例如Apache的mod_php SAPI實現需要初始化從Apache獲取的一些信息,在輸出內容是將內容返回給Apache, 其他的SAPI實現也類似。

而PHP最常用的SAPI提供的2種連接方法:mod_phpmod_fastcgi

mod_php和mod_fastcgi

當Apache採用mod_php模式工作時,需要有如下的配置:
apache配置
也即php作爲Apache的一個子模塊來運行,當通過web訪問php文件時,Apache就會調用php5_module來解析php代碼。

配置加載mod_php模塊後,php便是Apahce進程本身一部分,每個新的Apache子進程都會加載此模塊。這種模式相當於Apache自己就能運行php程序,這樣會有一些弊端:
mod_php 通過嵌入 PHP 解釋器到 Apache 進程中,mod_php 這種嵌入的方式最大的弊端就是內存佔用大,不論是否用到 PHP 解釋器都會將其加載到內存中,典型的就是處理CSS、JS之類的靜態文件是完全沒有必要加載解釋器。參考此篇文章。

CGI和FastCGI

CGI全稱是「通用網關接口」(Common Gateway Interface),它可以讓一個客戶端,從網頁瀏覽器向執行在Web服務器上的程序請求數據。 CGI描述了客戶端和這個程序之間傳輸數據的一種標準。 CGI的一個目的是要獨立於任何語言的,所以CGI可以用任何一種語言編寫,只要這種語言具有標準輸入、輸出和環境變量。 如php,perl,tcl等。

CGI運行原理表述如下:

  1. 客戶端訪問某個 URL 地址之後,通過 GET/POST/PUT 等方式提交數據,並通過 HTTP 協議向 Web 服務器發出請求。
  2. 服務器端的 HTTP Daemon(守護進程)啓動一個子進程。然後在子進程中,將 HTTP 請求裏描述的信息通過標準輸入 stdin和環境變量傳遞給 URL 指定的 CGI 程序,並啓動此應用程序進行處理,處理結果通過標準輸出 stdout 返回給 HTTP Daemon 子進程。
  3. 再由 HTTP Daemon 子進程通過 HTTP 協議返回給客戶端。

上面的這段話理解可能還是比較抽象,下面我們就通過一次 GET 請求爲例進行詳細說明。
cgi運行示意圖
如圖所示,本次請求的流程如下:

  1. 客戶端訪問 http://127.0.0.1:9003/cgi-bin/user?id=1
  2. 127.0.0.1 上監聽 9003 端口的守護進程接受到該請求
  3. 通過解析 HTTP 頭信息,得知是 GET 請求,並且請求的是 /cgi-bin/ 目錄下的 user 文件。
  4. 將 uri 裏的 id=1 通過存入 QUERY_STRING 環境變量。
  5. Web 守護進程 fork 一個子進程,然後在子進程中執行 user 程序,通過環境變量獲取到id。
  6. 執行完畢之後,將結果通過標準輸出返回到子進程。
  7. 子進程將結果返回給客戶端。

FastCGI是Web服務器和處理程序之間通信的一種協議, 是CGI的一種改進方案,FastCGI像是一個常駐(long-lived)型的CGI, 它可以一直執行,在請求到達時不會花費時間去fork一個進程來處理(這是CGI最爲人詬病的fork-and-execute模式)。 正是因爲他只是一個通信協議,它還支持分佈式的運算,所以 FastCGI 程序可以在網站服務器以外的主機上執行,並且可以接受來自其它網站服務器的請求。

FastCGI 是與語言無關的、可伸縮架構的 CGI 開放擴展,將 CGI 解釋器進程保持在內存中,以此獲得較高的性能。 CGI 程序反覆加載是 CGI 性能低下的主要原因,如果 CGI 程序保持在內存中並接受 FastCGI 進程管理器調度, 則可以提供良好的性能、伸縮性、Fail-Over 特性等。原理表述如下:

  1. FastCGI 進程管理器自身初始化,啓動多個 CGI 解釋器進程,並等待來自 Web Server 的連接。
  2. Web 服務器與 FastCGI 進程管理器進行 Socket 通信,通過 FastCGI 協議發送 CGI 環境變量和標準輸入數據給 CGI 解釋器進程。
  3. CGI 解釋器進程完成處理後將標準輸出和錯誤信息從同一連接返回 Web Server。
  4. CGI 解釋器進程接着等待並處理來自 Web Server 的下一個連接。
    FastCGI運行原理
    FastCGI 與傳統 CGI 模式的區別之一則是 Web 服務器不是直接執行 CGI 程序了,而是通過 Socket 與 FastCGI 響應器(FastCGI 進程管理器)進行交互,也正是由於 FastCGI 進程管理器是基於 Socket 通信的,所以也是分佈式的,Web 服務器可以和 CGI 響應器服務器分開部署。Web 服務器需要將數據 CGI/1.1 的規範封裝在遵循 FastCGI 協議包中發送給 FastCGI 響應器程序。
    PS:上述原理介紹引用自此篇文章

PHP-FPM

對FastCGI有一個通俗的解釋:FastCGI事先就需要啓動,而且可以啓動多個CGI模塊,在那裏一直運行等着web發請求,然後再給php解析運算,完成後生成html返回給web後,但是完成後它不會退出,而是繼續等着下一個web請求。參考這裏。

PHP-FPM就是針對於PHP的FastCGI的一種實現,他負責管理一個進程池,來處理來自Web服務器的請求。

但是PHP-FPM僅僅是個「PHP FastCGI 進程管理器」, 它仍會調用PHP解釋器本身來處理請求,PHP解釋器(在Windows下)就是php-cgi.exe

nginx的配置

nginx通常採用PHP-FPM模式運行php程序,它的基本配置如下:
nginx的php配置
Apache這裏就先不補充了。

總結

php的編程接口SAPI提供兩種連接模式mod_php和mod_fastapi。本文圍繞這個兩種模式展開了相關介紹。參考文章已在文中列出。