PHP的工作模型非常特殊。從某種程度上說,PHP和ASP、ASP.NET、JSP/Servlet等流行的Web技術,有著本質上的區別。
以Java為例,Java在Web應用領域,有兩種技術:Java Servlet和JSP(Java Server Page)。Java Servlet是一種特殊類型的Java程序,它通過實現相關接口,處理Web服務器發送過來的請求,完成相應的工作。JSP在形式上是一種類似於PHP的腳本,但是事實上,它最後也被編譯成Servlet。也就是說,在Java解決方案中,JSP和Servlet是作為獨立的Java應用程序執行的,它們在初始化之後就駐留內存,通過特定的接口和Web服務器通信,完成相應工作。除非被顯式地重啟,否則它們不會終止。因此,可以在JSP和Servlet中使用各種緩存技術,例如數據庫連接池。
ASP.NET的機制與此類似。至於ASP,雖然也是一種解釋型語言,但是仍然提供了Application對象來存放應用程序級的全局變量,它依托於ASP解釋器在IIS中駐留的進程,在整個應用程序的生命期有效。
PHP是一種純解釋型在服務端執行的可以內嵌HTML的腳本語言,尤其適合開發Web應用程序。請求一個 PHP 腳本時,PHP 會讀取該腳本,並將其編譯為 Zend 操作碼,這是要執行的代碼的一種二進制表示形式。隨後,此操作碼由 PHP 執行並丟棄。 PHP腳本在每次被解釋時進行初始化,在解釋完畢後終止運行。這種運行是互相獨立的,每一次請求都會創建一個單獨的進程或線程,來解釋相應的頁面文件。頁面創建的變量和其他對象,都只在當前的頁面內部可見,無法跨越頁面訪問。在終止運行後,頁面中申請的、沒有被代碼顯式釋放的外部資源,包括內存、數據庫連接、文件句柄、Socket連接等,都會被強行釋放。也就是說,PHP無法在語言級別上實現直接訪問跨越頁面的變量,也無法創建駐留內存的對象。
<?php class StaticVarTester { public static $StaticVar = 0; } function TestStaticVar() { StaticVarTester :: $StaticVar += 1; echo "StaticVarTester :: StaticVar = " . StaticVarTester :: $StaticVar; } TestStaticVar(); echo "<br / >"; TestStaticVar(); ?>
在這個例子中,定義了一個名為StaticVarTester的類,它僅有一個公共的靜態成員$StaticVar,並被初始化為0。然後,在TestStaticVar()函數中,對StaticVarTester :: $StaticVar進行累加操作,並將它打印輸出。
熟悉Java或C++的開發者對這個例子應該並不陌生。$StaticVar作為StaticVarTester類的一個靜態成員,只在類被裝載時進行初始化,無論StaticVarTester類被實例化多少次,$StaticVar都只存在一個實例,而且不會被多次初始化。因此,當第一次調用TestStaticVar()函數時,$StaticVar進行了累加操作,值為1,並被保存。第二次調用TestStaticVar()函數,$StaticVar的值為2。
打印出來的結果和我們預料的一樣:
StaticVarTester :: StaticVar = 1 StaticVarTester :: StaticVar = 2
但是,當浏覽器刷新頁面,再次執行這段代碼時,不同的情況出現了。在Java或C++裡面,$StaticVar的值會被保存並一直累加下去,我們將會看到如下的結果:
StaticVarTester :: StaticVar = 3 StaticVarTester :: StaticVar = 4 …
但是在PHP中,由於上文敘及的機制,當前頁面每次都解釋時,都會執行一次程序初始化和終止的過程。也就是說,每次訪問時,StaticVarTester都會被重新裝載,而下列這行語句 public static $StaticVar = 0; 也會被重復執行。當頁面執行完成後,所有的內存空間都會被回收,$StaticVar這個變量(連同整個StaticVarTester類)也就不復存在。因此,無論刷新頁面多少次,$StaticVar變量都會回到起點:先被初始化為0,然後在TestStaticVar()函數調用中被累加。所以,我們看到的結果永遠是這個:
StaticVarTester :: StaticVar = 1 StaticVarTester :: StaticVar = 2
PHP這種獨特的工作模型的優勢在於,基本上解決了令人頭疼的資源洩漏問題。Web應用的特點是大量的、短時間的並發處理,對各種資源的申請和釋放工作非常頻繁,很容易導致洩漏甚至崩潰。PHP的運行機制決定它不存在常規的崩潰問題(頂多連接超時腳本停止執行),可以說PHP是較穩定的Web應用。但是,這種機制的缺點也非常明顯。最直接的後果是,PHP在語言級別無法實現跨頁面的緩沖機制。這種緩沖機制缺失造成的影響,可以分成兩個方面:
一是對象的緩沖。眾所周知,很多設計模式都依賴於對象的緩沖機制,創建和銷毀對象是很費時間的,因為創建一個對象要獲取內存資源或者其它更多資源,對於需要頻繁應付大量並發的服務端軟件更是如此。因此,對象緩沖的缺失,理論上會極大地降低速度。應盡可能減少創建和銷毀對象的次數來提高服務程序的效率,由於 PHP目前還不支持多線程,也就無法像Java一樣通過線程池調度來彌補這一缺陷;但可以使用第三方軟件如Memcachd來實現PHP的對象緩沖機制,達到減少對象創建和銷毀的時間來提高服務程序的效率。Memcachd將PHP編譯後的 操作碼緩存並在內存中保存這個操作碼,並在下一次調用該頁面時重用它,這會節省很多時間。比較常用的緩存還有有 eAccelerator,另一種流行的 eAccelerator 替代工具是 Alternative PHP Cache(APC)。
二是數據庫連接的緩沖。對於MySQL,PHP提供了一種內置的數據庫緩沖機制,即用mysql_pconnect()代替mysql_connect() 來打開數據庫而已。PHP會自動回收被廢棄的數據庫連接,以供重復使用。在實際應用中,這種持久性數據庫連接往往會導致數據庫連接的偽洩漏現象:在某個時間,並發的數據庫連接過多,超過了MySQL的最大連接數,從而導致新的進程無法連接數據庫。但是過一段時間,當並發數減少時,PHP會釋放掉一些連接,網站又會恢復正常。出現這種現象的原因是,當使用pconnect時,Apache 的httpd進程會不釋放connect,而當Apache的httpd進程數超過了mysql的最大連接數時,就會出現無法連接的情況。因此,需要小心地調整Apache和Mysql的配置,以使Apache的httpd進程數不會超出MySQL的最大連接數。筆者經過實踐,在PHP5和 Oracle10g的連接中,由於頻於數據庫連接,有時候還會出現數據庫丟失連接的情況(Oracle官方有針對PHP的增強包,不知是否可以解決此問題,筆者未試)。
PHP的工作模型即是缺點也是優勢,從本質上說,這就是PHP 的獨特之處。
若以FastCGI模式運行php,解析php.ini、載入全部擴展並重初始化全部數據結構這些都只在進程啟動時發生一次。一個額外的好處是,持續數據庫連接可以工作。Nginx+PHP(FastCGI)是個不錯的選擇。