• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • Apache 性能最優化分析(下)

    發表于:2007-05-25來源:作者:點擊數: 標簽:
    有兩種實現這個特性的辦法:一是socket的SO_LINGER選項。但似乎是命中注定,在多數TCP/IP協議棧中它從來不能正確地實現。即使是在提供了正確實現的平臺(即 Linux 2.0.31)上,這種方法也要比第二種方法代價(指CPU時間)高得多。 大多數情況下,Apache在一
    有兩種實現這個特性的辦法:一是socket的SO_LINGER選項。但似乎是命中注定,在多數TCP/IP協議棧中它從來不能正確地實現。即使是在提供了正確實現的平臺(即Linux 2.0.31)上,這種方法也要比第二種方法代價(指CPU時間)高得多。

      大多數情況下,Apache在一個叫lingering_close的函數中實現了它(在 http_main.c)。這個函數大致如下所示:

    void lingering_close (int s)
    {
    char junk_buffer[2048];

    /* shutdown the sending side */
    shutdown (s, 1);

    signal (SIGALRM, lingering_death);
    alarm (30);

    for (;;) {
    select (s for reading, 2 second timeout);
    if (error) break;
    if (s is ready for reading) {
    read (s, junk_buffer, sizeof (junk_buffer));
    /* just toss away whatever is here */
    }
    }

    close (s);
    }

      這自然增加了連接結束時的開銷,但它是可靠的實現所必需的。隨著HTTP/1.1的日益盛行,所有連接都是持久的,這種開銷將被眾多的連接請求抵消。如果您想冒險禁止這一特性的話,可以定義宏NO_LINGCLOSE,但這顯然是不被推薦的。實際上,由于在HTTP/1.0中持久的管道式連接越來越普遍,lingering_close幾乎是必須的選擇。(管道式連接非常高效,所以您還是希望支持它的吧)

      5) 記分板文件

      Apache利用一種叫做記分板(scoreboard)的技術在父、子進程間通訊。它的理想實現是在共享內存中。有的操作系統允許我們直接訪問共享內存,或者提供它們的確切端口。在這些系統中的典型實現就是共享內存記分板。其他的系統則將磁盤上的文件作為缺省實現。磁盤文件不僅低效而且不穩定(又沒有什么優勢)。請為您的操作系統仔細閱讀src/main/conf.h文件,并在其中尋找USE_MMAP_SCOREBOARD或者USE_SHMGET_SCOREBOARD。定義它們之一(以及相應的HAVE_MMAP和HAVE_SHMGET)將允許Apache使用共享內存。如果您系統的內存共享機制與眾不同,請編輯src/main/http_main.c并增加Apache所需的掛鉤函數(同時請把補丁寄給我們)

      注:直到1.2版,Apache的Linux版才開始使用共享內存。這一疏忽使得以前版本的Apache在Linux上表現得很不理想。

      DYNAMIC_MODULE_LIMIT

      如果您不打算支持動態加載模塊的話(準備榨出最后一滴性能的您可能希望如此),編譯服務器時請設定參數-DDYNAMIC_MODULE_LIMIT=0。這將節省出為動態加載模塊而分配的內存。


      附錄:對某次跟蹤狀況的詳細分析

      本附錄描述了運行在Linux上的Apache 1.3系統調用的跟蹤情況。運行時(run-time)配置文件中除了必要的缺省選項外還增加了:

        
        AllowOverride none
        Options FollowSymLinks
        


      被請求的文件是一個6K的靜態網頁,其中不包含特殊內容。對于非靜態或者伴隨有內容協商的請求,跟蹤結果將有明顯的不同(一些情況下會十分晦澀)。我們將首先列出完整的跟蹤結果,然后逐條進行分析。(它是由strace跟蹤程序生成的,其他類似的程序包括truss、ktrace和par)

    accept(15, {sin_family=AF_INET, sin_port=htons(22283), sin_addr=inet_addr("127.0.0.1")}, [16]) = 3
    flock(18, LOCK_UN) = 0
    sigaction(SIGUSR1, {SIG_IGN}, {0x8059954, [], SA_INTERRUPT}) = 0
    getsockname(3, {sin_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0
    setsockopt(3, IPPROTO_TCP1, [1], 4) = 0
    read(3, "GET /6k HTTP/1.0\r\nUser-Agent: "..., 4096) = 60
    sigaction(SIGUSR1, {SIG_IGN}, {SIG_IGN}) = 0
    time(NULL) = 873959960
    gettimeofday({873959960, 404935}, NULL) = 0
    stat("/home/dgaudet/ap/apachen/htdocs/6k", {st_mode=S_IFREG|0644, st_size=6144, ...}) = 0
    open("/home/dgaudet/ap/apachen/htdocs/6k", O_RDONLY) = 4
    mmap(0, 6144, PROT_READ, MAP_PRIVATE, 4, 0) = 0x400ee000
    writev(3, [{"HTTP/1.1 200 OK\r\nDate: Thu, 11"..., 245}, {"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 6144}], 2) = 6389
    close(4) = 0
    time(NULL) = 873959960
    write(17, "127.0.0.1 - - [10/Sep/1997:23:39"..., 71) = 71
    gettimeofday({873959960, 417742}, NULL) = 0
    times({tms_utime=5, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 446747
    shutdown(3, 1 /* send */) = 0
    oldselect(4, [3], NULL, [3], {2, 0}) = 1 (in [3], left {2, 0})
    read(3, "", 2048) = 0
    close(3) = 0
    sigaction(SIGUSR1, {0x8059954, [], SA_INTERRUPT}, {SIG_IGN}) = 0
    munmap(0x400ee000, 6144) = 0
    flock(18, LOCK_EX) = 0

      accept串行化:

        flock(18, LOCK_UN) = 0
        ...
        flock(18, LOCK_EX) = 0


      這兩個調用可以被上文提到的宏SINGLE_LISTEN_UNSERIALIZED_ACCEPT去掉。

      對信號SIGUSR1的處理:

        sigaction(SIGUSR1, {SIG_IGN}, {0x8059954, [], SA_INTERRUPT}) = 0
        ...
        sigaction(SIGUSR1, {SIG_IGN}, {SIG_IGN}) = 0
        ...
        sigaction(SIGUSR1, {0x8059954, [], SA_INTERRUPT}, {SIG_IGN}) = 0


      引起它們的原因是"溫和重啟"(graceful restart --用SIGUSR1而不是SIGHUP使Apache重新啟動。這種方式為Apache留出了自行處理的余地--譯者注)。當父進程收到SIGUSR1后,它將把這一信號傳遞給所有子進程(同時遞增在共享內存中更新計數器的值"generation counter")。所有空閑的子進程(即在兩次連接之間的子進程)收到信號后將立即終止。所有處于持續連接(keep-alive)但在兩次請求之間的子進程也將立即終止。但處于連接中并等待第一次請求的子進程將不會立即終止。

      為了說明它的必要性,請考慮一個瀏覽器對已關閉連接的處理。如果已關閉的連接是持續連接,而且下一個請求不是該連接的第一個請求,瀏覽器將不動聲色地建立另一個連接并重新發出請求。由于服務器任何時候都可能關閉一個持續連接(可能因為超時或者超過了最大請求數目),這樣處理是有必要的。但是,如果在回應第一個請求前連接就被關閉,瀏覽器通常會顯?quot;文檔中無數據"的對話框(或者顯示被折斷的圖片)。這是在假設服務器非正常終止(或者太忙)的情況下作出的反應。因此,Apache力圖避免在作出任何回應前就關閉連接。這就是處理SIGUSR1的原因。

      盡管在理論上避免那三個調用是可行的,但在粗略的測試中這樣的改進是微不足道的。

      為了實現虛擬主機,Apache需要用本地socket地址接受連接:

        getsockname(3, {sin_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0

      在許多情況下都有可能去掉這個調用(比如沒有虛擬主機,或者在Listen命令中的地址不含通配符)。但目前尚未對此進行優化。

    Apache關掉了Nagle算法:

      setsockopt(3, IPPROTO_TCP1, [1], 4) = 0

      考慮到John Heidemann的論文中提及的問題,請關注兩個time調用:

        time(NULL) = 873959960
        ...
        time(NULL) = 873959960


      第一個出現在請求的開始,另一個用于寫入日志。前者是正確實現HTTP協議所必須的,后者的出現是因為通用日志格式指定了記錄中包含時間戳。自定義日志模塊能夠去掉這個調用?;蛘吣梢杂媚承┓椒ò褧r間移到共享內存中。參見補丁一節。

      正如前文描述的那樣,ExtendedStatus On將引發兩次gettimeofday調用和一次times調用:

        gettimeofday({873959960, 404935}, NULL) = 0
        ...
        gettimeofday({873959960, 417742}, NULL) = 0
        times({tms_utime=5, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 446747


      ExtendedStatus Off (即缺省值)將避免這些調用。

      這個調用看起來也許有些奇怪:

      stat("/home/dgaudet/ap/apachen/htdocs/6k", {st_mode=S_IFREG|0644, st_size=6144, ...}) = 0
    它用于生成CGI程序所需的PATH_INFO環境變量。事實上,對于指向/cgi-bin/printenv/foobar的某個請求,stat將被調用兩次。第一次調用查詢/home/dgaudet/ap/apachen/cgi-bin/printenv/foobar,但它并不存在;第二次調用查詢/home/dgaudet/ap/apachen/cgi-bin/printenv,它是存在的。無論如何,對于靜態請求而言,至少需要一次調用。文件尺寸和修改時間被用來生成HTTP頭標(比如Content-Length和Last-Modified)并用來實現協議的特色部分(比如If-Modified-Since)。一個聰明的服務器能夠對非靜態的請求避免調用stat,但是這樣的實現對于模塊化的Apache來說太難了。

      所有靜態文件使用mmap:

        mmap(0, 6144, PROT_READ, MAP_PRIVATE, 4, 0) = 0x400ee000
        ...
        munmap(0x400ee000, 6144) = 0


      在一些系統上mmap小文件的效率不如直接讀取該文件。宏MMAP_THRESHOLD用來設置應用mmap時的最小文件尺寸。缺省值是0(但在SunOS4上的缺省值是8129。實驗證明這個值在該系統上比較理想)類似lmbench的工具可以幫助您在您的系統上進行優化設置。

      您也許樂意在MMAP_SEGMENT_SIZE上做個實驗(缺省值32768)。它決定了被mmap的文件將以一次多少個字節寫出。Apache只在每次write之間重置客戶的超時時間,因此把這個值設得過大容易把帶寬較窄的用戶拒之門外--除非同時增加Timeout。

      您的系統有可能根本不用mmap。如果是這樣的話,定義USE_MMAP_FILES和HAVE_MMAP也許會奏效(如果它真的有效請告訴我們)。

      Apache盡全力避免在內存中拷貝數據。對任何請求的首次寫出都將借助writev合并頭標及第一塊數據:

        writev(3, [{"HTTP/1.1 200 OK\r\nDate: Thu, 11"..., 245},{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 6144}], 2) = 6389

      當進行HTTP/1.1塊狀編碼時,Apache將生成最多為4個元素的writev。它的目標是將字節拷貝至內核,這是典型情況下必須做的事情(為了組裝網絡數據包)。2.0.31之前的Linux并不進行合并,而是為每個元素生成一個數據包。因此升級系統是一個好主意。定義NO_WRITEV將阻止這種合并,但將使得塊狀編碼的性能很差。

      日志文件的寫入工作

        write(17, "127.0.0.1 - - [10/Sep/1997:23:39"..., 71] = 71

      能夠被宏定義BUFFERED_LOGS推遲。這種情況下,在真正寫入文件之前,最多PIPE_BUF個字節(POSIX標準定義的常量)的日志信息將被緩存。由于寫入條目不是atomic的(就是說來自不同子進程的信息將混合在一起),因此跨越PIPE_BUF邊界的條目不會被分割。當子進程終止時,Apache用出色的方式將緩存排空。

      延遲關閉引發了四個系統調用:

        shutdown(3, 1 /* send */) = 0
        oldselect(4, [3], NULL, [3], {2, 0}) = 1 (in [3], left {2, 0})
        read(3, "", 2048) = 0
        close(3) = 0


      這些在前文已經提及。

      當我 們使用了-DSINGLE_LISTEN_UNSERIALIZED_ACCEPT、-DBUFFERED_LOGS 和 ExtendedStatus Off 的優化參數后,最終得到的跟蹤結果如下:

    accept(15, {sin_family=AF_INET, sin_port=htons(22286), sin_addr=inet_addr("127.0.0.1")}, [16]) = 3
    sigaction(SIGUSR1, {SIG_IGN}, {0x8058c98, [], SA_INTERRUPT}) = 0
    getsockname(3, {sin_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0
    setsockopt(3, IPPROTO_TCP1, [1], 4) = 0
    read(3, "GET /6k HTTP/1.0\r\nUser-Agent: "..., 4096) = 60
    sigaction(SIGUSR1, {SIG_IGN}, {SIG_IGN}) = 0
    time(NULL) = 873961916
    stat("/home/dgaudet/ap/apachen/htdocs/6k", {st_mode=S_IFREG|0644, st_size=6144, ...}) = 0
    open("/home/dgaudet/ap/apachen/htdocs/6k", O_RDONLY) = 4
    mmap(0, 6144, PROT_READ, MAP_PRIVATE, 4, 0) = 0x400e3000
    writev(3, [{"HTTP/1.1 200 OK\r\nDate: Thu, 11"..., 245}, {"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 6144}], 2) = 6389
    close(4) = 0
    time(NULL) = 873961916
    shutdown(3, 1 /* send */) = 0
    oldselect(4, [3], NULL, [3], {2, 0}) = 1 (in [3], left {2, 0})
    read(3, "", 2048) = 0
    close(3) = 0
    sigaction(SIGUSR1, {0x8058c98, [], SA_INTERRUPT}, {SIG_IGN}) = 0
    munmap(0x400e3000, 6144) = 0

      只剩下19個系統調用了。其中有四個很容易被移走,但沒有什么必要了。

      附錄:可用的補丁

      這里有一些1.3版的性能補丁。但隨著1.3.0版的發行它們可能有些過時。對掌握一點C語言的知識的人來說,升級它們并不是難事。特別地:
    • 有一個補丁去掉所有time(2)系統調用。
    • 有一個補丁去掉mod_include中的許多系統調用,它們僅出于向后兼容而被少數站點所使用。
    • 有一個補丁結合了上面兩個特性,并通過去掉一些特性使服務器加速。
      附錄:預分支(Pre-Forking)模型

      Unix上的Apache是應用了預分支模型的服務器。父進程的責任僅在于繁衍子進程,它從不響應來自socket的任何請求。真正處理連接的是子進程,每個子進程在終止之前會(逐一地)為多個連接服務。父進程根據服務器負載的變化(通過監視記分板,記分板由子進程負責保持同步)生成新的或者殺掉舊的子進程。

      這種模型為服務器提供了其他模型所不具備的健壯。父進程的代碼非常簡單,它有足夠的信心保證在不出現錯誤的情況下持續運行。子進程就很復雜了,而且當您加入了第三方提供的模塊后,將冒segmentation fault和其他崩潰的危險。即便這樣的事情發生了,也只會影響到一個連接。父進程將繼續為請求服務,并迅速替換掉已經死亡的子進程。

      預分支在不同的Unix之間有良好的可移植性。Apache向來將它作為重要的目標之一,并且將保持下去。

      但預分支模型由于各種各樣的性能問題而飽受批判。主要的因素是分支進程帶來的負擔、上下文切換帶來的負擔和多個進程為內存帶來的負擔。另外它不能為請求提供有效的緩存機制(比如mmap文件池)。另有一些模型。JAWS project的論文對它們進行了詳細的分析。實際上,所有這些模型帶來的性能損失在不同操作系統上的差別迥異。

      Apache的內核代碼已經支持多線程。NT上的Apache 1.3就是多線程的。至少有另外兩種實驗性的多線程Apache:一個基于1.3內核并運行在DCE上;另一個基于1.0內核,它使用了一套自定義的用戶級線程庫。它們都不是對公眾發行的。有一個已經發行了的Apache實驗版本:運行于Netscape運行時可移植(Portable Run Time)平臺上的1.3版,可以在此下載(如果您準備使用它的話,歡迎您加入new-httpd郵件列表)。被重新設計的Apache2.0將包含抽象化的服務器模型,它使我們可以既支持預分支模型,又支持多種線程模型。

    原文轉自:http://www.kjueaiud.com

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>