Php工作模型和運行機制

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卻完全不是這樣。作爲一種純解釋型語言,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應用的特點是大量的、短時間的併發處理,對各種資源的申請和釋放工作非常頻繁,很容易導致泄漏。

同時,大量的動態html腳本的存在,使得追蹤和調試的工作都非常困難。PHP的運行機制,以一種非常簡單的方式避免了這個問題,同時也避免了將程序員帶入到繁瑣的緩衝池和同步等問題中去。在實踐中,基於PHP的應用往往比基於Java或.NET的應用更加穩定,不會出現由於某個頁面的BUG而導致整個站點崩潰的問題。

(相比之下,Java或.NET應用可能因爲緩衝池崩潰或其他的非法操作,而導致整個站點崩潰。)後果是,即使PHP程序員水平不高,也無法寫出使整個應用崩潰的代碼。PHP腳本可以一次調用極多的資源,從而導致頁面執行極爲緩慢,但是執行完畢後所有的資源都會被釋放,應用仍然不會崩潰。

甚至即使執行了一個死循環,也會在30秒(默認時間)後因爲超時而中止。從理論上來說,基於PHP的應用是不太可能崩潰的,因爲它的運行機制決定它不存在常規的崩潰這個問題。在實踐中,很多開發者也認爲PHP是最穩定的Web應用。

 但是,這種機制的缺點也非常明顯。最直接的後果是,PHP在語言級別無法實現跨頁面的緩衝機制。這種緩衝機制缺失造成的影響,可以分成兩個方面:

  一是對象的緩衝。如我們所知,很多設計模式都依賴於對象的緩衝機制,對於需要頻繁應付大量併發的服務端軟件更是如此。因此,對象緩衝的缺失,理論上會極大地降低速度。但是,由於PHP本身的定位和工作機制等原因,它在實際工作中的速度非常快。就作者自己的經驗來看,在小型的Web應用中,PHP至少不比 Java慢。

在大型的應用中,爲了榨乾每一分硬件資源,即使PHP本身足夠快,一個優秀的對象緩衝機制仍然是必要的。在這種情況下,可以使用第三方的內存緩衝軟件,如Memcached。由於Memcached本身的優異特性(高性能,支持跨服務器的分佈式存儲,和PHP的無縫集成等),在大型的PHP應用中, Memcached幾乎已經成爲不可或缺的基礎設施了。比起使用PHP語言自己實現對象緩衝來,這種第三方解決方案似乎更好一些。

   二是數據庫連接的緩衝。對MySQL,PHP提供了一種內置的數據庫緩衝機制,使用起來非常簡單,程序員需要做的只是用mysql_pconnect()代替mysql_connect()來打開數據庫而已。

PHP會自動回收被廢棄的數據庫連接,以供重複使用。具有諷刺意味的是,在實際應用中,這種持久性數據庫連接往往會導致數據庫連接的僞泄漏現象:在某個時間,併發的數據庫連接過多,超過了MySQL的最大連接數,從而導致新的進程無法連接數據庫。

但是過一段時間,當併發數減少時,PHP會釋放掉一些連接,網站又會恢復正常。出現這種現象的原因是,當使用pconnect時,Apache的httpd 進程會不釋放connect,而當Apache的httpd進程數超過了mysql的最大連接數時,就會出現無法連接的情況。因此,需要小心地調整 Apache和Mysql的配置,以使Apache的httpd進程數不會超出MySQL的最大連接數。在某些情況下,一些有經驗的PHP程序員寧可繼續使用mysql_connect(),而不是mysql_pconnect()。

  就作者所知,在PHP未來的roadmap中,對於工作模型這一部分,沒有根本性的變動。這是PHP的缺點,也是PHP的優勢,從本質上說,這就是PHP 的獨特之處。因此,我們很難期待PHP在近期內會對這一問題做出重大的改變。但是,在對待這個問題帶來的一系列後果時,我們必須謹慎應對舊電腦回收。
  
   數據庫訪問接口
  長期以來,PHP都缺乏一個象ADO或JDBC那樣的統一的數據庫訪問接口。PHP在訪問不同的數據庫時,使用不同的專門API。例如,使用 mysql_connect函數連接MySQL,使用ora_logon函數連接Oracle。平心而論,這種方式並沒有象我們想象的那樣麻煩。
在真實項目中,把系統從一種數據庫完全遷移到另一種數據庫的要求是比較少見的,特別是對於LAMP這樣的小型項目而言。而且,只要將訪問數據庫的代碼進行了良好的封裝,遷移的工作量也會較少。另外,使用專門API,在效率上多少會有一些優勢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章