<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    莊周夢(mèng)蝶

    生活、程序、未來(lái)
       :: 首頁(yè) ::  ::  :: 聚合  :: 管理

    簡(jiǎn)單的web server性能測(cè)試

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

    單線程模式,順序性地處理每一個(gè)請(qǐng)求,50000并發(fā)很快就沒(méi)有響應(yīng)了,不參與比較了。再來(lái)看看我們自己寫(xiě)的多線程方式處理每個(gè)請(qǐng)求:
    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;
        }
    }
    測(cè)試結(jié)果如下:
    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);
            }
        }
    默認(rèn)線程池大小取5,后經(jīng)過(guò)反復(fù)測(cè)試,線程池大小在5左右,測(cè)試結(jié)果達(dá)到最佳。測(cè)試采用線程池的結(jié)果如下:

    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

    與上面結(jié)果一比較,牛人寫(xiě)的線程池終究是大大不一樣。當(dāng)連接數(shù)增加到10W以上,兩個(gè)版本之間的性能差異就更明顯了。這里采用的是固定線程池,如果采用緩沖線程池會(huì)怎么樣呢?newFixedThreadPool改為newCachedThreadPool方法,測(cè)試可以發(fā)現(xiàn)結(jié)果與固定線程池的最佳結(jié)果相似。CachedThreadPool更適合此處短連接、高并發(fā)的場(chǎng)景。后來(lái),我想Erlang寫(xiě)一個(gè)簡(jiǎn)單的web server,性能上會(huì)不會(huì)超過(guò)采用線程池的這個(gè)版本呢?試試:
    %% 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.
    執(zhí)行:
     erl -noshell +5000 -s httpd start

    +P參數(shù)是將系統(tǒng)允許創(chuàng)建的process數(shù)目增加到50000,默認(rèn)是3萬(wàn)多。測(cè)試結(jié)果:

    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
        結(jié)果讓人大失所望,這個(gè)結(jié)果與我們自己寫(xiě)的多線程java版本差不多,與采用線程池的版本就差多了,減少并發(fā)的話,倒是比java版本的快點(diǎn)。側(cè)面驗(yàn)證了這個(gè)討論的結(jié)論:erlang的優(yōu)勢(shì)就是高并發(fā)而非高性能。當(dāng)然,這三者都比不上C語(yǔ)言寫(xiě)的多線程web server。測(cè)試了unix/linux編程實(shí)踐中的例子,速度是遠(yuǎn)遠(yuǎn)超過(guò)前三者,不過(guò)支持的并發(fā)有限,因?yàn)橄到y(tǒng)創(chuàng)建的線程在超過(guò)5000時(shí)就崩潰了。如果采用jdk5進(jìn)行開(kāi)發(fā),應(yīng)當(dāng)充分利用新的并發(fā)包,可惜我們公司還停留在1.4。




    評(píng)論

    # re: 簡(jiǎn)單的web server性能測(cè)試[未登錄](méi)  回復(fù)  更多評(píng)論   

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

    EDU.oswego.cs.dl.util.concurrent在這個(gè)包里面,jdk5也是這個(gè)人寫(xiě)的,不過(guò)移植到j(luò)dk5里面了,jboss里面就帶了這個(gè)包,叫concurrent.jar

    # re: 簡(jiǎn)單的web server性能測(cè)試  回復(fù)  更多評(píng)論   

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

    # re: 簡(jiǎn)單的web server性能測(cè)試  回復(fù)  更多評(píng)論   

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

    # re: 簡(jiǎn)單的web server性能測(cè)試  回復(fù)  更多評(píng)論   

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

    # re: 簡(jiǎn)單的web server性能測(cè)試[未登錄](méi)  回復(fù)  更多評(píng)論   

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

    # re: 簡(jiǎn)單的web server性能測(cè)試[未登錄](méi)  回復(fù)  更多評(píng)論   

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

    這個(gè)測(cè)試本來(lái)就沒(méi)有意義,忽略吧。


    主站蜘蛛池模板: 青娱乐免费在线视频| 国产免费一区二区视频| 免费观看男人免费桶女人视频| 亚洲综合小说久久另类区| 无码精品一区二区三区免费视频| 国产成人精品日本亚洲网站| 免费无码又爽又刺激一高潮| 好看的电影网站亚洲一区| 国精产品一区一区三区免费视频| 亚洲级αV无码毛片久久精品| 永久免费AV无码网站国产 | 午夜网站免费版在线观看| 亚洲xxxx视频| 国产猛烈高潮尖叫视频免费| 免费无遮挡无遮羞在线看| 伊人久久大香线蕉亚洲| 免费视频成人片在线观看| 亚洲理论在线观看| 免费毛片在线播放| 久久久精品国产亚洲成人满18免费网站 | 黄页免费视频播放在线播放| 中文字幕亚洲日韩无线码| 中文字幕久精品免费视频| 亚洲av无码国产综合专区| 国产免费看插插插视频| 国产中文字幕在线免费观看| 亚洲短视频男人的影院| 成年轻人网站色免费看| 特a级免费高清黄色片| 97久久精品亚洲中文字幕无码 | 久久久久久国产精品免费无码| 亚洲午夜久久久久久尤物| 国产免费人视频在线观看免费| 精品一区二区三区免费观看| 亚洲精品亚洲人成在线观看麻豆| 国产精品美女自在线观看免费| 日本黄色动图免费在线观看| 亚洲日韩亚洲另类激情文学| 亚洲综合AV在线在线播放| 日韩欧美一区二区三区免费观看| 免费大片av手机看片高清|