京山游俠

          專注技術,拒絕扯淡
          posts - 50, comments - 868, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          Web 2.0時代,決戰效率之巔

          Posted on 2009-04-18 22:17 京山游俠 閱讀(3871) 評論(9)  編輯  收藏 所屬分類: Linux和Java
          在這個博客上晃悠的,大部分都是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命令發現的一些軟件包
          yum_list.png


          于是,在我的心中出現了一個疑問: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>';
          ??}
          ?>

          執行情況如何呢?先讓大家看三個圖。這是我分別使用httpd、lighttpd、nginx做為服務器時,使用
          ab?-c?100?-n?10000?http://localhost/test.php
          命令進行測試所得到的結果:
          015.png

          006.png

          009.png

          其中,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;
          }

          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>

          這時候,我使用lighttpd作為服務器運行PHP和Fast CGI,使用Tomcat運行JSP,得到如下的測試結果:

          先看JSP,我使用的測試命令是ab -c 100 -n 10000 http://localhost:8080/test.jsp,測試的結果只有29.30req/s,如下圖
          java_result.png
          這個時候的資源占用是多少呢,請看top命令的截圖:
          java_top.png
          java進程只占用了470兆的內存,CPU基本占滿了,那是因為我寫的這個程序對CPU使用比較高。

          再來看看PHP執行的情況,測試命令為ab -c 100 -n 10000 http://localhost/test.php,測試結果有48.73req/s,如下圖:
          php_result.png

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

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

          php_procs.png

          總內存占用293M,CPU占用也比較高。

          使用C++寫的Fast CGI,測試命令為ab -c 100 -n 10000 http://localhost/test.fcgi,結果為266.47req/s,是PHP的5倍多,是JSP的9倍。
          fcgi_result.png

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

          總共有64個test.fcgi進程:

          fcgi_procs.png

          總內存占用只有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的庫和框架那也是如漫天繁星、數之不盡的。

          總之,具體怎么選擇,還是要看大家的,我已經有點迷茫了。

          評論

          # re: Web 2.0,決戰效率之巔  回復  更多評論   

          2009-04-18 22:56 by SearchFull
          怎么沒有寫完呢?

          # re: Web 2.0,決戰效率之巔  回復  更多評論   

          2009-04-19 13:24 by fireflyc
          這樣不公平吧?
          java有NIO的。你把tomcat的NIO開啟看看是效果如何的。我想你會驚訝的發現原來“IOCP、epoll、kqueue”可以不依賴操作系統的——Java的NIO。

          # re: Web 2.0,決戰效率之巔  回復  更多評論   

          2009-04-20 09:15 by r
          你java寫的不好,用StringBuilder

          # re: Web 2.0,決戰效率之巔[未登錄]  回復  更多評論   

          2009-04-20 10:05 by 海邊沫沫
          用StringBuffer就不公平了,因為PHP中也是用的字符串連接,大量創建消毀字符串對象是肯定的。只有c++最占便宜

          # re: Web 2.0時代,決戰效率之巔  回復  更多評論   

          2009-04-20 12:29 by 海邊沫沫
          最新測試結果:
          Tomcat開啟NIO后:28.33req/s
          JSP代碼改用StringBuffer后:193.05req/s

          根據樓上兩位的回復,我先是對開啟了Tomcat的NIO,結果發現性能基本沒有改變,理論上講NIO應該不可能一點性能提升都沒有的,有可能是Tomcat默認就已經開啟了NIO,不需要顯式配置。

          然后我優化了一下代碼,該用了StringBuffer,結果不得了,性能提升了5-6倍。和C++的性能以經很接近了。由此可見,在Java中創建對象開銷還是很高的。另外,我把Random rand = new Random()換了個地方,這樣每運行一次JSP只需要創建一個Random對象。

          請看截圖,上面一張是開啟NIO后的結果,下面一張是優化代碼后的結果。






          下面是優化后的代碼:

          <%@ 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>
          <%!
          Random rand = new Random();
          public String make_string ( int word_count,int word_length) {

          char letters[] = "abcdefghijklmnopqrstuvwxyz".toCharArray();
          StringBuffer temp_string = new StringBuffer();
          for (int i = 0; i < word_count; i++) {
          StringBuffer temp_word = new StringBuffer();
          for (int j = 0; j < word_length; j++) {
          int index = rand.nextInt(26);
          temp_word.append(letters[index]);
          }
          temp_string.append(" ");
          temp_string.append(temp_word);
          }
          return temp_string.toString();
          }
          %>
          <%
          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>

          # re: Web 2.0時代,決戰效率之巔  回復  更多評論   

          2009-04-20 18:10 by 海邊沫沫
          下面再增加一個比較項目,那就是對靜態文件的響應速度。我把前面用PHP生成的頁面保存下來,作為test.html,文件大小是80多k,然后使用
          ab -c 1000 -n 10000 http://localhost/test.html
          ab -c 1000 -n 10000 http://localhost:8080/test.html
          進行測試。

          結果:
          Apache httpd:3276 req/s
          lighttpd:5633 req/s
          nginx: 6080 req/s
          Tomcat:1015 req/s

          結果表明,Tomcat只有nginx的六分之一,而且Tomcat的Failed Request字段的值太高,其余三個服務器該字段的值都是0,說明Tomcat面對大量并發連接時還不夠穩定。所以,使用lighttpd或nginx做Tomcat的反向代理,并進行緩存,應該可以獲得不錯的性能。

          下面上圖:






          # re: Web 2.0時代,決戰效率之巔  回復  更多評論   

          2009-05-30 14:40 by 虎嘯龍吟
          樓主強啊,什么都會啊

          # re: Web 2.0時代,決戰效率之巔  回復  更多評論   

          2009-06-24 17:58 by ycmhn
          這種簡單并且cpu密集的任務肯定是java比php占優勢
          因為畢竟是半編譯語言
          但是復雜的任務php最大的好處是擴展多~而且很容易用c語言寫擴展,
          如果把你這個寫成c擴展的話~~~嘿嘿 那就難說了~
          java。。。我看了幾年都很少看到用C寫擴展的,因為java對自己速度比較自信,且編寫擴展比較繁雜
          上面說看到nio會很驚訝的同學,,,可能是對別的語言了解比較少~其實說的就是一個跨平臺問題~
          以前我也為java宣稱的跨平臺激動過~可是呢~名不符實~不如很多腳本好

          # re: Web 2.0時代,決戰效率之巔  回復  更多評論   

          2009-06-30 11:29 by aDuan
          web 2.0
          nginx + php + mysql.
          主站蜘蛛池模板: 昌平区| 靖江市| 芦溪县| 通榆县| 汉中市| 博野县| 伊川县| 昭觉县| 龙门县| 高州市| 孟州市| 云安县| 依安县| 涡阳县| 襄汾县| 波密县| 察雅县| 马龙县| 河北省| 明光市| 永州市| 阳江市| 江达县| 莆田市| 寿光市| 屏东市| 大渡口区| 富阳市| 辛集市| 蒙阴县| 若尔盖县| 遂昌县| 岳池县| 新竹市| 高唐县| 南漳县| 桃源县| 宝兴县| 喀喇| 中西区| 伊川县|