Web 2.0時(shí)代,決戰(zhàn)效率之巔
Posted on 2009-04-18 22:17 京山游俠 閱讀(3876) 評(píng)論(9) 編輯 收藏 所屬分類(lèi): Linux和Java在這個(gè)博客上晃悠的,大部分都是Java的忠實(shí)粉絲。大家都知道Java開(kāi)發(fā)Web應(yīng)用的眾多優(yōu)點(diǎn),有大量開(kāi)源的穩(wěn)定的應(yīng)用服務(wù)器,有大量開(kāi)源的穩(wěn)定的開(kāi)發(fā)庫(kù)。以前在開(kāi)發(fā)效率上Java比不上動(dòng)態(tài)語(yǔ)言,但是現(xiàn)在出現(xiàn)了數(shù)都數(shù)不清的開(kāi)發(fā)框架,讓我們使用Java進(jìn)行Web開(kāi)發(fā)的效率突飛猛進(jìn),直逼動(dòng)態(tài)語(yǔ)言。
然而在Web 2.0時(shí)代,Java一定是最優(yōu)秀的嗎?
在Web 2.0時(shí)代,大家都有點(diǎn)在效率方面的偏執(zhí),不斷有人在挑戰(zhàn)極限,他們追求更大的并發(fā)鏈接數(shù),他們追求更加快的處理速度。操作系統(tǒng)覺(jué)得select不夠快,于是出現(xiàn)了IOCP、epoll、kqueue等系統(tǒng)調(diào)用;Web服務(wù)器的開(kāi)發(fā)者們覺(jué)得Apache httpd不夠快,于是出現(xiàn)了lighttpd、nginx。使用上Fedora 10之后,我就不斷用yum list命令企圖發(fā)現(xiàn)一些驚奇。在這樣的探索中,我發(fā)現(xiàn)了Fedora 10中安裝lighttpd和nginx是多么簡(jiǎn)單,也發(fā)現(xiàn)使用Fast CGI開(kāi)發(fā)也是那么順手。下圖中是我用yum list命令發(fā)現(xiàn)的一些軟件包

于是,在我的心中出現(xiàn)了一個(gè)疑問(wèn):httpd、lighttpd、nginx、tomcat哪一個(gè)更快呢?
我使用PHP寫(xiě)了一個(gè)小程序,以便測(cè)試上面的Web服務(wù)器哪一個(gè)更快,tomcat是Java的服務(wù)器,不能運(yùn)行PHP,只能運(yùn)行JSP。我的程序是這樣設(shè)計(jì)的:先生成1個(gè)長(zhǎng)度為1000個(gè)單詞、每個(gè)單詞長(zhǎng)度為1-10個(gè)字母的字符串,再生成1000個(gè)長(zhǎng)度為10個(gè)單詞、每個(gè)單詞長(zhǎng)度為1-10個(gè)字母的字符串,然后對(duì)這些字符串排序,輸出。為什么這么設(shè)計(jì)呢?因?yàn)槲矣X(jué)得這比較接近Web 2.0的真實(shí)使用環(huán)境,一個(gè)1000字長(zhǎng)度的文章加上1000個(gè)10字長(zhǎng)度的評(píng)論,這不是我們最常見(jiàn)的嗎?而且這個(gè)程序中會(huì)多次調(diào)用rand()函數(shù),會(huì)多次進(jìn)行字符串連接操作,會(huì)對(duì)字符串進(jìn)行排序,這些操作對(duì)語(yǔ)言的執(zhí)行速度也是一個(gè)考驗(yàn)。
先來(lái)看代碼,test.php:
執(zhí)行情況如何呢?先讓大家看三個(gè)圖。這是我分別使用httpd、lighttpd、nginx做為服務(wù)器時(shí),使用



其中,httpd是以mod_php的方式運(yùn)行PHP,而其它兩個(gè)用的是FastCGI的方式運(yùn)行PHP。從速度上來(lái)講,httpd略占優(yōu)勢(shì)。但是httpd占用的資源太多,而且很難支持大的并發(fā)連接數(shù)。當(dāng)并發(fā)數(shù)增加到1000的時(shí)候,httpd就反應(yīng)不過(guò)來(lái)了,出現(xiàn)超時(shí);而lighttpd和nginx都可以輕松支持到30000的并發(fā)。注意,如果使用超過(guò)1000的并發(fā)來(lái)進(jìn)行測(cè)試,千萬(wàn)別忘了使用ulimit命令來(lái)設(shè)置進(jìn)程可以打開(kāi)的最大文件數(shù)。
到了這里我就想,如果使用C++編寫(xiě)Fast CGI程序,運(yùn)行的速度是不是更快呢。使用Java呢?于是我用C++和Java分別實(shí)現(xiàn)了和上面功能相同的程序。代碼如下:
JSP:
這時(shí)候,我使用lighttpd作為服務(wù)器運(yùn)行PHP和Fast CGI,使用Tomcat運(yùn)行JSP,得到如下的測(cè)試結(jié)果:
先看JSP,我使用的測(cè)試命令是ab -c 100 -n 10000 http://localhost:8080/test.jsp,測(cè)試的結(jié)果只有29.30req/s,如下圖

這個(gè)時(shí)候的資源占用是多少呢,請(qǐng)看top命令的截圖:

java進(jìn)程只占用了470兆的內(nèi)存,CPU基本占滿(mǎn)了,那是因?yàn)槲覍?xiě)的這個(gè)程序?qū)PU使用比較高。
再來(lái)看看PHP執(zhí)行的情況,測(cè)試命令為ab -c 100 -n 10000 http://localhost/test.php,測(cè)試結(jié)果有48.73req/s,如下圖:

資源占用有多少呢,再來(lái)看看top命令的截圖

每個(gè)php-cgi進(jìn)程用4M內(nèi)存,lighttpd服務(wù)器占用5M內(nèi)存,而我的機(jī)器上跑了72個(gè)php-cgi進(jìn)程,如下圖:

總內(nèi)存占用293M,CPU占用也比較高。
使用C++寫(xiě)的Fast CGI,測(cè)試命令為ab -c 100 -n 10000 http://localhost/test.fcgi,結(jié)果為266.47req/s,是PHP的5倍多,是JSP的9倍。

top截圖,發(fā)現(xiàn)C++寫(xiě)的test.fcgi每個(gè)進(jìn)程只占1M內(nèi)存:

總共有64個(gè)test.fcgi進(jìn)程:

總內(nèi)存占用只有79M,CPU占用也比較低,沒(méi)有達(dá)到滿(mǎn)載。
討論:
先來(lái)說(shuō)說(shuō)服務(wù)器,對(duì)于Web 2.0的應(yīng)用來(lái)說(shuō),httpd基本上可以淘汰了,資源占用太高,支持不了上千的并發(fā)請(qǐng)求。lighttpd和nginx在FastCGI上的表現(xiàn)都很不錯(cuò),從性能上說(shuō),nginx似乎還要強(qiáng)一些,但是缺點(diǎn)就是文檔不完善,nginx需要lighttpd提供的spawn-fcgi程序來(lái)啟動(dòng)Fast CGI進(jìn)程,而且不知道對(duì)Fast CGI負(fù)載均衡的支持怎么樣,因?yàn)槲以诰W(wǎng)上找了很久都沒(méi)有找到相關(guān)的文檔。而lighttpd的文檔就完善多了,雖然是英文的,但讀起來(lái)不難。我已經(jīng)把lihgttpd的文檔都讀了一遍了,對(duì)于fastcgi.txt和performance.txt我還反復(fù)閱讀,這些文檔對(duì)于Fast CGI的配置和功能有不錯(cuò)的描述。所以,如果選擇Web服務(wù)器,我的答案是lighttpd。
再來(lái)說(shuō)說(shuō)編程語(yǔ)言。速度最快的無(wú)疑是C++,它是Java的9倍,是PHP的5倍。這里就有一點(diǎn)奇怪了,Java是編譯型語(yǔ)言,而PHP是解釋的,在我的測(cè)試中,沒(méi)有對(duì)PHP使用字節(jié)碼緩存,結(jié)果編譯型的語(yǔ)言居然跑不過(guò)解釋型的。有人也許會(huì)說(shuō),PHP開(kāi)了70幾個(gè)進(jìn)程,而Tomcat只有一個(gè)進(jìn)程,但是不管怎么樣,CPU都是滿(mǎn)載的,就算多開(kāi)幾個(gè)Tomcat進(jìn)程,也不可能把一個(gè)CPU當(dāng)兩個(gè)用。當(dāng)然,躲開(kāi)Tomcat進(jìn)程對(duì)提高并發(fā)和提高穩(wěn)定性肯定是有好處的。大家都知道,httpd可以通過(guò)mod_jk來(lái)和Tomcat服務(wù)器集成,而lighttpd沒(méi)有mod_jk,但是可以通過(guò)mod_proxy實(shí)現(xiàn)相同的功能,也就是讓lighttpd做前端服務(wù)器,把動(dòng)態(tài)請(qǐng)求分別發(fā)送到后端的Tomcat,并實(shí)現(xiàn)負(fù)載均衡和緩存。
上面的測(cè)試還有一個(gè)問(wèn)題,那就是純代碼的測(cè)試,而在實(shí)際應(yīng)用中,除了運(yùn)行動(dòng)態(tài)代碼,還有數(shù)據(jù)庫(kù)操作,數(shù)據(jù)庫(kù)操作也是非常費(fèi)時(shí)間的。我在想,應(yīng)該再寫(xiě)一個(gè)測(cè)試代碼,就把上面生成的這1001個(gè)字符串寫(xiě)入數(shù)據(jù)庫(kù)再取出,看看運(yùn)行速度如何。
C++雖然運(yùn)行速度快,但是用來(lái)寫(xiě)Web應(yīng)用還是比較難的,我讀了FastCGI develep kit的源代碼,它只實(shí)現(xiàn)了很底層的功能:讀入環(huán)境變量,標(biāo)準(zhǔn)輸入輸出。很顯然,它對(duì)多字節(jié)字符支持是沒(méi)有的,所有對(duì)于網(wǎng)絡(luò)上千奇百怪的字符編碼,我們普通程序員是沒(méi)有辦法處理的。在Web開(kāi)發(fā)領(lǐng)域,更缺少基于C++的頁(yè)面模板引擎、MVC引擎、IOC引擎、ORM引擎、SOA引擎,等等。如果有哪個(gè)高手立志于C++ Web開(kāi)發(fā),寫(xiě)一個(gè)基于C++的超級(jí)牛B的框架,說(shuō)不定可以創(chuàng)立一番大事業(yè)。
PHP應(yīng)該是個(gè)不錯(cuò)的選擇,因?yàn)閘ighttpd的作者還有另外一個(gè)作品,那就是XCache,是用來(lái)緩存PHP的字節(jié)碼的,據(jù)說(shuō)可以提高PHP的執(zhí)行速度3-5倍。這么說(shuō)來(lái),PHP甚至可以達(dá)到和C++相同的性能。PHP也有不錯(cuò)的開(kāi)發(fā)框架Zend,PHP有豐富的庫(kù)可供使用。PHP是動(dòng)態(tài)語(yǔ)言,寫(xiě)起代碼來(lái)沒(méi)有Java死板??磥?lái)是不錯(cuò)的選擇。
Java也不錯(cuò),不過(guò)目前Java領(lǐng)域的應(yīng)用服務(wù)器都很龐大,而且大部分都是基于Java語(yǔ)言開(kāi)發(fā)出來(lái)的,比起C++開(kāi)發(fā)的lihgttpd和nginx,性能自然是要差一點(diǎn)點(diǎn)。不過(guò)對(duì)于企業(yè)開(kāi)發(fā),Java依然是利器,正是因?yàn)閼?yīng)用服務(wù)器的存在,讓我們少考慮很多底層的細(xì)節(jié),讓我們很方便開(kāi)發(fā)分布式的應(yīng)用,穩(wěn)定的企業(yè)級(jí)應(yīng)用。Java的庫(kù)和框架那也是如漫天繁星、數(shù)之不盡的。
總之,具體怎么選擇,還是要看大家的,我已經(jīng)有點(diǎn)迷茫了。
然而在Web 2.0時(shí)代,Java一定是最優(yōu)秀的嗎?
在Web 2.0時(shí)代,大家都有點(diǎn)在效率方面的偏執(zhí),不斷有人在挑戰(zhàn)極限,他們追求更大的并發(fā)鏈接數(shù),他們追求更加快的處理速度。操作系統(tǒng)覺(jué)得select不夠快,于是出現(xiàn)了IOCP、epoll、kqueue等系統(tǒng)調(diào)用;Web服務(wù)器的開(kāi)發(fā)者們覺(jué)得Apache httpd不夠快,于是出現(xiàn)了lighttpd、nginx。使用上Fedora 10之后,我就不斷用yum list命令企圖發(fā)現(xiàn)一些驚奇。在這樣的探索中,我發(fā)現(xiàn)了Fedora 10中安裝lighttpd和nginx是多么簡(jiǎn)單,也發(fā)現(xiàn)使用Fast CGI開(kāi)發(fā)也是那么順手。下圖中是我用yum list命令發(fā)現(xiàn)的一些軟件包

于是,在我的心中出現(xiàn)了一個(gè)疑問(wèn):httpd、lighttpd、nginx、tomcat哪一個(gè)更快呢?
我使用PHP寫(xiě)了一個(gè)小程序,以便測(cè)試上面的Web服務(wù)器哪一個(gè)更快,tomcat是Java的服務(wù)器,不能運(yùn)行PHP,只能運(yùn)行JSP。我的程序是這樣設(shè)計(jì)的:先生成1個(gè)長(zhǎng)度為1000個(gè)單詞、每個(gè)單詞長(zhǎng)度為1-10個(gè)字母的字符串,再生成1000個(gè)長(zhǎng)度為10個(gè)單詞、每個(gè)單詞長(zhǎng)度為1-10個(gè)字母的字符串,然后對(duì)這些字符串排序,輸出。為什么這么設(shè)計(jì)呢?因?yàn)槲矣X(jué)得這比較接近Web 2.0的真實(shí)使用環(huán)境,一個(gè)1000字長(zhǎng)度的文章加上1000個(gè)10字長(zhǎng)度的評(píng)論,這不是我們最常見(jiàn)的嗎?而且這個(gè)程序中會(huì)多次調(diào)用rand()函數(shù),會(huì)多次進(jìn)行字符串連接操作,會(huì)對(duì)字符串進(jìn)行排序,這些操作對(duì)語(yǔ)言的執(zhí)行速度也是一個(gè)考驗(yàn)。
先來(lái)看代碼,test.php:
<?php
??//?定義隨機(jī)生成字符串的函數(shù)
??function?make_string($word_count,$word_length){
????$letters?=?range('a','z');
??????$temp_string?=?'';
??????for($i=0;?$i<$word_count;?$i++){
????????$temp_word?=?'';
????????for($j=0;?$j<$word_length;?$j++){
??????????????$temp_word?=?$temp_word.$letters[rand(0,25)];
????????}
????????$temp_string?=?$temp_string.'?'.$temp_word;
??????}
??????return?$temp_string;
??}
??
??//?定義隨機(jī)數(shù)種子,以便每次運(yùn)行測(cè)試,都生成相同的內(nèi)容
??srand(1);
??
??//?首先生成一個(gè)包含一千個(gè)單詞的字符串,每個(gè)單詞長(zhǎng)度為1到10個(gè)字母
??$strings[0]?=?make_string(1000,rand(1,10));
??
??//?再生成1000個(gè)包含10個(gè)單詞的字符串,每個(gè)單詞的長(zhǎng)度為1到10個(gè)字母
??for($i=0;?$i<1000;?$i?++){
??????$strings[$i+1]?=?make_string(10,rand(1,10));
??}
??
??//?排序,輸出
??sort($strings);
??for($i=0;?$i<1001;?$i++){
????echo?$strings[$i].'<br><br>';
??}
?>
??//?定義隨機(jī)生成字符串的函數(shù)
??function?make_string($word_count,$word_length){
????$letters?=?range('a','z');
??????$temp_string?=?'';
??????for($i=0;?$i<$word_count;?$i++){
????????$temp_word?=?'';
????????for($j=0;?$j<$word_length;?$j++){
??????????????$temp_word?=?$temp_word.$letters[rand(0,25)];
????????}
????????$temp_string?=?$temp_string.'?'.$temp_word;
??????}
??????return?$temp_string;
??}
??
??//?定義隨機(jī)數(shù)種子,以便每次運(yùn)行測(cè)試,都生成相同的內(nèi)容
??srand(1);
??
??//?首先生成一個(gè)包含一千個(gè)單詞的字符串,每個(gè)單詞長(zhǎng)度為1到10個(gè)字母
??$strings[0]?=?make_string(1000,rand(1,10));
??
??//?再生成1000個(gè)包含10個(gè)單詞的字符串,每個(gè)單詞的長(zhǎng)度為1到10個(gè)字母
??for($i=0;?$i<1000;?$i?++){
??????$strings[$i+1]?=?make_string(10,rand(1,10));
??}
??
??//?排序,輸出
??sort($strings);
??for($i=0;?$i<1001;?$i++){
????echo?$strings[$i].'<br><br>';
??}
?>
執(zhí)行情況如何呢?先讓大家看三個(gè)圖。這是我分別使用httpd、lighttpd、nginx做為服務(wù)器時(shí),使用
ab?-c?100?-n?10000?http://localhost/test.php
命令進(jìn)行測(cè)試所得到的結(jié)果:


其中,httpd是以mod_php的方式運(yùn)行PHP,而其它兩個(gè)用的是FastCGI的方式運(yùn)行PHP。從速度上來(lái)講,httpd略占優(yōu)勢(shì)。但是httpd占用的資源太多,而且很難支持大的并發(fā)連接數(shù)。當(dāng)并發(fā)數(shù)增加到1000的時(shí)候,httpd就反應(yīng)不過(guò)來(lái)了,出現(xiàn)超時(shí);而lighttpd和nginx都可以輕松支持到30000的并發(fā)。注意,如果使用超過(guò)1000的并發(fā)來(lái)進(jìn)行測(cè)試,千萬(wàn)別忘了使用ulimit命令來(lái)設(shè)置進(jìn)程可以打開(kāi)的最大文件數(shù)。
到了這里我就想,如果使用C++編寫(xiě)Fast CGI程序,運(yùn)行的速度是不是更快呢。使用Java呢?于是我用C++和Java分別實(shí)現(xiàn)了和上面功能相同的程序。代碼如下:
#include?<fcgi_config.h>
#include?<unistd.h>
#include?<cstdlib>
#include?<string>
#include?<vector>
#include?<algorithm>
#include?<fcgi_stdio.h>?/*?fcgi?library;?put?it?first*/
using?namespace?std;
string?make_string(int?word_count,int?word_length){
????char?letters[]?=?"abcdefghijklmnopqrstuvwxyz";
????string?temp_string;
????for?(int?i?=?0;?i?<?word_count;?i++)?{
????????string?temp_word;
????????for?(int?j?=?0;?j?<?word_length;?j++)?{
????????????int?index?=?rand()?%?26;
????????????temp_word.append(1,?letters[index]);
????????}
????????temp_string.append("?");
????????temp_string.append(temp_word);
????}
????return?temp_string;
}
int?main?()
{
????while?(FCGI_Accept()?>=?0)?{
????????char?*contentLength?=?getenv("CONTENT_LENGTH");
????????int?len;
????????printf("Content-type:?text/html\r\n"
????????????"\r\n");
????????if?(contentLength?!=?NULL)?{
????????????len?=?strtol(contentLength,?NULL,?10);
????????}?else?{
????????????len?=?0;
????????}
????????if?(len?<=?0)?{
????????????printf("No?data?from?standard?input.<p>\n");
????????}?else?{
????????????int?i,?ch;
????????????printf("Standard?input:<br>\n<pre>\n");
????????????for?(i?=?0;?i?<?len;?i++)?{
????????????????if?((ch?=?getchar())?<?0)?{
????????????????????printf(
????????????????????????????"Error:?Not?enough?bytes?received?on?standard?input<p>\n");
????????????????????break;
????????????????}
????????????????putchar(ch);
????????????}
????????????printf("\n</pre><p>\n");
????????}
????????vector<string>?strings;
????????strings.push_back(make_string(1000,?rand()?%?10?+?1));
????????for?(int?i?=?0;?i?<?1000;?i++)?{
????????????strings.push_back(make_string(10,?rand()?%?10?+?1));
????????}
????????sort(strings.begin(),?strings.end());
????????for?(vector<string>::iterator?it?=?strings.begin();?it?!=?strings.end();?it++)?{
????????????printf("%s<br><br>",?(*it).c_str());
????????????printf("\r\n");
????????}
????}?/*?while?*/
????return?0;
}
#include?<unistd.h>
#include?<cstdlib>
#include?<string>
#include?<vector>
#include?<algorithm>
#include?<fcgi_stdio.h>?/*?fcgi?library;?put?it?first*/
using?namespace?std;
string?make_string(int?word_count,int?word_length){
????char?letters[]?=?"abcdefghijklmnopqrstuvwxyz";
????string?temp_string;
????for?(int?i?=?0;?i?<?word_count;?i++)?{
????????string?temp_word;
????????for?(int?j?=?0;?j?<?word_length;?j++)?{
????????????int?index?=?rand()?%?26;
????????????temp_word.append(1,?letters[index]);
????????}
????????temp_string.append("?");
????????temp_string.append(temp_word);
????}
????return?temp_string;
}
int?main?()
{
????while?(FCGI_Accept()?>=?0)?{
????????char?*contentLength?=?getenv("CONTENT_LENGTH");
????????int?len;
????????printf("Content-type:?text/html\r\n"
????????????"\r\n");
????????if?(contentLength?!=?NULL)?{
????????????len?=?strtol(contentLength,?NULL,?10);
????????}?else?{
????????????len?=?0;
????????}
????????if?(len?<=?0)?{
????????????printf("No?data?from?standard?input.<p>\n");
????????}?else?{
????????????int?i,?ch;
????????????printf("Standard?input:<br>\n<pre>\n");
????????????for?(i?=?0;?i?<?len;?i++)?{
????????????????if?((ch?=?getchar())?<?0)?{
????????????????????printf(
????????????????????????????"Error:?Not?enough?bytes?received?on?standard?input<p>\n");
????????????????????break;
????????????????}
????????????????putchar(ch);
????????????}
????????????printf("\n</pre><p>\n");
????????}
????????vector<string>?strings;
????????strings.push_back(make_string(1000,?rand()?%?10?+?1));
????????for?(int?i?=?0;?i?<?1000;?i++)?{
????????????strings.push_back(make_string(10,?rand()?%?10?+?1));
????????}
????????sort(strings.begin(),?strings.end());
????????for?(vector<string>::iterator?it?=?strings.begin();?it?!=?strings.end();?it++)?{
????????????printf("%s<br><br>",?(*it).c_str());
????????????printf("\r\n");
????????}
????}?/*?while?*/
????return?0;
}
JSP:
<%@?page?language="java"?contentType="text/html;?charset=UTF-8"
????pageEncoding="UTF-8"%>
<%@?page?import="java.util.Random"?%>
<%@?page?import="java.util.Arrays"?%>
<!DOCTYPE?html?PUBLIC?"-//W3C//DTD?HTML?4.01?Transitional//EN"?"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta?http-equiv="Content-Type"?content="text/html;?charset=UTF-8">
<title>Insert?title?here</title>
</head>
<body>
<%!
public?String?make_string?(?int?word_count,int?word_length)?{
????Random?rand?=?new?Random();
????char?letters[]?=?"abcdefghijklmnopqrstuvwxyz".toCharArray();
????String?temp_string?=?"";
????for?(int?i?=?0;?i?<?word_count;?i++)?{
????????String?temp_word?=?"";
????????for?(int?j?=?0;?j?<?word_length;?j++)?{
????????????int?index?=?rand.nextInt(26);
????????????temp_word?+=?Character.toString(letters[index]);
????????}
????????temp_string?+=?"?";
????????temp_string?+=?temp_word;
????}
????return?temp_string;
}
%>
<%
Random?rand?=?new?Random();
String[]?strings?=?new?String[1001];
strings[0]?=?make_string(1000,rand.nextInt(10)+1);
for(int?i=1;?i<1001;?i++){
????strings[i]?=?make_string(10,rand.nextInt(10)+1);
}
Arrays.sort(strings);
for(int?i=0;?i<1001;?i++){
%>
<%=strings[i]%><br><br>
<%
}
%>
</body>
</html>
????pageEncoding="UTF-8"%>
<%@?page?import="java.util.Random"?%>
<%@?page?import="java.util.Arrays"?%>
<!DOCTYPE?html?PUBLIC?"-//W3C//DTD?HTML?4.01?Transitional//EN"?"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta?http-equiv="Content-Type"?content="text/html;?charset=UTF-8">
<title>Insert?title?here</title>
</head>
<body>
<%!
public?String?make_string?(?int?word_count,int?word_length)?{
????Random?rand?=?new?Random();
????char?letters[]?=?"abcdefghijklmnopqrstuvwxyz".toCharArray();
????String?temp_string?=?"";
????for?(int?i?=?0;?i?<?word_count;?i++)?{
????????String?temp_word?=?"";
????????for?(int?j?=?0;?j?<?word_length;?j++)?{
????????????int?index?=?rand.nextInt(26);
????????????temp_word?+=?Character.toString(letters[index]);
????????}
????????temp_string?+=?"?";
????????temp_string?+=?temp_word;
????}
????return?temp_string;
}
%>
<%
Random?rand?=?new?Random();
String[]?strings?=?new?String[1001];
strings[0]?=?make_string(1000,rand.nextInt(10)+1);
for(int?i=1;?i<1001;?i++){
????strings[i]?=?make_string(10,rand.nextInt(10)+1);
}
Arrays.sort(strings);
for(int?i=0;?i<1001;?i++){
%>
<%=strings[i]%><br><br>
<%
}
%>
</body>
</html>
這時(shí)候,我使用lighttpd作為服務(wù)器運(yùn)行PHP和Fast CGI,使用Tomcat運(yùn)行JSP,得到如下的測(cè)試結(jié)果:
先看JSP,我使用的測(cè)試命令是ab -c 100 -n 10000 http://localhost:8080/test.jsp,測(cè)試的結(jié)果只有29.30req/s,如下圖

這個(gè)時(shí)候的資源占用是多少呢,請(qǐng)看top命令的截圖:

java進(jìn)程只占用了470兆的內(nèi)存,CPU基本占滿(mǎn)了,那是因?yàn)槲覍?xiě)的這個(gè)程序?qū)PU使用比較高。
再來(lái)看看PHP執(zhí)行的情況,測(cè)試命令為ab -c 100 -n 10000 http://localhost/test.php,測(cè)試結(jié)果有48.73req/s,如下圖:

資源占用有多少呢,再來(lái)看看top命令的截圖

每個(gè)php-cgi進(jìn)程用4M內(nèi)存,lighttpd服務(wù)器占用5M內(nèi)存,而我的機(jī)器上跑了72個(gè)php-cgi進(jìn)程,如下圖:

總內(nèi)存占用293M,CPU占用也比較高。
使用C++寫(xiě)的Fast CGI,測(cè)試命令為ab -c 100 -n 10000 http://localhost/test.fcgi,結(jié)果為266.47req/s,是PHP的5倍多,是JSP的9倍。

top截圖,發(fā)現(xiàn)C++寫(xiě)的test.fcgi每個(gè)進(jìn)程只占1M內(nèi)存:

總共有64個(gè)test.fcgi進(jìn)程:

總內(nèi)存占用只有79M,CPU占用也比較低,沒(méi)有達(dá)到滿(mǎn)載。
討論:
先來(lái)說(shuō)說(shuō)服務(wù)器,對(duì)于Web 2.0的應(yīng)用來(lái)說(shuō),httpd基本上可以淘汰了,資源占用太高,支持不了上千的并發(fā)請(qǐng)求。lighttpd和nginx在FastCGI上的表現(xiàn)都很不錯(cuò),從性能上說(shuō),nginx似乎還要強(qiáng)一些,但是缺點(diǎn)就是文檔不完善,nginx需要lighttpd提供的spawn-fcgi程序來(lái)啟動(dòng)Fast CGI進(jìn)程,而且不知道對(duì)Fast CGI負(fù)載均衡的支持怎么樣,因?yàn)槲以诰W(wǎng)上找了很久都沒(méi)有找到相關(guān)的文檔。而lighttpd的文檔就完善多了,雖然是英文的,但讀起來(lái)不難。我已經(jīng)把lihgttpd的文檔都讀了一遍了,對(duì)于fastcgi.txt和performance.txt我還反復(fù)閱讀,這些文檔對(duì)于Fast CGI的配置和功能有不錯(cuò)的描述。所以,如果選擇Web服務(wù)器,我的答案是lighttpd。
再來(lái)說(shuō)說(shuō)編程語(yǔ)言。速度最快的無(wú)疑是C++,它是Java的9倍,是PHP的5倍。這里就有一點(diǎn)奇怪了,Java是編譯型語(yǔ)言,而PHP是解釋的,在我的測(cè)試中,沒(méi)有對(duì)PHP使用字節(jié)碼緩存,結(jié)果編譯型的語(yǔ)言居然跑不過(guò)解釋型的。有人也許會(huì)說(shuō),PHP開(kāi)了70幾個(gè)進(jìn)程,而Tomcat只有一個(gè)進(jìn)程,但是不管怎么樣,CPU都是滿(mǎn)載的,就算多開(kāi)幾個(gè)Tomcat進(jìn)程,也不可能把一個(gè)CPU當(dāng)兩個(gè)用。當(dāng)然,躲開(kāi)Tomcat進(jìn)程對(duì)提高并發(fā)和提高穩(wěn)定性肯定是有好處的。大家都知道,httpd可以通過(guò)mod_jk來(lái)和Tomcat服務(wù)器集成,而lighttpd沒(méi)有mod_jk,但是可以通過(guò)mod_proxy實(shí)現(xiàn)相同的功能,也就是讓lighttpd做前端服務(wù)器,把動(dòng)態(tài)請(qǐng)求分別發(fā)送到后端的Tomcat,并實(shí)現(xiàn)負(fù)載均衡和緩存。
上面的測(cè)試還有一個(gè)問(wèn)題,那就是純代碼的測(cè)試,而在實(shí)際應(yīng)用中,除了運(yùn)行動(dòng)態(tài)代碼,還有數(shù)據(jù)庫(kù)操作,數(shù)據(jù)庫(kù)操作也是非常費(fèi)時(shí)間的。我在想,應(yīng)該再寫(xiě)一個(gè)測(cè)試代碼,就把上面生成的這1001個(gè)字符串寫(xiě)入數(shù)據(jù)庫(kù)再取出,看看運(yùn)行速度如何。
C++雖然運(yùn)行速度快,但是用來(lái)寫(xiě)Web應(yīng)用還是比較難的,我讀了FastCGI develep kit的源代碼,它只實(shí)現(xiàn)了很底層的功能:讀入環(huán)境變量,標(biāo)準(zhǔn)輸入輸出。很顯然,它對(duì)多字節(jié)字符支持是沒(méi)有的,所有對(duì)于網(wǎng)絡(luò)上千奇百怪的字符編碼,我們普通程序員是沒(méi)有辦法處理的。在Web開(kāi)發(fā)領(lǐng)域,更缺少基于C++的頁(yè)面模板引擎、MVC引擎、IOC引擎、ORM引擎、SOA引擎,等等。如果有哪個(gè)高手立志于C++ Web開(kāi)發(fā),寫(xiě)一個(gè)基于C++的超級(jí)牛B的框架,說(shuō)不定可以創(chuàng)立一番大事業(yè)。
PHP應(yīng)該是個(gè)不錯(cuò)的選擇,因?yàn)閘ighttpd的作者還有另外一個(gè)作品,那就是XCache,是用來(lái)緩存PHP的字節(jié)碼的,據(jù)說(shuō)可以提高PHP的執(zhí)行速度3-5倍。這么說(shuō)來(lái),PHP甚至可以達(dá)到和C++相同的性能。PHP也有不錯(cuò)的開(kāi)發(fā)框架Zend,PHP有豐富的庫(kù)可供使用。PHP是動(dòng)態(tài)語(yǔ)言,寫(xiě)起代碼來(lái)沒(méi)有Java死板??磥?lái)是不錯(cuò)的選擇。
Java也不錯(cuò),不過(guò)目前Java領(lǐng)域的應(yīng)用服務(wù)器都很龐大,而且大部分都是基于Java語(yǔ)言開(kāi)發(fā)出來(lái)的,比起C++開(kāi)發(fā)的lihgttpd和nginx,性能自然是要差一點(diǎn)點(diǎn)。不過(guò)對(duì)于企業(yè)開(kāi)發(fā),Java依然是利器,正是因?yàn)閼?yīng)用服務(wù)器的存在,讓我們少考慮很多底層的細(xì)節(jié),讓我們很方便開(kāi)發(fā)分布式的應(yīng)用,穩(wěn)定的企業(yè)級(jí)應(yīng)用。Java的庫(kù)和框架那也是如漫天繁星、數(shù)之不盡的。
總之,具體怎么選擇,還是要看大家的,我已經(jīng)有點(diǎn)迷茫了。