在這個博客上晃悠的,大部分都是Java的忠實粉絲。大家都知道Java開發Web應用的眾多優點,有大量開源的穩定的應用服務器,有大量開源的穩定的開發庫。以前在開發效率上Java比不上動態語言,但是現在出現了數都數不清的開發框架,讓我們使用Java進行Web開發的效率突飛猛進,直逼動態語言。
然而在Web 2.0時代,Java一定是最優秀的嗎?
在Web 2.0時代,大家都有點在效率方面的偏執,不斷有人在挑戰極限,他們追求更大的并發鏈接數,他們追求更加快的處理速度。操作系統覺得select不夠快,于是出現了IOCP、epoll、kqueue等系統調用;Web服務器的開發者們覺得Apache httpd不夠快,于是出現了lighttpd、nginx。使用上Fedora 10之后,我就不斷用yum list命令企圖發現一些驚奇。在這樣的探索中,我發現了Fedora 10中安裝lighttpd和nginx是多么簡單,也發現使用Fast CGI開發也是那么順手。下圖中是我用yum list命令發現的一些軟件包

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



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

這個時候的資源占用是多少呢,請看top命令的截圖:

java進程只占用了470兆的內存,CPU基本占滿了,那是因為我寫的這個程序對CPU使用比較高。
再來看看PHP執行的情況,測試命令為ab -c 100 -n 10000 http://localhost/test.php,測試結果有48.73req/s,如下圖:

資源占用有多少呢,再來看看top命令的截圖

每個php-cgi進程用4M內存,lighttpd服務器占用5M內存,而我的機器上跑了72個php-cgi進程,如下圖:

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

top截圖,發現C++寫的test.fcgi每個進程只占1M內存:

總共有64個test.fcgi進程:

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

于是,在我的心中出現了一個疑問:httpd、lighttpd、nginx、tomcat哪一個更快呢?
我使用PHP寫了一個小程序,以便測試上面的Web服務器哪一個更快,tomcat是Java的服務器,不能運行PHP,只能運行JSP。我的程序是這樣設計的:先生成1個長度為1000個單詞、每個單詞長度為1-10個字母的字符串,再生成1000個長度為10個單詞、每個單詞長度為1-10個字母的字符串,然后對這些字符串排序,輸出。為什么這么設計呢?因為我覺得這比較接近Web 2.0的真實使用環境,一個1000字長度的文章加上1000個10字長度的評論,這不是我們最常見的嗎?而且這個程序中會多次調用rand()函數,會多次進行字符串連接操作,會對字符串進行排序,這些操作對語言的執行速度也是一個考驗。
先來看代碼,test.php:
<?php
??//?定義隨機生成字符串的函數
??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;
??}
??
??//?定義隨機數種子,以便每次運行測試,都生成相同的內容
??srand(1);
??
??//?首先生成一個包含一千個單詞的字符串,每個單詞長度為1到10個字母
??$strings[0]?=?make_string(1000,rand(1,10));
??
??//?再生成1000個包含10個單詞的字符串,每個單詞的長度為1到10個字母
??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>';
??}
?>
??//?定義隨機生成字符串的函數
??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;
??}
??
??//?定義隨機數種子,以便每次運行測試,都生成相同的內容
??srand(1);
??
??//?首先生成一個包含一千個單詞的字符串,每個單詞長度為1到10個字母
??$strings[0]?=?make_string(1000,rand(1,10));
??
??//?再生成1000個包含10個單詞的字符串,每個單詞的長度為1到10個字母
??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>';
??}
?>
執行情況如何呢?先讓大家看三個圖。這是我分別使用httpd、lighttpd、nginx做為服務器時,使用
ab?-c?100?-n?10000?http://localhost/test.php
命令進行測試所得到的結果:


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

這個時候的資源占用是多少呢,請看top命令的截圖:

java進程只占用了470兆的內存,CPU基本占滿了,那是因為我寫的這個程序對CPU使用比較高。
再來看看PHP執行的情況,測試命令為ab -c 100 -n 10000 http://localhost/test.php,測試結果有48.73req/s,如下圖:

資源占用有多少呢,再來看看top命令的截圖

每個php-cgi進程用4M內存,lighttpd服務器占用5M內存,而我的機器上跑了72個php-cgi進程,如下圖:

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

top截圖,發現C++寫的test.fcgi每個進程只占1M內存:

總共有64個test.fcgi進程:

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