莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          簡單的web server性能測試

          Posted on 2007-08-29 18:10 dennis 閱讀(4732) 評論(6)  編輯  收藏 所屬分類: javaerlangmy open-source
              最近一直在讀《java并發編程實踐》,書是絕對的好書,翻譯不能說差,也談不上好,特別是第一部分的前面幾章,有的地方翻譯的南轅北轍了,還是要對照著英文版來看。我關注并發編程是從學習Erlang開始的,在多核來臨的時代,有人說并發將是下一個10年的關鍵技術。java5之前的多線程編程很復雜,況且我也沒有從事此類應用的開發,了解不多,而從jdk5引入了讓人流口水的concurrent包之后,java的并發編程開始變的有趣起來。
             書中第6章以編寫一個web server為例子,引出了幾種不同版本的寫法:單線程、多線程以及采用jdk5提供的線程池實現。我就用apache自帶的ab工具測試了下各個版本的性能,在redhat9 p4 2g內存的機器上進行了測試。
          ab -50000 -1000 http://localhost/index.html >benchmark

          單線程模式,順序性地處理每一個請求,50000并發很快就沒有響應了,不參與比較了。再來看看我們自己寫的多線程方式處理每個請求:
          package net.rubyeye.concurrency.chapter6;

          import java.io.BufferedReader;
          import java.io.DataOutputStream;
          import java.io.File;
          import java.io.FileInputStream;
          import java.io.IOException;
          import java.io.InputStreamReader;
          import java.net.InetAddress;
          import java.net.ServerSocket;
          import java.net.Socket;

          public class ThreadPerTaskWebServer {
              
          public static void main(String[] args) throws IOException {
                  ServerSocket server 
          = new ServerSocket(80);
                  
          while (true) {
                      
          final Socket connection = server.accept();
                      Runnable task 
          = new Runnable() {
                          
          public void run() {
                              
          try {
                                  handleRequest(connection);
                              } 
          catch (IOException e) {
                                  e.printStackTrace();
                              }

                          }
                      };
                      
          new Thread(task).start();
                  }
              }

              
          public static void handleRequest(Socket socket) throws IOException {
                  
          try {
                      InetAddress client 
          = socket.getInetAddress();
                      
          // and print it to gui
                      s(client.getHostName() + " connected to server.\n");
                      
          // Read the http request from the client from the socket interface
                      
          // into a buffer.
                      BufferedReader input = new BufferedReader(new InputStreamReader(
                              socket.getInputStream()));
                      
          // Prepare a outputstream from us to the client,
                      
          // this will be used sending back our response
                      
          // (header + requested file) to the client.
                      DataOutputStream output = new DataOutputStream(socket
                              .getOutputStream());

                      
          // as the name suggest this method handles the http request, see
                      
          // further down.
                      
          // abstraction rules
                      http_handler(input, output);
                      socket.close();
                  } 
          catch (Exception e) { // catch any errors, and print them
                      s("\nError:" + e.getMessage());
                  }

              } 
          // go back in loop, wait for next request

              
          // our implementation of the hypertext transfer protocol
              
          // its very basic and stripped down
              private static void http_handler(BufferedReader input,
                      DataOutputStream output) {
                  
          int method = 0// 1 get, 2 head, 0 not supported
                  String http = new String(); // a bunch of strings to hold
                  String path = new String(); // the various things, what http v, what
                  
          // path,
                  String file = new String(); // what file
                  String user_agent = new String(); // what user_agent
                  try {
                      
          // This is the two types of request we can handle
                      
          // GET /index.html HTTP/1.0
                      
          // HEAD /index.html HTTP/1.0
                      String tmp = input.readLine(); // read from the stream
                      String tmp2 = new String(tmp);
                      tmp.toUpperCase(); 
          // convert it to uppercase
                      if (tmp.startsWith("GET")) { // compare it is it GET
                          method = 1;
                      } 
          // if we set it to method 1
                      if (tmp.startsWith("HEAD")) { // same here is it HEAD
                          method = 2;
                      } 
          // set method to 2

                      
          if (method == 0) { // not supported
                          try {
                              output.writeBytes(construct_http_header(
          5010));
                              output.close();
                              
          return;
                          } 
          catch (Exception e3) { // if some error happened catch it
                              s("error:" + e3.getMessage());
                          } 
          // and display error
                      }
                      
          // }

                      
          // tmp contains "GET /index.html HTTP/1.0 ."
                      
          // find first space
                      
          // find next space
                      
          // copy whats between minus slash, then you get "index.html"
                      
          // it's a bit of dirty code, but bear with me
                      int start = 0;
                      
          int end = 0;
                      
          for (int a = 0; a < tmp2.length(); a++) {
                          
          if (tmp2.charAt(a) == ' ' && start != 0) {
                              end 
          = a;
                              
          break;
                          }
                          
          if (tmp2.charAt(a) == ' ' && start == 0) {
                              start 
          = a;
                          }
                      }
                      path 
          = tmp2.substring(start + 2, end); // fill in the path
                  } catch (Exception e) {
                      s(
          "errorr" + e.getMessage());
                  } 
          // catch any exception

                  
          // path do now have the filename to what to the file it wants to open
                  s("\nClient requested:" + new File(path).getAbsolutePath() + "\n");
                  FileInputStream requestedfile 
          = null;

                  
          try {
                      
          // NOTE that there are several security consideration when passing
                      
          // the untrusted string "path" to FileInputStream.
                      
          // You can access all files the current user has read access to!!!
                      
          // current user is the user running the javaprogram.
                      
          // you can do this by passing "../" in the url or specify absoulute
                      
          // path
                      
          // or change drive (win)

                      
          // try to open the file,
                      requestedfile = new FileInputStream(path);
                  } 
          catch (Exception e) {
                      
          try {
                          
          // if you could not open the file send a 404
                          output.writeBytes(construct_http_header(4040));
                          
          // close the stream
                          output.close();
                      } 
          catch (Exception e2) {
                      }
                      ;
                      s(
          "error" + e.getMessage());
                  } 
          // print error to gui

                  
          // happy day scenario
                  try {
                      
          int type_is = 0;
                      
          // find out what the filename ends with,
                      
          // so you can construct a the right content type
                      if (path.endsWith(".zip"|| path.endsWith(".exe")
                              
          || path.endsWith(".tar")) {
                          type_is 
          = 3;
                      }
                      
          if (path.endsWith(".jpg"|| path.endsWith(".jpeg")) {
                          type_is 
          = 1;
                      }
                      
          if (path.endsWith(".gif")) {
                          type_is 
          = 2;
                          
          // write out the header, 200 ->everything is ok we are all
                          
          // happy.
                      }
                      output.writeBytes(construct_http_header(
          2005));

                      
          // if it was a HEAD request, we don't print any BODY
                      if (method == 1) { // 1 is GET 2 is head and skips the body
                          while (true) {
                              
          // read the file from filestream, and print out through the
                              
          // client-outputstream on a byte per byte base.
                              int b = requestedfile.read();
                              
          if (b == -1) {
                                  
          break// end of file
                              }
                              output.write(b);
                          }

                      }
                      
          // clean up the files, close open handles
                      output.close();
                      requestedfile.close();
                  }

                  
          catch (Exception e) {
                  }

              }

              
          private static void s(String s) {
              
          //    System.out.println(s);
              }

              
          // this method makes the HTTP header for the response
              
          // the headers job is to tell the browser the result of the request
              
          // among if it was successful or not.
              private static String construct_http_header(int return_code, int file_type) {
                  String s 
          = "HTTP/1.0 ";
                  
          // you probably have seen these if you have been surfing the web a while
                  switch (return_code) {
                  
          case 200:
                      s 
          = s + "200 OK";
                      
          break;
                  
          case 400:
                      s 
          = s + "400 Bad Request";
                      
          break;
                  
          case 403:
                      s 
          = s + "403 Forbidden";
                      
          break;
                  
          case 404:
                      s 
          = s + "404 Not Found";
                      
          break;
                  
          case 500:
                      s 
          = s + "500 Internal Server Error";
                      
          break;
                  
          case 501:
                      s 
          = s + "501 Not Implemented";
                      
          break;
                  }

                  s 
          = s + "\r\n"// other header fields,
                  s = s + "Connection: close\r\n"// we can't handle persistent
                  
          // connections
                  s = s + "Server: SimpleHTTPtutorial v0\r\n"// server name

                  
          // Construct the right Content-Type for the header.
                  
          // This is so the browser knows what to do with the
                  
          // file, you may know the browser dosen't look on the file
                  
          // extension, it is the servers job to let the browser know
                  
          // what kind of file is being transmitted. You may have experienced
                  
          // if the server is miss configured it may result in
                  
          // pictures displayed as text!
                  switch (file_type) {
                  
          // plenty of types for you to fill in
                  case 0:
                      
          break;
                  
          case 1:
                      s 
          = s + "Content-Type: image/jpeg\r\n";
                      
          break;
                  
          case 2:
                      s 
          = s + "Content-Type: image/gif\r\n";
                  
          case 3:
                      s 
          = s + "Content-Type: application/x-zip-compressed\r\n";
                  
          default:
                      s 
          = s + "Content-Type: text/html\r\n";
                      
          break;
                  }

                  
          // //so on and so on
                  s = s + "\r\n"// this marks the end of the httpheader
                  
          // and the start of the body
                  
          // ok return our newly created header!
                  return s;
              }
          }
          測試結果如下:
          Concurrency Level:      1000
          Time taken for tests:   111.869356 seconds
          Complete requests:      50000
          Failed requests:        0
          Write errors:           0
          Total transferred:      4950000 bytes
          HTML transferred:       250000 bytes
          Requests per second:    446.95 [#/sec] (mean)
          Time per request:       2237.387 [ms] (mean)
          Time per request:       2.237 [ms] (mean, across all concurrent requests)
          Transfer rate:          43.20 [Kbytes/sec] received

          修改下上面的程序,采用jdk5提供的線程池:
              private static final int NTHREADS = 5;

              
          private static Executor exec;

              
          public static void main(String[] args) throws IOException {
                  ServerSocket server 
          = new ServerSocket(80);
                  
          if (args.length == 0)
                      exec 
          = Executors.newFixedThreadPool(NTHREADS);
                  
          else
                      exec 
          = Executors.newFixedThreadPool(Integer.parseInt(args[0]));
                  
          while (true) {
                      
          final Socket connection = server.accept();
                      Runnable task 
          = new Runnable() {
                          
          public void run() {
                              
          try {
                                  handleRequest(connection);
                              } 
          catch (IOException e) {
                                  e.printStackTrace();
                              }

                          }
                      };
                      exec.execute(task);
                  }
              }
          默認線程池大小取5,后經過反復測試,線程池大小在5左右,測試結果達到最佳。測試采用線程池的結果如下:

          Concurrency Level:      1000
          Time taken for tests:   51.648142 seconds
          Complete requests:      50000
          Failed requests:        0
          Write errors:           0
          Total transferred:      4978908 bytes
          HTML transferred:       251460 bytes
          Requests per second:    968.09 [#/sec] (mean)
          Time per request:       1032.963 [ms] (mean)
          Time per request:       1.033 [ms] (mean, across all concurrent requests)
          Transfer rate:          94.14 [Kbytes/sec] received

          與上面結果一比較,牛人寫的線程池終究是大大不一樣。當連接數增加到10W以上,兩個版本之間的性能差異就更明顯了。這里采用的是固定線程池,如果采用緩沖線程池會怎么樣呢?newFixedThreadPool改為newCachedThreadPool方法,測試可以發現結果與固定線程池的最佳結果相似。CachedThreadPool更適合此處短連接、高并發的場景。后來,我想Erlang寫一個簡單的web server,性能上會不會超過采用線程池的這個版本呢?試試:
          %% httpd.erl - MicroHttpd 
          -module(httpd).
          -export([start/0,start/1,start/2,process/2]).
          -import(regexp,[split/2]). 
          -define(defPort,80). 
          -define(docRoot,"."). 
          start() 
          -> start(?defPort,?docRoot).
          start(Port) 
          -> start(Port,?docRoot). 
          start(Port,DocRoot) 
          -> 
                
          case gen_tcp:listen(Port, [binary,{packet, 0},{active, false}]) of 
                    {ok, LSock}     
          -> 
                         server_loop(LSock,DocRoot);   
                    {error, Reason}     
          -> 
                        exit({Port,Reason}) 
                end.
                
          %% main server loop - wait for next connection, spawn child to process it
                server_loop(LSock,DocRoot) 
          ->   
                    
          case gen_tcp:accept(LSock) of   
                              {ok, Sock}     
          ->  
                                    spawn(
          ?MODULE,process,[Sock,DocRoot]),  
                                    server_loop(LSock,DocRoot);    
                            {error, Reason}     
          ->    
                    exit({accept,Reason})  
            end.
            
          %% process current connection
          process(Sock,DocRoot) 
          ->  
                Req 
          = do_recv(Sock),  
                {ok,[Cmd
          |[Name|[Vers|_]]]} = split(Req,"[ \r\n]"),  
                FileName 
          = DocRoot ++ Name, 
                LogReq 
          = Cmd ++ " " ++ Name ++ " " ++ Vers, 
                Resp 
          = case file:read_file(FileName) of  
                          {ok, Data}     
          ->    
                               io:format(
          "~p ~p ok~n",[LogReq,FileName]), 
                              Data;   
                          {error, Reason}     
          ->   
                               io:format(
          "~p ~p failed ~p~n",[LogReq,FileName,Reason]),   
                             error_response(LogReq,file:format_error(Reason))  
                   end, 
                  do_send(Sock,Resp),
                  gen_tcp:close(Sock). 
                  
          %% construct HTML for failure message 
          error_response(LogReq,Reason) 
          ->  
            
          "<html><head><title>Request Failed</title></head><body>\n" ++
                
          "<h1>Request Failed</h1>\n" ++ 
                
          "Your request to " ++ LogReq ++ 
              
          " failed due to: " ++ Reason ++  "\n</body></html>\n"
          .
                
          %% send a line of text to the 
          do_send(Sock,Msg) 
          ->  
                
          case gen_tcp:send(Sock, Msg) of  
                ok        
          ->
                    ok;  
                {error, Reason}     
          -> 
                    exit(Reason)  
            end. 
                    
          %% receive data from the socket
          do_recv(Sock) 
          ->  
                
          case gen_tcp:recv(Sock, 0) of    
                     {ok, Bin}     
          -> 
                            binary_to_list(Bin);   
                     {error, closed}     
          -> 
                            exit(closed);    
                     {error, Reason}     
          -> 
                            exit(Reason)  
            end.
          執行:
           erl -noshell +5000 -s httpd start

          +P參數是將系統允許創建的process數目增加到50000,默認是3萬多。測試結果:

          Concurrency Level:      1000
          Time taken for tests:   106.35735 seconds
          Complete requests:      50000
          Failed requests:        0
          Write errors:           0
          Total transferred:      250000 bytes
          HTML transferred:       0 bytes
          Requests per second:    471.54 [#/sec] (mean)
          Time per request:       2120.715 [ms] (mean)
          Time per request:       2.121 [ms] (mean, across all concurrent requests)
          Transfer rate:          2.30 [Kbytes/sec] received
              結果讓人大失所望,這個結果與我們自己寫的多線程java版本差不多,與采用線程池的版本就差多了,減少并發的話,倒是比java版本的快點。側面驗證了這個討論的結論:erlang的優勢就是高并發而非高性能。當然,這三者都比不上C語言寫的多線程web server。測試了unix/linux編程實踐中的例子,速度是遠遠超過前三者,不過支持的并發有限,因為系統創建的線程在超過5000時就崩潰了。如果采用jdk5進行開發,應當充分利用新的并發包,可惜我們公司還停留在1.4。




          評論

          # re: 簡單的web server性能測試[未登錄]  回復  更多評論   

          2007-08-29 19:04 by cauherk
          jdk1.4照樣可以使用jdk5帶的線程池,
          PooledExecutor

          EDU.oswego.cs.dl.util.concurrent在這個包里面,jdk5也是這個人寫的,不過移植到jdk5里面了,jboss里面就帶了這個包,叫concurrent.jar

          # re: 簡單的web server性能測試  回復  更多評論   

          2007-08-30 08:23 by 天天看海
          你在《java并發編程實踐》嗎?有個QQ群:34237757,JAVA多線程QQ群,里面的朋友也在研究這本書,歡迎你加入和我們一起交流。

          # re: 簡單的web server性能測試  回復  更多評論   

          2007-08-30 12:42 by JAVA面試題
          QQ群:34237757。我喜歡

          # re: 簡單的web server性能測試  回復  更多評論   

          2007-08-31 02:24 by gengmao
          好文。有參考價值

          # re: 簡單的web server性能測試[未登錄]  回復  更多評論   

          2011-06-24 11:58 by Eric
          這篇文章太片面了,摸黑了Erlang, 用Erlang做開發,你緒要做很多優化工作。你這篇文章一直圍繞多線程來提高性能,這是錯誤的觀點,目前高并發服務基本都是采用事件驅動模型(比如Nginx),就是為了避免大量線程上下文切換帶來的嚴重的CPU開銷以及內存開銷,導致低效率。建議你參照這里:http://www.ibm.com/developerworks/cn/aix/library/au-libev/index.html?ca=drs- Erlang跑在linux kernerl 2.6+上時,應當在編譯以及應用階段開啟其epoll模式,這對性能有極大的影響。epoll是目前linux系統上網絡IO操作事件驅動模型底層的支持。

          # re: 簡單的web server性能測試[未登錄]  回復  更多評論   

          2011-06-24 12:06 by dennis
          @Eric

          這個測試本來就沒有意義,忽略吧。


          主站蜘蛛池模板: 固始县| 延川县| 古蔺县| 潍坊市| 台中县| 澄城县| 原平市| 彭阳县| 玉龙| 江门市| 鄯善县| 哈尔滨市| 铁力市| 甘肃省| 锡林郭勒盟| 陵川县| 金湖县| 柳江县| 积石山| 会理县| 乌拉特中旗| 东兰县| 通道| 龙陵县| 吴江市| 双鸭山市| 胶州市| 龙岩市| 固始县| 乐业县| 白河县| 灌阳县| 揭阳市| 闵行区| 萨嘎县| 当涂县| 南雄市| 乌兰浩特市| 怀远县| 大理市| 宽甸|