- 論壇徽章:
- 0
|
工作模型
PHP的工作模型非常特殊。從某種程度上說(shuō),PHP和ASP、ASP.NET、JSP/Servlet等流行的Web技術(shù),有著本質(zhì)上的區(qū)別。
以Java為例,Java在Web應(yīng)用領(lǐng)域,有兩種技術(shù):Java Servlet和JSP(Java Server Page)。Java
Servlet是一種特殊類(lèi)型的Java程序,它通過(guò)實(shí)現(xiàn)相關(guān)接口,處理Web服務(wù)器發(fā)送過(guò)來(lái)的請(qǐng)求,完成相應(yīng)的工作。JSP在形式上是一種類(lèi)似于PHP
的腳本,但是事實(shí)上,它最后也被編譯成Servlet。
也就是說(shuō),在Java解決方案中,JSP和Servlet是作為獨(dú)立的Java應(yīng)用
程序執(zhí)行的,它們?cè)诔跏蓟缶婉v留內(nèi)存,通過(guò)特定的接口和Web服務(wù)器通信,完成相應(yīng)工作。除非被顯式地重啟,否則它們不會(huì)終止。因此,可以在JSP和
Servlet中使用各種緩存技術(shù),例如數(shù)據(jù)庫(kù)連接池。
ASP.NET的機(jī)制與此類(lèi)似。至于ASP,雖然也是一種解釋型語(yǔ)言,但是仍然提供了Application對(duì)象來(lái)存放應(yīng)用程序級(jí)的全局變量,它依托于ASP解釋器在IIS中駐留的進(jìn)程,在整個(gè)應(yīng)用程序的生命期有效。
PHP卻完全不是這樣。作為一種純解釋型語(yǔ)言,PHP腳本在每次被解釋時(shí)進(jìn)行初始化,在解釋完畢后終止運(yùn)行。這種運(yùn)行是互相獨(dú)立的,每一次請(qǐng)求都會(huì)創(chuàng)建一
個(gè)單獨(dú)的進(jìn)程或線程,來(lái)解釋相應(yīng)的頁(yè)面文件。頁(yè)面創(chuàng)建的變量和其他對(duì)象,都只在當(dāng)前的頁(yè)面內(nèi)部可見(jiàn),無(wú)法跨越頁(yè)面訪問(wèn)。
在終止運(yùn)行后,頁(yè)面中申請(qǐng)的、沒(méi)有被代碼顯式釋放的外部資源,包括內(nèi)存、數(shù)據(jù)庫(kù)連接、文件句柄、Socket連接等,都會(huì)被強(qiáng)行釋放。
也就是說(shuō),PHP無(wú)法在語(yǔ)言級(jí)別直接訪問(wèn)跨越頁(yè)面的變量,也無(wú)法創(chuàng)建駐留內(nèi)存的對(duì)象。見(jiàn)下例:
";
TestStaticVar();
?>
在這個(gè)例子中,定義了一個(gè)名為StaticVarTester的類(lèi),它僅有一個(gè)公共的靜態(tài)成員$StaticVar,并被初始化為0。然后,在
TestStaticVar()函數(shù)中,對(duì)StaticVarTester :: $StaticVar進(jìn)行累加操作,并將它打印輸出。
熟悉Java或C++的開(kāi)發(fā)者對(duì)這個(gè)例子應(yīng)該并不陌生。$StaticVar作為StaticVarTester類(lèi)的一個(gè)靜態(tài)成員,只在類(lèi)被裝載時(shí)進(jìn)行初
始化,無(wú)論StaticVarTester類(lèi)被實(shí)例化多少次,$StaticVar都只存在一個(gè)實(shí)例,而且不會(huì)被多次初始化。因此,當(dāng)?shù)谝淮握{(diào)用
TestStaticVar()函數(shù)時(shí),$StaticVar進(jìn)行了累加操作,值為1,并被保存。第二次調(diào)用TestStaticVar()函
數(shù),$StaticVar的值為2。
打印出來(lái)的結(jié)果和我們預(yù)料的一樣:
StaticVarTester :: StaticVar = 1
StaticVarTester :: StaticVar = 2
但是,當(dāng)瀏覽器刷新頁(yè)面,再次執(zhí)行這段代碼時(shí),不同的情況出現(xiàn)了。在Java或C++里面,$StaticVar的值會(huì)被保存并一直累加下去,我們將會(huì)看到如下的結(jié)果:
StaticVarTester :: StaticVar = 3
StaticVarTester :: StaticVar = 4
…
但是在PHP中,由于上文敘及的機(jī)制,當(dāng)前頁(yè)面每次都解釋時(shí),都會(huì)執(zhí)行一次程序初始化和終止的過(guò)程。也就是說(shuō),每次訪問(wèn)時(shí),StaticVarTester都會(huì)被重新裝載,而下列這行語(yǔ)句
public static $StaticVar = 0;
也會(huì)被重復(fù)執(zhí)行。當(dāng)頁(yè)面執(zhí)行完成后,所有的內(nèi)存空間都會(huì)被回收,$StaticVar這個(gè)變量(連同整個(gè)StaticVarTester類(lèi))也就不復(fù)存
在。因此,無(wú)論刷新頁(yè)面多少次,$StaticVar變量都會(huì)回到起點(diǎn):先被初始化為0,然后在TestStaticVar()函數(shù)調(diào)用中被累加。所以,
我們看到的結(jié)果永遠(yuǎn)是這個(gè):
StaticVarTester :: StaticVar = 1
StaticVarTester :: StaticVar = 2
PHP這種獨(dú)特的工作模型的優(yōu)勢(shì)在于,基本上解決了令人頭疼的資源泄漏問(wèn)題。Web應(yīng)用的特點(diǎn)是大量的、短時(shí)間的并發(fā)處理,對(duì)各種資源的申請(qǐng)和釋放工作非常頻繁,很容易導(dǎo)致泄漏。
同
時(shí),大量的動(dòng)態(tài)html腳本的存在,使得追蹤和調(diào)試的工作都非常困難。PHP的運(yùn)行機(jī)制,以一種非常簡(jiǎn)單的方式避免了這個(gè)問(wèn)題,同時(shí)也避免了將程序員帶入
到繁瑣的緩沖池和同步等問(wèn)題中去。在實(shí)踐中,基于PHP的應(yīng)用往往比基于Java或.NET的應(yīng)用更加穩(wěn)定,不會(huì)出現(xiàn)由于某個(gè)頁(yè)面的BUG而導(dǎo)致整個(gè)站點(diǎn)
崩潰的問(wèn)題。
(相比之下,Java或.NET應(yīng)用可能因?yàn)榫彌_池崩潰或其他的非法操作,而導(dǎo)致整個(gè)站點(diǎn)崩潰。)后果是,即使PHP程序員水
平不高,也無(wú)法寫(xiě)出使整個(gè)應(yīng)用崩潰的代碼。PHP腳本可以一次調(diào)用極多的資源,從而導(dǎo)致頁(yè)面執(zhí)行極為緩慢,但是執(zhí)行完畢后所有的資源都會(huì)被釋放,應(yīng)用仍然
不會(huì)崩潰。
甚至即使執(zhí)行了一個(gè)死循環(huán),也會(huì)在30秒(默認(rèn)時(shí)間)后因?yàn)槌瑫r(shí)而中止。從理論上來(lái)說(shuō),基于PHP的應(yīng)用是不太可能崩潰的,因?yàn)樗倪\(yùn)行機(jī)制決定它不存在常規(guī)的崩潰這個(gè)問(wèn)題。在實(shí)踐中,很多開(kāi)發(fā)者也認(rèn)為PHP是最穩(wěn)定的Web應(yīng)用。
但是,這種機(jī)制的缺點(diǎn)也非常明顯。最直接的后果是,PHP在語(yǔ)言級(jí)別無(wú)法實(shí)現(xiàn)跨頁(yè)面的緩沖機(jī)制。這種緩沖機(jī)制缺失造成的影響,可以分成兩個(gè)方面:
一是對(duì)象的緩沖。如我們所知,很多設(shè)計(jì)模式都依賴(lài)于對(duì)象的緩沖機(jī)制,對(duì)于需要頻繁應(yīng)付大量并發(fā)的服務(wù)端軟件更是如此。因此,對(duì)象緩沖的缺失,理論上會(huì)極大
地降低速度。但是,由于PHP本身的定位和工作機(jī)制等原因,它在實(shí)際工作中的速度非?。就作者自己的經(jīng)驗(yàn)來(lái)看,在小型的Web應(yīng)用中,PHP至少不比
Java慢。
在大型的應(yīng)用中,為了榨干每一分硬件資源,即使PHP本身足夠快,一個(gè)優(yōu)秀的對(duì)象緩沖機(jī)制仍然是必要的。在這種情況下,可以使
用第三方的內(nèi)存緩沖軟件,如Memcached。由于Memcached本身的優(yōu)異特性(高性能,支持跨服務(wù)器的分布式存儲(chǔ),和PHP的無(wú)縫集成等),在
大型的PHP應(yīng)用中,Memcached幾乎已經(jīng)成為不可或缺的基礎(chǔ)設(shè)施了。比起使用PHP語(yǔ)言自己實(shí)現(xiàn)對(duì)象緩沖來(lái),這種第三方解決方案似乎更好一些。
二是數(shù)據(jù)庫(kù)連接的緩沖。對(duì)MySQL,PHP提供了一種內(nèi)置的數(shù)據(jù)庫(kù)緩沖機(jī)制,使用起來(lái)非常簡(jiǎn)單,程序員需要做的只是用mysql_pconnect()代替mysql_connect()來(lái)打開(kāi)數(shù)據(jù)庫(kù)而已。
PHP會(huì)自動(dòng)回收被廢棄的數(shù)據(jù)庫(kù)連接,以供重復(fù)使用。具有諷刺意味的是,在實(shí)際應(yīng)用中,這種持久性數(shù)據(jù)庫(kù)連接往往會(huì)導(dǎo)致數(shù)據(jù)庫(kù)連接的偽泄漏現(xiàn)象:在某個(gè)時(shí)間,并發(fā)的數(shù)據(jù)庫(kù)連接過(guò)多,超過(guò)了MySQL的最大連接數(shù),從而導(dǎo)致新的進(jìn)程無(wú)法連接數(shù)據(jù)庫(kù)。
但
是過(guò)一段時(shí)間,當(dāng)并發(fā)數(shù)減少時(shí),PHP會(huì)釋放掉一些連接,網(wǎng)站又會(huì)恢復(fù)正常。出現(xiàn)這種現(xiàn)象的原因是,當(dāng)使用pconnect時(shí),Apache的httpd
進(jìn)程會(huì)不釋放connect,而當(dāng)Apache的httpd進(jìn)程數(shù)超過(guò)了mysql的最大連接數(shù)時(shí),就會(huì)出現(xiàn)無(wú)法連接的情況。因此,需要小心地調(diào)整
Apache和Mysql的配置,以使Apache的httpd進(jìn)程數(shù)不會(huì)超出MySQL的最大連接數(shù)。在某些情況下,一些有經(jīng)驗(yàn)的PHP程序員寧可繼續(xù)
使用mysql_connect(),而不是mysql_pconnect()。
就作者所知,在PHP未來(lái)的roadmap中,對(duì)于工作模型這一部分,沒(méi)有根本性的變動(dòng)。這是PHP的缺點(diǎn),也是PHP的優(yōu)勢(shì),從本質(zhì)上說(shuō),這就是PHP
的獨(dú)特之處。因此,我們很難期待PHP在近期內(nèi)會(huì)對(duì)這一問(wèn)題做出重大的改變。但是,在對(duì)待這個(gè)問(wèn)題帶來(lái)的一系列后果時(shí),我們必須謹(jǐn)慎應(yīng)對(duì)。
數(shù)據(jù)庫(kù)訪問(wèn)接口
長(zhǎng)期以來(lái),PHP都缺乏一個(gè)象ADO或JDBC那樣的統(tǒng)一的數(shù)據(jù)庫(kù)訪問(wèn)接口。PHP在訪問(wèn)不同的數(shù)據(jù)庫(kù)時(shí),使用不同的專(zhuān)門(mén)API。例如,使用
mysql_connect函數(shù)連接MySQL,使用ora_logon函數(shù)連接Oracle。平心而論,這種方式并沒(méi)有象我們想象的那樣麻煩。
在真實(shí)項(xiàng)目中,把系統(tǒng)從一種數(shù)據(jù)庫(kù)完全遷移到另一種數(shù)據(jù)庫(kù)的要求是比較少見(jiàn)的,特別是對(duì)于LAMP這樣的小型項(xiàng)目而言。而且,只要將訪問(wèn)數(shù)據(jù)庫(kù)的代碼進(jìn)行了良好的封裝,遷移的工作量也會(huì)較少。另外,使用專(zhuān)門(mén)API,在效率上多少會(huì)有一些優(yōu)勢(shì)。
雖然如此,PHP的開(kāi)發(fā)人員仍然在努力構(gòu)建PHP的統(tǒng)一的數(shù)據(jù)庫(kù)訪問(wèn)接口。從PHP 5.1開(kāi)始,PHP的發(fā)行包內(nèi)置了PDO(PHP Data Objects,PHP數(shù)據(jù)對(duì)象)。PDO具有如下特性:
統(tǒng)一的數(shù)據(jù)庫(kù)訪問(wèn)接口。PDO為訪問(wèn)不同的數(shù)據(jù)庫(kù)提供了統(tǒng)一的接口,并且能夠通過(guò)切換數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序,方便地支持各種流行的數(shù)據(jù)庫(kù)。
面向?qū)ο。PDO完全基于PHP 5的對(duì)象機(jī)制,因此區(qū)別于基于過(guò)程的專(zhuān)用API。
高性能。PDO的底層用C編寫(xiě),比起用純PHP開(kāi)發(fā)的其他類(lèi)似解決方案,有更高的性能。
一個(gè)典型的PDO應(yīng)用如下例:
$pdo = new PDO("mysql:host=localhost;dbname=justtest", " mysql_user ", " mysql_password");
$query = "SELECT id, username FROM userinfo ORDER BY ID";
foreach ($pdo->query($query) as $row) {
echo $row['id']." | ".$row['username']."";
}
PME模型
在大規(guī)模的程序設(shè)計(jì)中,組件(component)已經(jīng)成為一種非常流行的技術(shù)。常見(jiàn)的組件技術(shù)都基于PME模型,即屬性(Property)、方法(Method)和事件(Event)。
基于PME的組件技術(shù)可以方便地實(shí)現(xiàn)IoC(Inversion of Control,控制反轉(zhuǎn)),是從IDE的plugin到應(yīng)用服務(wù)器的“熱發(fā)布”等許多技術(shù)的基礎(chǔ)。
PHP從版本5開(kāi)始,大大完善了對(duì)OO的支持,以前不能被應(yīng)用的許多pattern現(xiàn)在都可以在PHP5中實(shí)現(xiàn)。因此,是否能夠?qū)崿F(xiàn)基于PHP的組件技術(shù),也就成了一個(gè)值得討論的問(wèn)題。
下面對(duì)PHP對(duì)于PME模型的支持,逐一進(jìn)行討論:
屬性(Property)
PHP并不支持類(lèi)似Delphi或者C#的property語(yǔ)法,但這并不是問(wèn)題。Java也不支持property語(yǔ)法,但是通過(guò)getXXX()和setXXX()的命名約定,同樣可以支持屬性。
PHP也可以通過(guò)這一方式來(lái)支持屬性。但是,PHP提供了另一種也許更好的方法,那就是__set()和__get()方法。
在PHP中,每一個(gè)class都會(huì)自動(dòng)繼承__set()和__get()方法。它們的定義如下:
void __set ( string name, mixed value )
mixed __get ( string name )
這兩個(gè)方法將在下列情況下被觸發(fā):當(dāng)程序訪問(wèn)一個(gè)當(dāng)前類(lèi)沒(méi)有顯式定義的屬性時(shí)。在這個(gè)時(shí)候,被訪問(wèn)的屬性名稱(chēng)作為參數(shù)被傳入相應(yīng)的方法。任何類(lèi)都可以重載__set()和__get()方法,以實(shí)現(xiàn)自己的功能。
如下例:
class PropertyTester {
public function __get($PropName) {
echo "Getting Property $PropNamen";
}
public function __set($PropName, $Value) {
echo "Setting Property $PropName to '$Value'n";
}
}
$Prop = new PropertyTester();
$Prop->Name;
$Prop->Name = "some string";
類(lèi)
PropertyTester重載了__set()和__get()方法,為了測(cè)試,僅僅將參數(shù)打印輸出,沒(méi)有做更多的工作。測(cè)試代碼創(chuàng)建了
PropertyTester類(lèi)的實(shí)例,并試圖讀寫(xiě)它并不存在的一個(gè)屬性Name。此時(shí),__set()和__get()相繼被調(diào)用,并打印出相關(guān)參數(shù)。
它的輸出結(jié)果如下:
Getting Property Name
Setting Property Name to 'some string'
基于這種機(jī)制,我們可以將屬性的值放在一個(gè)private的List中,在讀寫(xiě)屬性時(shí),通過(guò)重載__set()和__get()方法,讀寫(xiě)List中的屬性值。
但
是,__set()和__get()方法的有趣之處遠(yuǎn)不止及。通過(guò)這兩個(gè)方法,可以實(shí)現(xiàn)動(dòng)態(tài)屬性,也就是不在程序中顯式定義,而是在運(yùn)行時(shí)動(dòng)態(tài)生成的屬
性。只要想想這種技術(shù)在OR
Mapping中的作用就能夠明白它的重要性了。配合__call()方法(用于實(shí)現(xiàn)動(dòng)態(tài)方法,在下一節(jié)中詳述),它能夠取代丑陋的代碼生成器(code
generator)的大部分功能。
方法(Method)
PHP對(duì)方法的支持比較簡(jiǎn)單,沒(méi)有太多可以討論的。值得一提的是,PHP從版本5開(kāi)始支持類(lèi)的靜態(tài)方法(static method),這使得程序員再也不用無(wú)謂地增加許多全局函數(shù)了。
事件(Event)
事件也許是PHP遇到的最復(fù)雜的問(wèn)題。PHP并沒(méi)有在語(yǔ)法層面提供對(duì)事件的支持,我們只能考慮通過(guò)別的途徑來(lái)實(shí)現(xiàn)。因此,我們需要先對(duì)事件的概念和其他語(yǔ)言對(duì)事件的實(shí)現(xiàn)方式進(jìn)行討論。
事件模型可以簡(jiǎn)述如下:充當(dāng)事件觸發(fā)者的代碼本身并不處理事件,而僅僅是在事件發(fā)生時(shí),把程序控制權(quán)轉(zhuǎn)交給事件的處理者,在事件處理完成后,再收回控制權(quán)。事件觸發(fā)者本身并不知道事件將會(huì)被如何處理,在大多數(shù)情況下,事件觸發(fā)者的代碼要先于事件處理者的代碼被完成。
在
傳統(tǒng)的面向過(guò)程的語(yǔ)言(例如C或者PASCAL)中,事件可以通過(guò)函數(shù)指針來(lái)實(shí)現(xiàn)。具體來(lái)說(shuō),事件觸發(fā)者定義一個(gè)函數(shù)指針,這個(gè)函數(shù)指針可以在以后被指向
某個(gè)處理事件的函數(shù)。在事件發(fā)生時(shí),調(diào)用該函數(shù)指針指向的處理函數(shù),并將事件的上下文作為參數(shù)傳入。處理完成后,控制權(quán)再回到事件觸發(fā)者。
在面向?qū)ο蟮恼Z(yǔ)言中,方法指針(指向某個(gè)類(lèi)的方法的指針)取代了函數(shù)指針。以Delphi為例,事件處理的例子如下:
type
TNotifyEvent = procedure(Sender: TObject) of object;
TMainForm = class(TForm)
procedure ButtonClick(Sender: TObject);
…
End;
Var
MainForm: TMainForm;
OnClick: TNotifyEvent;
…
可
以看出,TNotifyEvent被定義為所謂的過(guò)程類(lèi)型(Procedural
Type),事實(shí)上就是一個(gè)方法指針。TMainForm的ButtonClick方法是一個(gè)事件處理者,符合TNotifyEvent的簽名。
OnClick是一個(gè)事件觸發(fā)者。在實(shí)際使用時(shí),通過(guò)如下代碼:
OnClick := MainForm.ButtonClick;
將MainForm.ButtonClick方法綁定到了OnClick事件。當(dāng)OnClick事件觸發(fā)時(shí),MainForm.ButtonClick方法將被調(diào)用,并且將Sender(觸發(fā)事件的組件對(duì)象)作為參數(shù)傳入。
回到PHP,由于PHP不支持指針,因此無(wú)法使用函數(shù)指針這一技術(shù)。但是,PHP支持所謂的“函數(shù)變量”,可以把函數(shù)賦予某個(gè)變量,其作用類(lèi)似于函數(shù)指針。如下例:
function EventHandler($Sender) {
echo "Calling EventHandler(), arv = $Sendern";
}
$Func = 'EventHandler';
$Func('Sender Name');
由
于PHP是一種動(dòng)態(tài)語(yǔ)言,變量可以為任何類(lèi)型,所以無(wú)須先定義函數(shù)指針的類(lèi)型作為事件的簽名。直接定義了一個(gè)函數(shù)EventHandler作為事件處理
者,然后將它賦予變量$Func(注意直接使用了字符串形式的函數(shù)名),最后觸發(fā)該事件,并將一個(gè)字符串“Sender
Name”傳給它作為參數(shù)。輸出的結(jié)果是:
Calling EventHandler(), arv = Sender Name
同樣地,PHP也提供了類(lèi)似方法指針的機(jī)制。如下例:
Class EventHandler {
public function DoEvent($Sender) {
echo "Calling EventHandler.DoEvent(), arg = $Sendern";
}
}
$EventHanler = new EventHandler();
$HandlerObject = $EventHanler;
$Method = 'DoEvent';
$HandlerObject->$Method('Sender Name');
由于PHP中沒(méi)有能夠直接引用對(duì)象方法的變量,因此需要使用兩個(gè)變量來(lái)間接實(shí)現(xiàn):$HandlerObject指向?qū)ο螅?Method指向?qū)ο蠓椒āMㄟ^(guò)$HandlerObject->$Method方式的調(diào)用,可以動(dòng)態(tài)地指向任何對(duì)象方法。
為了使代碼更加優(yōu)雅和更適合復(fù)用,可以定義一個(gè)專(zhuān)門(mén)的類(lèi)NotifyEvent,并使用一段新的調(diào)用代碼:
final class NotifyEvent {
private $HandlerObject;
private $Method;
public function __construct($HandlerObject, $Method) {
$this->HandlerObject = $HandlerObject;
$this->Method = $Method;
}
public function Call($Sender) {
$Method = $this->Method;
$this->HandlerObject->$Method($Sender);
}
}
$EventHanler = new EventHandler();
$NotifyEvent = new NotifyEvent($EventHanler, 'DoEvent');
$NotifyEvent->Call('Sender Name');
NotifyEvent類(lèi)定義了兩個(gè)私有變量$HandlerObject和$Method,分別指向事件處理者對(duì)象和處理方法。在構(gòu)造函數(shù)中對(duì)這兩個(gè)變量賦值,再通過(guò)Call方法來(lái)調(diào)用。
熟
悉C#的讀者可以發(fā)現(xiàn),NotifyEvent類(lèi)與C#中的Delegate十分類(lèi)似。Delegate超過(guò)NotifyEvent的地方在于支持多播
(Multicast),也就是一個(gè)事件可以綁定多個(gè)事件處理者。只要事件觸發(fā)者自己維護(hù)一個(gè)NotifyEvent對(duì)象數(shù)組,支持多播也不是一件難事。
至此,PHP對(duì)事件的支持已經(jīng)得到了比較圓滿的解決。但是,人的求知欲是無(wú)窮無(wú)盡的。還有沒(méi)有可能通過(guò)其他的方式來(lái)實(shí)現(xiàn)事件呢?
除了方法指針,接口(interface)也可以用于實(shí)現(xiàn)事件。在Java中,這種技術(shù)被廣泛應(yīng)用。其核心思想是,將事件處理者的處理函數(shù)定義抽象為一個(gè)接口(相當(dāng)于函數(shù)指針的簽名),事件觸發(fā)者針對(duì)這個(gè)接口編程,事件處理者則實(shí)現(xiàn)這個(gè)接口。
這種方式的好處在于,不需要語(yǔ)言支持函數(shù)指針或方法指針,讓代碼顯得更加清晰和優(yōu)雅,缺陷在于,實(shí)現(xiàn)同一種功能,要使用更多的代碼。如下例:
interface IEventHandler {
public function DoEvent($Sender, $Arg);
}
class EventHanlerAdapter implements IEventHandler {
public function DoEvent($Sender, $Arg) {
echo "Calling EventHanlerAdapter.DoEvent(), Sender = $Sender, arg = $Argn";
}
}
class EventRaiser {
private $EventHanlerVar;
public function __construct($EventHanlerAdapter) {
$this->EventHanlerVar = $EventHanlerAdapter;
}
public function RaiseEvent() {
if ($this->EventHanlerVar != null) {
$this->EventHanlerVar->DoEvent($this, 'some string');
}
}
public function __tostring() {
return 'Object EventRaier';
}
}
$EventHanlerAdapter = new EventHanlerAdapter();
$EventRaiser = new EventRaiser($EventHanlerAdapter);
$EventRaiser->RaiseEvent();
首
先定義了一個(gè)接口IEventHandler,它包含了方法的簽名。EventHanlerAdapter類(lèi)作為事件處理者,實(shí)現(xiàn)了這個(gè)接口,并提供了相
應(yīng)的處理方法。EventRaiser類(lèi)作為事件觸發(fā)者,針對(duì)$EventHanlerVar變量(它應(yīng)該是IEventHandler接口類(lèi)型,但是在
PHP中不用顯式定義)編碼。
在實(shí)際應(yīng)用中,將EventHanlerAdapter的實(shí)例作為參數(shù)賦予傳給EventRaiser類(lèi)的構(gòu)造函數(shù),當(dāng)事件發(fā)生時(shí),相應(yīng)的處理方法將被調(diào)用。輸出結(jié)果如下:
Calling EventHanlerAdapter.DoEvent(), Sender = Object EventRaier, arg = some string
最后,讓我們回到現(xiàn)實(shí)世界中來(lái)。雖然我們用PHP完整地實(shí)現(xiàn)了PME模型,但是這到底有什么用呢?畢竟,我們不會(huì)用PHP去編寫(xiě)IDE,也不會(huì)用它編寫(xiě)應(yīng)用服務(wù)器;卮鹗,基于PME模型的組件技術(shù)可以實(shí)現(xiàn)更加方便和更大規(guī)模的代碼復(fù)用。
在基于PHP的應(yīng)用系統(tǒng)中,雖然插件已經(jīng)被廣泛使用,但是通過(guò)組件技術(shù)可以實(shí)現(xiàn)功能更強(qiáng)大、更加規(guī)范和更容易維護(hù)的插件。此外,組件技術(shù)在實(shí)現(xiàn)一些大的Framework(例如,針對(duì)Web UI的Framework)時(shí),也是不可或缺的。
Session有效期問(wèn)題
Session處理是所有的Web應(yīng)用都必須面對(duì)的問(wèn)題。PHP中對(duì)session有效期的處理,和其他的解決方案有著很大的不同,這是和PHP的工作機(jī)制相關(guān)的。
在傳統(tǒng)的client/server應(yīng)用中,對(duì)于session失效的情況,可以交給網(wǎng)絡(luò)協(xié)議自己來(lái)處理。無(wú)論是client端主動(dòng)關(guān)閉連接,還是因?yàn)榫W(wǎng)
絡(luò)異常而導(dǎo)致的連接中斷,server端都能夠得到通知,觸發(fā)連接中斷的事件。只要編程響應(yīng)這一事件,執(zhí)行指定的操作即可。但對(duì)于web應(yīng)用來(lái)說(shuō),情況卻
完全不一樣。HTTP協(xié)議本身是無(wú)狀態(tài)的,也就是說(shuō),每當(dāng)client/server完成一次請(qǐng)求/響應(yīng)的過(guò)程后,連接就會(huì)被斷開(kāi)。在斷開(kāi)連接以
后,server并不知道client是否繼續(xù)“在線”,還會(huì)繼續(xù)發(fā)送下一次請(qǐng)求。換句話說(shuō),無(wú)論client端的用戶已經(jīng)關(guān)閉了瀏覽器窗口,還是用戶僅
僅在閱讀當(dāng)前網(wǎng)頁(yè)并準(zhǔn)備在下一秒鐘繼續(xù)瀏覽,或者用戶因?yàn)閃indows崩潰/停電/硬盤(pán)壞掉/網(wǎng)線被拔/地球爆炸而徹底無(wú)法再發(fā)送下一個(gè)請(qǐng)
求,server都一無(wú)所知。(在HTTP
1.1中,瀏覽器可以通過(guò)keep-alive參數(shù),來(lái)通知server不要在響應(yīng)請(qǐng)求后主動(dòng)斷開(kāi)連接,從而實(shí)現(xiàn)物理上的長(zhǎng)連接。但是,這只是為了提高網(wǎng)
絡(luò)傳輸?shù)男阅芏扇〉拇胧琀TTP在邏輯上仍然是無(wú)狀態(tài)的。)因此,只能通過(guò)某種模擬的方式來(lái)判斷當(dāng)前session是否有效。如果某個(gè)session
在超過(guò)一段時(shí)間后沒(méi)有對(duì)server端發(fā)出請(qǐng)求,server都會(huì)判斷用戶已經(jīng)“離線”,當(dāng)前session失效,并觸發(fā)連接中斷的事件。要做到這一
點(diǎn),server需要運(yùn)行一個(gè)后臺(tái)線程,定時(shí)掃描所有的session信息,判斷session是否已經(jīng)超時(shí)。
PHP處理session的原理也不例外,但是在具體的實(shí)現(xiàn)方式上,卻與眾不同。這是因?yàn),由于PHP的工作機(jī)制,它并沒(méi)有一個(gè)后臺(tái)線程,來(lái)定時(shí)地掃描
session信息并判斷其是否失效。它的解決之道是,當(dāng)一個(gè)有效請(qǐng)求發(fā)生時(shí),PHP會(huì)根據(jù)某個(gè)概率,來(lái)決定是否調(diào)用一個(gè)GC(Garbage
Collector)。GC的工作,就是掃描所有的session信息,用當(dāng)前時(shí)間減去session的最后修改時(shí)間(modified
date),同配置參數(shù)(configuration
option)session.gc_maxlifetime的值進(jìn)行比較,如果生存時(shí)間已經(jīng)超過(guò)gc_maxlifetime,就把該session刪
除。這是很容易理解的,因?yàn)槿绻看握?qǐng)求都要調(diào)用GC代碼,那么PHP的效率就會(huì)低得令人吃不消了。這個(gè)概率取決于配置參數(shù)
session.gc_probability/session.gc_divisor的值(可以通過(guò)php.ini或者ini_set()函數(shù)來(lái)修
改)。默認(rèn)情況下,session.gc_probability =
1,session.gc_divisor=100,也就是說(shuō)有1%的可能性會(huì)啟動(dòng)GC。
這三個(gè)參數(shù),session.gc_maxlifetime/session.gc_probability
/session.gc_divisor都可以通過(guò)php.ini或者ini_set()函數(shù)來(lái)修改。但要記得,如果使用ini_set()函數(shù)的話,必
須在每一個(gè)頁(yè)面的開(kāi)始處都調(diào)用ini_set()。
這又導(dǎo)致了另外一個(gè)問(wèn)題,gc_maxlifetime只能保證session生存的最短時(shí)間,并不能夠保存在超過(guò)這一時(shí)間之后
session信息立即會(huì)得到刪除。因?yàn)镚C是按概率啟動(dòng)的,可能在某一個(gè)長(zhǎng)時(shí)間內(nèi)都沒(méi)有被啟動(dòng),那么大量的session在超過(guò)
gc_maxlifetime以后仍然會(huì)有效。當(dāng)然,發(fā)生這種情況的概率很小,但是如果你的應(yīng)用對(duì)session的失效期要求很精確的話,這會(huì)導(dǎo)致很?chē)?yán)重
的問(wèn)題。解決這個(gè)問(wèn)題的一個(gè)方法是,把session.gc_probability/session.gc_divisor的機(jī)率提高,如果提到
100%,就會(huì)徹底解決這個(gè)問(wèn)題,但顯然會(huì)對(duì)性能造成嚴(yán)重的影響。另一個(gè)方法是放棄PHP的GC,自己在代碼中判斷當(dāng)前session的生存時(shí)間,如果超
出了 gc_maxlifetime,就清空當(dāng)前session。
PHP中的session有效期默認(rèn)是1440秒(24分鐘),也就是說(shuō),客戶端超過(guò)24分鐘沒(méi)有刷新,當(dāng)前session就會(huì)失效。要修改這個(gè)默認(rèn)值,正確的解決辦法是修改配置參數(shù)session.gc_maxlifetime。
我曾經(jīng)在網(wǎng)上搜索過(guò)這個(gè)問(wèn)題的解決方式,找到的結(jié)果千奇百怪。有的說(shuō)要設(shè)置“session_life_time”,據(jù)我知所,PHP中沒(méi)有這個(gè)參數(shù)。有
的說(shuō)要調(diào)用session_set_cookie_params,或者設(shè)置session.cookie_lifetime,這僅僅用于設(shè)置
client端cookie的生存時(shí)間,換言之,只當(dāng)client端cookie的生存時(shí)間小于server端的session生存期時(shí),修改這個(gè)值才有
效,并且最長(zhǎng)不能超過(guò)server端的session生存期,原因很簡(jiǎn)單,當(dāng)server端的session已經(jīng)失效時(shí),client端cookie的生
存時(shí)間再長(zhǎng)也是沒(méi)有意義的。還有的說(shuō)要調(diào)用
session_cache_expire,這個(gè)參數(shù)用于通知瀏覽器和proxy,當(dāng)前頁(yè)面的內(nèi)容應(yīng)該被緩存多長(zhǎng)時(shí)間,和session的生存期并沒(méi)有直
接關(guān)系。
聽(tīng)起來(lái),這種解決方案很完美。但是,當(dāng)你在實(shí)際中嘗試修改session.gc_maxlifetime的值的時(shí)候,你很可能會(huì)發(fā)現(xiàn),這個(gè)參數(shù)基本不起作用,session有效期仍然保持24分鐘的默認(rèn)值。甚至可能出現(xiàn),在開(kāi)發(fā)環(huán)境下工作正常,在服務(wù)器上卻無(wú)效!
為了徹底解決這個(gè)問(wèn)題,需要對(duì)PHP的工作細(xì)節(jié)進(jìn)行進(jìn)一步的分析。
在默認(rèn)情況下,PHP
中的session信息會(huì)以文本文件的形式,被保存在系統(tǒng)的臨時(shí)文件目錄中。這個(gè)路徑由配置參數(shù)session.save_path指定。在Linux
下,這一路徑通常為\tmp,在
Windows下通常為C:\Windows\Temp。當(dāng)服務(wù)器上有多個(gè)PHP應(yīng)用時(shí),它們會(huì)把自己的session文件都保存在同一個(gè)目錄中(因?yàn)樗?br />
們使用同一個(gè)session.save_path參數(shù))。同樣地,這些PHP應(yīng)用也會(huì)按一定機(jī)率啟動(dòng)GC,掃描所有的session文件。
問(wèn)題在于,GC在工作時(shí),并不會(huì)區(qū)分不同站點(diǎn)的session。舉例言之,站點(diǎn)A的gc_maxlifetime設(shè)置為2小時(shí),站點(diǎn)B的
gc_maxlifetime設(shè)置為默認(rèn)的24分鐘。當(dāng)站點(diǎn)B的GC啟動(dòng)時(shí),它會(huì)掃描公用的臨時(shí)文件目錄,把所有超過(guò)24分鐘的session文件全部刪
除掉,而不管它們來(lái)自于站點(diǎn)A或B。這樣,站點(diǎn)A的gc_maxlifetime設(shè)置就形同虛設(shè)了。
找到問(wèn)題所在,解決起來(lái)就很簡(jiǎn)單了。在頁(yè)面的開(kāi)始處調(diào)用session_save_path()函數(shù),它能夠修改
session.save_path參數(shù),把保存session的目錄指向一個(gè)專(zhuān)用的目錄,例如\tmp\myapp\。這
樣,gc_maxlifetime參數(shù)就工作正常了。
使用公用的session.save_path還會(huì)導(dǎo)致安全性問(wèn)題,因?yàn)檫@意味著,同一臺(tái)服務(wù)器上的其它PHP程序也可以讀取你的站點(diǎn)的
session文件,這可能被用于黑客攻擊。另一個(gè)問(wèn)題是效率:在一個(gè)繁忙的站點(diǎn)中,可能存在成千上萬(wàn)個(gè)session文件,而把許多不同網(wǎng)站的
session文件都放在同一個(gè)目錄下,無(wú)論是對(duì)單個(gè)文件的讀寫(xiě),還是遍歷所有文件進(jìn)行GC,都無(wú)疑會(huì)導(dǎo)致性能的降低。因此,如果你的PHP應(yīng)用和別的
PHP應(yīng)用運(yùn)行在同一臺(tái)服務(wù)器上的話,強(qiáng)烈建議你使用自己的session.save_path。
嚴(yán)格地來(lái)說(shuō),這算是PHP的一個(gè)bug。當(dāng)PHP在進(jìn)行GC時(shí),它應(yīng)該區(qū)別來(lái)自不同站點(diǎn)的session文件,并應(yīng)用不同的gc_maxlifetime值。目前,最新的PHP 5.2.X仍然存在這個(gè)問(wèn)題。
上文說(shuō)到,在一個(gè)繁忙的站點(diǎn)中,可能存在成千上萬(wàn)個(gè)session文件,即使區(qū)分了不同站點(diǎn)的session.save_path目錄,單個(gè)站點(diǎn)的session文件數(shù)目仍然可能導(dǎo)致效率問(wèn)題。為了解決這一問(wèn)題,可行的幾種方法有:
1. 如果PHP運(yùn)行在Linux系統(tǒng)下,使用ReiserFS文件系統(tǒng)取代默認(rèn)的ext2/ext3文件系統(tǒng)。ReiserFS對(duì)于大量小文件的存取性能,比ext2/ext3有極大的提高。
2. 將session.save_path指向一個(gè)內(nèi)存路徑。這意味著,session文件的讀寫(xiě)只在內(nèi)存中進(jìn)行,而不執(zhí)行磁盤(pán)操作。
3. session.save_path接受一個(gè)額外的N參數(shù),用于指定目錄的級(jí)數(shù)。例如,“5;/tmp”
將導(dǎo)致創(chuàng)建類(lèi)似這樣的session文件:/tmp/4/b/1/e/3
/sess_4b1e384ad74619bd212e236e52a5a174If。具體的說(shuō)明,請(qǐng)參見(jiàn):
http://cn.php.net/manual/en/session.configuration.php#ini.session.save-path
4. 終極的解決方案,是放棄PHP的session處理機(jī)制,自己編碼接管所有的session處理操作,通過(guò)
session_set_save_handler()函數(shù)來(lái)實(shí)現(xiàn)。通過(guò)自己接管session處理,可以將所有的session保存在專(zhuān)門(mén)的數(shù)據(jù)庫(kù)(往
往使用內(nèi)存表)中,從而徹底解決session文件帶來(lái)的問(wèn)題,并且可以方便地實(shí)現(xiàn)session的共享和復(fù)制。這也是大型的PHP應(yīng)用一般會(huì)使用的方
式。關(guān)于session_set_save_handler()函數(shù)的使用,網(wǎng)上和相關(guān)圖書(shū)都有詳細(xì)的說(shuō)明,這里不再贅述。值得一提的是,即使在這種方式
下,啟動(dòng)GC的概率仍然取決于session.gc_probability/session.gc_divisor。
Drupal的性能問(wèn)題
Drupal是一個(gè)基于PHP的開(kāi)源CMS系統(tǒng),也是我認(rèn)為技術(shù)上實(shí)現(xiàn)得最好的一個(gè)PHP應(yīng)用。Drupal的架構(gòu)非常優(yōu)秀,通過(guò)微內(nèi)核+plugin的
方式,實(shí)現(xiàn)了極佳的擴(kuò)展性,從而使Drupal遠(yuǎn)遠(yuǎn)超出一般的CMS這一范疇。從這個(gè)意義上來(lái)說(shuō),把Drupal稱(chēng)為Web
OS似乎更加合適一些。關(guān)于Drupal,有太多的話可以說(shuō),也許我會(huì)在以后的時(shí)間里寫(xiě)一篇文章對(duì)它進(jìn)行專(zhuān)門(mén)的討論。但是在本文中,我想討論的,是
Drupal社區(qū)中的每一個(gè)人都會(huì)面對(duì),但不是每一個(gè)人都對(duì)其有清晰認(rèn)識(shí)的問(wèn)題,即Drupal的性能問(wèn)題。
因?yàn)榭蛻粜枨,我曾?jīng)對(duì)Drupal做過(guò)比較全面的測(cè)試。當(dāng)時(shí)的環(huán)境是雙服務(wù)器(DB server+Web
Server),硬件配置都是單CPU+4G。數(shù)據(jù)庫(kù)里面有幾千條Node記錄。用JMeter對(duì)各種情況下(開(kāi)/關(guān)各種cache模塊,logged
user/anonymous user)不同頁(yè)面的讀取和寫(xiě)入操作都進(jìn)行過(guò)測(cè)試。
測(cè)試的結(jié)果可能和很多人印象中不一樣。兩個(gè)主要的結(jié)果如下:
1. Logged user和anonymous user的性能差距非常大。同一個(gè)頁(yè)面,logged
user的RPS(Requests per second)一般不超過(guò)20,而啟用了cache的anonymous
user的RPS在100多,當(dāng)使用了file-based cache以后,甚至能超過(guò)300。
2.
數(shù)據(jù)庫(kù)壓力相對(duì)較小。由于Drupal把大量可配置的內(nèi)容都放在數(shù)據(jù)庫(kù)中,因此往往容易產(chǎn)生這樣一種印象,即Drupal對(duì)數(shù)據(jù)庫(kù)要求應(yīng)該是很高的。但事
實(shí)上,無(wú)論是cache還是非cache模式,DB server的壓力都相當(dāng)小(CPU在10%以下),而Web
Server的CPU在80%以上。跟蹤所有的db query的執(zhí)行時(shí)間后,也證明了這一點(diǎn)(全部db
query的執(zhí)行時(shí)間只占頁(yè)面生成時(shí)間的一小部分)。
經(jīng)過(guò)反復(fù)的測(cè)試和思考,我得出了一些結(jié)論。很明顯,Drupal在大量logged
user并發(fā)情況下的瓶頸,在于執(zhí)行Drupal代碼的CPU時(shí)間,而不是在于數(shù)據(jù)庫(kù)或者其他地方。之所以出現(xiàn)這樣的情況,和PHP本身的執(zhí)行機(jī)制和
Drupal的實(shí)現(xiàn)方式有關(guān)。Drupal在生成一個(gè)非cached的頁(yè)面時(shí),不管這個(gè)頁(yè)面多么簡(jiǎn)單,都要執(zhí)行一個(gè)完整的bootstrap過(guò)程,即使只
啟用了最少的模塊,這個(gè)過(guò)程也要調(diào)用幾十個(gè)PHP文件,執(zhí)行成千上萬(wàn)行PHP代碼。而PHP的機(jī)制又決定了沒(méi)有任何PHP代碼或者對(duì)象能夠駐留內(nèi)存,每次
響應(yīng)請(qǐng)求都必須執(zhí)行完整的初始化工作。而anonymous user之所以快,是因?yàn)镈rupal在執(zhí)行cached
page的時(shí)候,不會(huì)執(zhí)行完整的bootstrap過(guò)程,它先檢查是否cached page,是的話就讀取緩存,然后結(jié)束工作。這樣當(dāng)然就快了。
以這個(gè)結(jié)論為前提,可以解釋一些事情:
1. 為什么Drupal的性能在各種環(huán)境下相差并不多。無(wú)論是雙服務(wù)器,單服務(wù)器,甚至內(nèi)存非常小的虛擬機(jī),logged
user的RPS值往往總是在10~20之間。數(shù)據(jù)庫(kù)里面有幾百條,或者幾十萬(wàn)條記錄,影響也不大。因?yàn)槠款i并不在于DB或者內(nèi)存,而是在于執(zhí)行代碼的過(guò)
程。
2. 為什么使用APC/XCache這樣的代碼優(yōu)化程序,能夠得到極大的性能提升。在我自己的虛擬機(jī)環(huán)境上,RPS從3~4提升到了12。因?yàn)樗嵘氖荘HP代碼的執(zhí)行時(shí)間。
從這個(gè)結(jié)論出發(fā),列出一些對(duì)優(yōu)化Drupal的logged user性能有明顯作用和沒(méi)有明顯作用的措施:
I. 沒(méi)有明顯作用的:
1. 加內(nèi)存。在并發(fā)數(shù)只有10+的時(shí)候,即使每個(gè)請(qǐng)求占20M內(nèi)存,也只有200M+內(nèi)存而已。
2. DB server和Web server分開(kāi),或者增強(qiáng)DB server的配置。一臺(tái)中等性能的mysql服務(wù)器,應(yīng)付200~300的并發(fā)是很輕松的事情,在并發(fā)數(shù)只有10+的時(shí)候,db server實(shí)際上是很空閑的。
3.
基礎(chǔ)軟件的優(yōu)化,例如從Windows轉(zhuǎn)移到Linux,從apache轉(zhuǎn)移到Lighttpd,從MySQL遷移到其他數(shù)據(jù)庫(kù),除了從Windows轉(zhuǎn)
移到
Linux會(huì)有比較明顯的提升以外(因?yàn)镻HP在Linux上的效率比在Windows上要好),其它的措施可能會(huì)快一些,但不會(huì)有大幅度的提高,因?yàn)槠?br />
頸不在那里。
II. 有明顯作用的:
1. 使用APC/XCache這樣的代碼優(yōu)化程序,速度會(huì)有幾倍的提升。估計(jì)大家都已經(jīng)這樣做過(guò)了。
2. 增加web server的CPU數(shù)量。雙核的肯定比單核的快,4個(gè)CPU肯定比2個(gè)CPU快得多。
3. 使用多web server+單db server的配置,把代碼執(zhí)行的壓力分散到不同的web server上。上文說(shuō)到,單臺(tái)db server可以輕松應(yīng)付200+的并發(fā),這意味著理論上可以支持10臺(tái)以上的web server。
4. 使用Quercus這樣的引擎,把PHP代碼編譯成Java,再在Java VM中運(yùn)行,理論上會(huì)有很大的提高。原因是,第一,Java的運(yùn)行效率比PHP高,第二,Java代碼是可以cache的,不需要每次都重新加載。這里有個(gè)測(cè)試結(jié)果:
http://www.workhabit.org/resin-backed-php-drives-4x-performance-improvements-drupal
。Drupal在Quercus下有4倍的性能提高,但是這個(gè)數(shù)字跟Drupal在打開(kāi)APC/eAccelerator下的提升差不多,所以可能沒(méi)有太大的實(shí)用價(jià)值。
另外一種思路是代碼本身的優(yōu)化。
使用cache API基本上是沒(méi)有意義的,因?yàn)閷?duì)于logger user,Drupal不會(huì)調(diào)用cache
API。Drupal.org上有人提出,即使是logged
user,有很多頁(yè)面也是不用定制化的,這意味著可以cache它們。但是Drupal沒(méi)有提供這樣一種機(jī)制。只要是logged
user,Drupal就會(huì)執(zhí)行完整的bootstrap過(guò)程,即使只打印出一個(gè)hello world,因此實(shí)際上你沒(méi)有辦法在logged
user狀態(tài)下cache單個(gè)頁(yè)面。
到目前最新版本的Drupal(Drupal 6.4)為止,對(duì)于logger
user,Drupal只提供了一種cache功能,就是可以將部分block設(shè)置為可cache的。在block占用大量服務(wù)器時(shí)間的情況
下,block cache能夠有效地提高效率。但是,由于block
cache對(duì)于bootstrap過(guò)程并無(wú)影響,因此當(dāng)瓶頸在于bootstrap本身時(shí),Block cache是無(wú)能為力的。
在Drupal.org的社區(qū),關(guān)于logger user的cache問(wèn)題,一直處于熱烈的討論之中;镜慕Y(jié)論是,由于Drupal的架構(gòu)就是這樣,目前沒(méi)有很好的解決方案,只能期待Drupal在以后的版本中進(jìn)行改進(jìn)了。
我研究了一下Drupal的bootstrap過(guò)程,發(fā)現(xiàn)也許這樣是可行的:實(shí)現(xiàn)hook_boot函數(shù),這是bootstrap中執(zhí)行最靠前的一個(gè)函
數(shù),它被調(diào)用時(shí),bootstrap的大部分過(guò)程還沒(méi)有執(zhí)行。在hook_boot中,檢查當(dāng)前頁(yè)面是否需要cache,如果是,直接讀
cache生成頁(yè)面,然后調(diào)用exit()強(qiáng)行結(jié)束。這在理論上是可行的,但太過(guò)hack了一點(diǎn)。
Drupal的情況是這樣,那么其他的PHP框架,尤其是半官方的Zend Framework,性能如何呢?通過(guò)搜索,我在網(wǎng)上找到了一份PHP framework comparison benchmarks,網(wǎng)址見(jiàn):
http://www.avnetlabs.com/php/php-framework-comparison-benchmarks
。
根據(jù)這份報(bào)告的數(shù)據(jù),Zend
Framework的性能只有原生PHP的10%,如果沒(méi)有用APC,連3%都不到。當(dāng)然,這份報(bào)告的數(shù)據(jù)不一定詳盡,Zend
Framework在不同環(huán)境下的表現(xiàn)應(yīng)該也會(huì)有出入。但是,Zend Framework的性能大幅度落后于Baseline
PHP,應(yīng)該是確鑿無(wú)疑的。
為什么PHP主流框架的性能都存在著這樣的問(wèn)題呢?其實(shí)這也不難理解;仡橮HP沉思錄系列第一篇中對(duì)于PHP工作模型的討論,由于PHP沒(méi)有駐留內(nèi)存的
進(jìn)程,所以每一個(gè)request發(fā)生時(shí),都必須初始化所有的對(duì)象,這導(dǎo)致大量的時(shí)間被耗費(fèi)在進(jìn)程代碼的執(zhí)行過(guò)程中。當(dāng)PHP程序僅僅是簡(jiǎn)單的腳本時(shí),這無(wú)
關(guān)緊要,但是在結(jié)構(gòu)復(fù)雜的架構(gòu)中,由于每次處理request都要重復(fù)調(diào)用成千上萬(wàn)行代碼,這一問(wèn)題就變得非常突出了。而且,除非PHP以后的版本對(duì)這一
機(jī)制進(jìn)行改進(jìn),否則這個(gè)問(wèn)題無(wú)法得到徹底的解決。
那么,這是否意味著,PHP只能適用于小型的網(wǎng)站,而無(wú)法在高并發(fā)量的大型網(wǎng)站施展拳腳呢?當(dāng)然不是這樣。事實(shí)上,在Yahoo和其他許多知名的巨型網(wǎng)站
上,都大量地使用了PHP。原因在于,PHP僅僅被用作一個(gè)內(nèi)容生成器,生成的內(nèi)容會(huì)被轉(zhuǎn)化為靜態(tài)文本,絕大多數(shù)用戶瀏覽的都是被cache的靜態(tài)文本。
這就和PHP程序的性能毫無(wú)關(guān)系了。但是,當(dāng)用戶并不是僅僅進(jìn)行瀏覽,而是需要頻繁地和網(wǎng)站進(jìn)行互動(dòng)時(shí),PHP的性能不但無(wú)法比擬C和Java,甚至無(wú)法
與同為腳本語(yǔ)言的Python和Ruby相比。也就是說(shuō),PHP更適合于新聞門(mén)戶這樣的內(nèi)容發(fā)布站點(diǎn),而不是web 2.0應(yīng)用的首選。
在本系列文章告一段落的時(shí)候,我們看到的是PHP的局限性。熱愛(ài)PHP的人們可能會(huì)對(duì)此覺(jué)得沮喪。但是,這并無(wú)損于PHP作為一門(mén)優(yōu)秀語(yǔ)言的聲譽(yù)。尺有所短,寸有所長(zhǎng),對(duì)于我們熟悉和喜愛(ài)的工具,我們更應(yīng)該了解它們的局限,這也有利于我們更有效地使用它們。
[zz from] http://www.chinahtml.com/programming/2/2007/php-119069789516247.shtml
[zz from] http://koda.javaeye.com/blog/319605
本文來(lái)自ChinaUnix博客,如果查看原文請(qǐng)點(diǎn):http://blog.chinaunix.net/u2/87830/showart_2043017.html |
|