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

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

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

    海闊天空

    I'm on my way!
    隨筆 - 17, 文章 - 69, 評(píng)論 - 21, 引用 - 0
    數(shù)據(jù)加載中……

    網(wǎng)絡(luò)服務(wù)器的性能分析

    這篇文章分析網(wǎng)絡(luò)服務(wù)的系統(tǒng)響應(yīng)速度,閱讀這篇文章需要一定的操作系統(tǒng)基礎(chǔ)和網(wǎng)絡(luò)編程基礎(chǔ),建議閱讀《操作系統(tǒng)概念》、《Windows網(wǎng)絡(luò)編程》、《UNIX高級(jí)網(wǎng)絡(luò)編程》。
     
    網(wǎng)絡(luò)服務(wù)的系統(tǒng)響應(yīng)速度就是提交一個(gè)處理請(qǐng)求給網(wǎng)絡(luò)服務(wù)系統(tǒng)開(kāi)始計(jì)時(shí),直到網(wǎng)絡(luò)服務(wù)系統(tǒng)返回處理結(jié)果為止的時(shí)間間隔。系統(tǒng)響應(yīng)速度越快表明服務(wù)器處理效率越高,用戶滿意度也越高。
     

    網(wǎng)絡(luò)服務(wù)結(jié)構(gòu)

    先看一個(gè)典型的網(wǎng)絡(luò)服務(wù),使用類(lèi)C的偽代碼來(lái)描述。

    // 服務(wù)監(jiān)聽(tīng)套接字
    int lsfd;
    // 當(dāng)前連接套接字
    int fd;
    // 連接隊(duì)列
    list fdlist;
    while (true)
    {
         
    // 等待套接字事件
         wait_event(lsfd, fdlist);
         
    // 檢查是否是監(jiān)聽(tīng)套接字的事件
         if (event(lsfd))
         {
             
    // 接收新的連接.
             fd = accept(lsfd);
             
    // 把新的連接放入連接隊(duì)列
             push(fd, fdlist);
             
    // 通知系統(tǒng)有新的連接
             
    // on_connect通常需要進(jìn)行數(shù)據(jù)庫(kù)和日志操作
             on_connect(fd);
         }
         while (-1 != (fd = event(fdlist)))
         {
             
    // 連接上有可接收的數(shù)據(jù)
             if (is_read(fd))
             {
                  
    // 接收數(shù)據(jù)
                  recv(fd);
                  
    // 處理數(shù)據(jù)
                  
    // proc_data通常需要進(jìn)行數(shù)據(jù)庫(kù)和日志操作
                  proc_data(fd);
                  
    // 發(fā)送數(shù)據(jù)
                  send(fd);
             }
             
    // 連接已經(jīng)斷開(kāi)
             if (is_disconnect(fd))
             {
                  
    // 關(guān)閉連接
                  close(fd);
                  
    // 把連接從連接隊(duì)列清除
                  pop(fd);
                  
    // 通知系統(tǒng)連接斷開(kāi)
                  
    // on_disconnect通常需要進(jìn)行數(shù)據(jù)庫(kù)和日志操作
                  on_disconnect(fd);
             }
         }
    }

    首先wait_event等待監(jiān)聽(tīng)套接字lsfd和連接套接字隊(duì)列fdlist上的事件,對(duì)于lsfd,通常關(guān)注新連接到來(lái)的事件,對(duì)于fdlist的套接字連接,通常關(guān)注數(shù)據(jù)接收和連接斷開(kāi)事件。
     
    如果是lsfd的事件,那么通過(guò)accept接收新的連接,然后把連接通過(guò)push添加到fdlist中,最后調(diào)用on_connect通知系統(tǒng)新連接建立,on_connect通常會(huì)向數(shù)據(jù)庫(kù)或者日志系統(tǒng)寫(xiě)入連接日志。
     
    如果是fdlist中某個(gè)或者某些連接的事件,先找出有事件的連接。如果是數(shù)據(jù)接收事件is_readtrue,那么使用recv接收數(shù)據(jù),然后調(diào)用proc_data處理接收的數(shù)據(jù),proc_data通常會(huì)查詢數(shù)據(jù)庫(kù)進(jìn)行業(yè)務(wù)邏輯處理,也會(huì)向日志系統(tǒng)寫(xiě)入處理日志,最后調(diào)用send向客戶端反饋一些處理結(jié)果。如果是連接斷開(kāi)事件is_connect_true,首先調(diào)用close關(guān)閉連接,然后調(diào)用popfdlist中把fd刪除,最后調(diào)用on_disconnect通知系統(tǒng)連接斷開(kāi),on_disconnect可能會(huì)向數(shù)據(jù)庫(kù)或日志系統(tǒng)寫(xiě)入斷開(kāi)日志。
     

    單線程的系統(tǒng)響應(yīng)速度

    先考察邏輯處理響應(yīng)速度,接收數(shù)據(jù)、處理、回復(fù)結(jié)果,在上面的基礎(chǔ)結(jié)構(gòu)里面是recvproc_datasend部分。
    假設(shè)網(wǎng)絡(luò)服務(wù)用來(lái)計(jì)算1+2+…+n等差數(shù)列的和,n由客戶端傳送過(guò)來(lái),服務(wù)端計(jì)算完成后,將計(jì)算結(jié)果寫(xiě)入數(shù)據(jù)庫(kù),然后寫(xiě)入本地文件日志,最后將結(jié)果返回給客戶端,偽代碼如下:

    // 連接上有可接收的數(shù)據(jù)
    if (is_read(fd))
    {
         
    // 接收等差數(shù)列尾數(shù)
         int n = recv(fd);
         
    // 調(diào)用處理函數(shù)
         int m = proc_data(n);
         
    // 回復(fù)計(jì)算結(jié)果到客戶端
         send(fd, m);
    }
    // 處理等差數(shù)列計(jì)算
    int proc_data(int n)
    {
         
    // 計(jì)算等差數(shù)列
         int m = 0;
         for (int i = 1;i <= n;i ++)
         {
             m += i;
         }
         
    // 把M寫(xiě)入數(shù)據(jù)庫(kù)
         db_query(m);
         
    // 把M寫(xiě)入日志
         log(m);
         
    // 返回
         return m;
    }

    假設(shè)recv需要處理時(shí)間Trsend需要處理時(shí)間Ts,計(jì)算等差數(shù)列和需要時(shí)間Te,寫(xiě)入數(shù)據(jù)庫(kù)需要時(shí)間Tdb,寫(xiě)日志需要時(shí)間Tlog,為了便于分析,假設(shè)系統(tǒng)是理想系統(tǒng),既TrTsTdbTlog只與IO相關(guān),Te只與CPU相關(guān),畫(huà)出處理過(guò)程系統(tǒng)調(diào)度圖:
    500)this.width=500;" border="0" width="500">
    1
    如果有N個(gè)連接,他們同時(shí)提交處理請(qǐng)求,如圖1,這些請(qǐng)求將被順序執(zhí)行,那么最先被處理的請(qǐng)求等待時(shí)間最短為:
    T(min) = (Tr+Te+Tdb+Tlog+Ts)
    最后被處理的請(qǐng)求等待時(shí)間最長(zhǎng)為:
    T(max) = N*(Tr+Te+Tdb+Tlog+Ts)
    系統(tǒng)最快響應(yīng)既T(min),系統(tǒng)最慢響應(yīng)既T(max),隨便選擇一個(gè)連接觀察,得到的響應(yīng)速度介于T(min)T(max)之間。
    可以做一個(gè)估算,N = 1000Tr = Ts = 100nsTe = 10nsTdb = 10msTlog = 100ns,那么T(min) = 10.31msT(max) = 10s,等待10秒是大多數(shù)人無(wú)法忍受的。
     

    多線程的系統(tǒng)響應(yīng)速度

    觀察系統(tǒng)調(diào)度圖,藍(lán)色部分TeCPU運(yùn)行時(shí)間,可以看到有大部分時(shí)間CPU處于空閑狀態(tài),這些空閑時(shí)間的CPU可以用來(lái)處理更多的任務(wù),一個(gè)可行的方案就是使用多線程。
    先考察為每個(gè)連接建立一個(gè)處理線程的情況,同樣假設(shè)系統(tǒng)是理想系統(tǒng),線程的創(chuàng)建和銷(xiāo)毀不需要時(shí)間,偽代碼如下:

    // 連接上有可接收的數(shù)據(jù)
    if (is_read(fd))
    {
         
    // 觸發(fā)線程執(zhí)行
         trigger_thread(fd, thread_proc);
    }
    // 線程處理函數(shù)
    void thread_proc(fd)
    {
         
    // 接收等差數(shù)列尾數(shù)
         int N = recv(fd);
         
    // 調(diào)用處理函數(shù)
         int E = proc_data(N);
         
    // 回復(fù)計(jì)算結(jié)果到客戶端
         send(fd, E);
    }
    // 處理等差數(shù)列計(jì)算
    int proc_data(int N)
    {
         
    // 計(jì)算等差數(shù)列
         int M = 0;
         for (int i = 1;i <= N;i ++)
         {
             M += i;
         }
         
    // 把M寫(xiě)入數(shù)據(jù)庫(kù)
         db_query(M);
         
    // 把M寫(xiě)入日志
         log(M);
         
    // 返回
         return M;
    }

    可以畫(huà)出系統(tǒng)調(diào)度圖:
    500)this.width=500;" border="0" width="500">
    2
    如果有N個(gè)連接,就有N個(gè)處理線程,如果N個(gè)連接同時(shí)提交處理請(qǐng)求,CPU同時(shí)其中一個(gè)連接的Te,這些連接請(qǐng)求的Te保持時(shí)間上的連貫性。
    從圖2可以得出系統(tǒng)最快響應(yīng)T(min)和系統(tǒng)最慢響應(yīng)T(max)
    T(min) = (Tr+Te+Tdb+Tlog+Ts)
    T(max) = (N-1)*Te+ (Tr+Te+Tdb+Tlog+Ts)
    這時(shí)T(min)和單線程處理時(shí)T(min)一致,T(max)比單線程處理時(shí)減少了(N-1)* (Tr+Te+Tdb+Tlog+Ts)
    再次使用上面的估算數(shù)據(jù),N=1000Tr = Ts = 100nsTe = 10nsTdb = 10msTlog = 100ns,那么T(min)=10.31msT(max)=20.31ms,等待時(shí)間介于10ms20ms之間,屬于正常等待范圍。
     
    這是在理想情況下得出的結(jié)論,在上面的理想系統(tǒng)中假設(shè)了TrTdbTlogTs只與IO相關(guān)而與CPU無(wú)關(guān),在實(shí)際的系統(tǒng)中,TrTdbTlogTs也需要消耗微量的CPU時(shí)間來(lái)完成運(yùn)算,如果線程數(shù)N小,這些微量的CPU時(shí)間相對(duì)于系統(tǒng)可用的CPU時(shí)間來(lái)說(shuō)是高階無(wú)窮小,可以忽略不計(jì),但是如果N變得很大時(shí),操作系統(tǒng)需要使用大量的CPU時(shí)間來(lái)處理線程調(diào)度,留給系統(tǒng)可用的CPU時(shí)間變少,這時(shí)TrTdbTlogTs消耗的CPU時(shí)間相對(duì)于系統(tǒng)可用的CPU時(shí)間來(lái)說(shuō)在數(shù)量級(jí)上比較接近,變成必須考慮的因素,TrTdbTlogTs與線程N有如下近似關(guān)系:
    500)this.width=500;" border="0">
    3
    如圖3,當(dāng)線程數(shù)N較小時(shí),TrTdbTlogTs接近常數(shù),當(dāng)N變大時(shí),TrTdbTlogTs急速增長(zhǎng)。
    可以根據(jù)圖3畫(huà)出T(min)T(max)對(duì)N的變化趨勢(shì)圖:
    500)this.width=500;" border="0">
    4
    紅色曲線是T(min)變化曲線,藍(lán)色曲線是T(max)變化曲線,當(dāng)N較小時(shí),T(min)T(max)很小并且變化不大,可以為每個(gè)連接開(kāi)一個(gè)處理線程;當(dāng)N變大時(shí),T(min)T(max)也急速變大,如果N成千上萬(wàn),T(min)T(max)甚至可能比單線程時(shí)還大,不能再為每個(gè)連接開(kāi)一個(gè)線程。
     
    當(dāng)N變得很大時(shí),導(dǎo)致T(min)T(max)急速變大的原因是因?yàn)椴僮飨到y(tǒng)使用了大量的CPU時(shí)間來(lái)調(diào)度線程,所以應(yīng)該保持線程數(shù)在一個(gè)范圍之內(nèi),讓調(diào)度程序盡量少占用CPU時(shí)間,一個(gè)線程處理多個(gè)連接的請(qǐng)求,這就是線程池模型。
     
    假設(shè)有M個(gè)處理線程,M較小,畫(huà)出系統(tǒng)調(diào)度圖:
    500)this.width=500;" border="0" width="500">
    5
    5M4,各個(gè)線程之間依然必須滿足CPU時(shí)間上的順序,當(dāng)M很小時(shí),可以看到,一個(gè)線程處理完一個(gè)連接請(qǐng)求后,可以立即處理下一個(gè)連接的請(qǐng)求,因?yàn)?font face="Times New Roman">CPU已經(jīng)空閑,如圖5所示的CPU時(shí)間空洞,這樣每個(gè)處理線程處理請(qǐng)求都是連續(xù)的。
    從圖5可以得出:
    T(min) = Tr+Te+Tdb+Tlog+Ts
    T(max)依然為最后一個(gè)被處理的連接的等待時(shí)間,因?yàn)?font face="Times New Roman">CPU要么處于Te運(yùn)行狀態(tài),要么處于T(idle)的空閑狀態(tài),所以到了處理最后一個(gè)連接的請(qǐng)求時(shí),需要等待的時(shí)間是CPU運(yùn)行時(shí)間總和和CPU空閑時(shí)間的總和,而最后一個(gè)請(qǐng)求需要Tr+Te+Tdb+Tlog+Ts的時(shí)間來(lái)處理,所以T(max)由三部分組成:一是CPU運(yùn)行時(shí)間,二是CPU空閑時(shí)間,三是請(qǐng)求處理時(shí)間。
    CPU運(yùn)行時(shí)間就是處理N-1個(gè)請(qǐng)求需要消耗的CPU時(shí)間為(N-1)*Te
    CPU空閑時(shí)間是(請(qǐng)求分批數(shù)-1)*T(idle),請(qǐng)求分批數(shù)為(N-1-(N-1)%M)/M%表示取余數(shù),分批數(shù)可以理解為一次處理M個(gè)請(qǐng)求的情況下處理N-1個(gè)請(qǐng)求需要多少次。
    T(max) = (N-1)*Te+((N-1-(N-1)%M)/M)*T(idle)+(Tr+Te+Tdb+Tlog+Ts)
    T(idle) = (Tr+Te+Tdb+Tlog+Ts)-M*Te
    所以:
    T(max) = (N-1)*Te+((N-1-(N-1)%M)/M)*((Tr+Te+Tdb+Tlog+Ts)-M*Te)+(Tr+Te+Tdb+Tlog+Ts)
     
    很顯然,T(max)并不是最小的,因?yàn)?font face="Times New Roman">CPU有空閑狀態(tài),這些CPU時(shí)間可以被用來(lái)處理更多的請(qǐng)求,直到CPU不再有空閑狀態(tài),這就要求((N-1-(N-1)%M)/M)*((Tr+Te+Tdb+Tlog+Ts)-M*Te)0,因?yàn)?font face="Times New Roman">((N-1-(N-1)%M)/M)與N相關(guān),通常不為0,所以只能(Tr+Te+Tdb+Tlog+Ts)-M*Te0,既T(idle) = 0,可以計(jì)算出M
    M = (Tr+Te+Tdb+Tlog+Ts)/Te = 1+(Tr+Tdb+Tlog+Ts)/Te
    當(dāng)M取這個(gè)值的時(shí)候,T(max)最小為:
    T(max) = (N-1)*Te+(Tr+Te+Tdb+Tlog+Ts)
    與為使用N個(gè)線程處理N個(gè)請(qǐng)求時(shí)一致。
     
    上面是在M較小并且T(idle) >= 0的情況下得出的結(jié)論,下面繼續(xù)增大M,看看系統(tǒng)有何變化。
    M繼續(xù)增大到大于1+(Tr+Tdb+Tlog+Ts)/Te時(shí),T(max)的計(jì)算式不再有效,因?yàn)?font face="Times New Roman">T(idle)不再存在,CPU滿負(fù)荷運(yùn)轉(zhuǎn),畫(huà)出這時(shí)的系統(tǒng)調(diào)度圖:

    500)this.width=500;" border="0" width="500">

    6
    這時(shí)如果Tr+Te+Tdb+Tlog+Ts較大,每個(gè)線程處理兩次請(qǐng)求可能有時(shí)間間隔,也就是調(diào)度延遲T(delay),因?yàn)?font face="Times New Roman">M越大,處理M個(gè)請(qǐng)求就需要更多的時(shí)間,所以處理下一批M個(gè)請(qǐng)求就需要等待更多的時(shí)間。
    從圖6可以得出:
    T(min) = Tr+Te+Tdb+Tlog+Ts
    T(max)也可以直觀得出,從圖中可知CPU沒(méi)有空閑,那么處理前N-1個(gè)請(qǐng)求需要(N-1)*Te的時(shí)間,處理第N個(gè)請(qǐng)求是緊接著進(jìn)行的,所以:
    T(max) = (N-1)*Te+(Tr+Te+Tdb+Tlog+Ts)
    可見(jiàn)當(dāng)M增大到大于1+(Tr+Tdb+Tlog+Ts)/Te的時(shí)候,T(min)T(max)并不會(huì)減少,相反,跟N個(gè)線程處理N個(gè)請(qǐng)求一樣的道理,M增大后,TrTdbTlogTs會(huì)變大,T(min)T(max)不減反增,所以M = 1+(Tr+Tdb+Tlog+Ts)/Te是較為合理的線程數(shù)。
     
    由于Tr+Tdb+Tlog+Ts只與IO相關(guān),Te只與CPU相關(guān),所以:
    M = 1+Tio/Te
    Tio為請(qǐng)求處理過(guò)程所有IO操作時(shí)間總和,Te為請(qǐng)求處理過(guò)程所有CPU操作時(shí)間總和,就像《程序性能分析》(參考:http://blog.chinaunix.net/u1/52224/showart_417513.html )中分析的一樣,任何一個(gè)處理過(guò)程都可以看作是CPU操作和IO操作的復(fù)合體,所以M = 1+Tio/Te是普適用的。
    如果Tio0,也就是沒(méi)有IO操作,那么M = 1,也就是使用單線程處理和使用多線程處理有同樣的系統(tǒng)響應(yīng)速度,沒(méi)有IO操作也稱(chēng)作計(jì)算密集型的處理。
    如果Te0,也就是沒(méi)有CPU操作,那么M = ∞,也就是應(yīng)該使用盡可能多的線程來(lái)處理,在實(shí)際的系統(tǒng)中沒(méi)有一個(gè)系統(tǒng)Te是為0的,因?yàn)槿魏翁幚磉^(guò)程總是要消耗CPU時(shí)間,包括IO處理本身,只是CPU時(shí)間相對(duì)較少,這也稱(chēng)作IO密集型的處理,在IO設(shè)備能力許可的情況下,線程越多系統(tǒng)響應(yīng)速度會(huì)越快。
     
    在實(shí)際的系統(tǒng)中,TioTe都不是常數(shù),受到IO設(shè)備、CPU使用率、算法設(shè)計(jì)等多方面影響,可能在一定范圍內(nèi)波動(dòng),所以實(shí)際使用的線程池一般都有動(dòng)態(tài)調(diào)節(jié)功能,能根據(jù)系統(tǒng)情況動(dòng)態(tài)調(diào)整線程池的線程數(shù),來(lái)優(yōu)化系統(tǒng)響應(yīng)時(shí)間。

    IO復(fù)用的系統(tǒng)響應(yīng)速度

    除了多線程方式,還可以使用IO復(fù)用的方式來(lái)提高CPU使用率,減少系統(tǒng)響應(yīng)時(shí)間。
    IO復(fù)用也可以稱(chēng)作異步IO操作,也就是IO操作不會(huì)阻塞等待直到操作完成,而是首先提交一個(gè)IO請(qǐng)求立即返回,IO完成后通過(guò)事件通知處理過(guò)程,處理過(guò)程接收到IO完成通知后可以進(jìn)行相應(yīng)處理,這時(shí)CPU可以盡可能多的執(zhí)行Te,畫(huà)出系統(tǒng)調(diào)度圖:
    500)this.width=500;" border="0" width="500">
    7
    系統(tǒng)調(diào)度圖類(lèi)似N個(gè)線程處理N個(gè)請(qǐng)求的情況,從圖7中可以得出系統(tǒng)響應(yīng)時(shí)間:
    T(min) = (Tr+Te+Tdb+Tlog+Ts)
    T(max) = (N-1)*Te+ (Tr+Te+Tdb+Tlog+Ts)
    與用線程池處理一致。
    但是對(duì)存在數(shù)據(jù)庫(kù)操作的系統(tǒng),因?yàn)閿?shù)據(jù)庫(kù)接口由數(shù)據(jù)庫(kù)供應(yīng)商提供,目前大多數(shù)數(shù)據(jù)庫(kù)供應(yīng)商沒(méi)有提供IO復(fù)用的數(shù)據(jù)庫(kù)訪問(wèn)接口,所以使用IO復(fù)用方式難于進(jìn)行數(shù)據(jù)庫(kù)訪問(wèn)操作。

    CPU的系統(tǒng)響應(yīng)速度

    CPU系統(tǒng),首先論證只有所有CPU滿負(fù)荷運(yùn)轉(zhuǎn)時(shí)系統(tǒng)響應(yīng)時(shí)間最小。
    如果任何一個(gè)CPU沒(méi)有滿負(fù)荷運(yùn)轉(zhuǎn)而系統(tǒng)響應(yīng)時(shí)間最小的話,總可以找一段空閑的CPU時(shí)間來(lái)處理最后的第N個(gè)請(qǐng)求,也就是系統(tǒng)響應(yīng)時(shí)間可以變得更小,與系統(tǒng)響應(yīng)時(shí)間最小矛盾,所以系統(tǒng)響應(yīng)時(shí)間最小的時(shí)候一定是所有CPU滿負(fù)荷運(yùn)轉(zhuǎn)的時(shí)候。這個(gè)道理跟上面的CPU時(shí)間空洞時(shí)可以增加線程來(lái)提高CPU利用率的道理一樣。
     
    在所有CPU都滿負(fù)荷運(yùn)轉(zhuǎn)的情況下,雖然不能確定哪個(gè)CPU處理哪個(gè)請(qǐng)求,但是既然都是滿負(fù)荷運(yùn)轉(zhuǎn),究竟誰(shuí)處理哪個(gè)請(qǐng)求并不重要,對(duì)于第一個(gè)被處理的請(qǐng)求,依然要經(jīng)過(guò)TrTeTdbTlogTs的流程,所以:
    T(min) = Tr+Te+Tdb+Tlog+Ts
    對(duì)第N個(gè)請(qǐng)求來(lái)說(shuō),最關(guān)心的就是前面N-1個(gè)花費(fèi)的CPU時(shí)間,一個(gè)CPU的時(shí)候,前面N-1個(gè)請(qǐng)求需要花費(fèi)(N-1)*TeCPU時(shí)間,如果有P個(gè)CPU,那么前面N-1個(gè)請(qǐng)求需要花費(fèi)(N-1)*Te/P的時(shí)間,而處理第N個(gè)請(qǐng)求本身需要花費(fèi)T(min)的時(shí)間,所以:
    T(max) = (N-1)*Te/P+(Tr+Te+Tdb+Tlog+Ts)
    不管使用多線程方式還是使用IO復(fù)用方式,都是基于CPU滿負(fù)荷運(yùn)轉(zhuǎn)的考慮,所以上面的系統(tǒng)響應(yīng)時(shí)間是普遍適用的,并且對(duì)于IO復(fù)用方式,必須使用P個(gè)線程才能使所有CPU都滿負(fù)荷運(yùn)轉(zhuǎn)。
    可見(jiàn),對(duì)于IO密集型系統(tǒng),多CPU并不能明顯提高系統(tǒng)響應(yīng)速度,而對(duì)于計(jì)算密集型系統(tǒng),多CPU能成倍的提高系統(tǒng)響應(yīng)速度。
    再看看合理的線程數(shù)M,因?yàn)樵趩?font face="Times New Roman">CPU的時(shí)候線程數(shù)M1+Tio/Te,所以顯然,在P個(gè)CPU的時(shí)候?yàn)椋?/font>
    M = P*(1+Tio/Te)
    因?yàn)槿绻僭黾泳€程數(shù),因?yàn)闆](méi)有空閑CPU時(shí)間來(lái)執(zhí)行這些線程,所以增加線程數(shù)只會(huì)增加系統(tǒng)負(fù)擔(dān)而不會(huì)提高系統(tǒng)響應(yīng)速度,相反會(huì)降低系統(tǒng)響應(yīng)速度。
    如果減少線程數(shù),CPU使用率不能達(dá)到100%,在單位時(shí)間內(nèi)能處理的請(qǐng)求數(shù)減少,最后一個(gè)請(qǐng)求被處理的時(shí)間延長(zhǎng),系統(tǒng)響應(yīng)速度降低。
     

    連接和斷開(kāi)處理的系統(tǒng)響應(yīng)速度

    連接建立和連接斷開(kāi)也是需要考慮的過(guò)程,分析過(guò)程類(lèi)似請(qǐng)求處理過(guò)程,考慮連接建立過(guò)程,在前面的偽代碼中假設(shè)on_connect有數(shù)據(jù)庫(kù)和日志操作兩個(gè)過(guò)程,用來(lái)把連接狀態(tài)記錄下來(lái),分別用時(shí)TdbTlog,假設(shè)N個(gè)客戶端同時(shí)連接,這時(shí)系統(tǒng)響應(yīng):
    T(min) = Tdb+Tlog
    T(max) = N*(Tdb+Tlog)
    跟單線程處理請(qǐng)求的道理一樣,這時(shí)T(max)可能有10多秒,這樣后面的連接請(qǐng)求可能被操作系統(tǒng)因?yàn)槌瑫r(shí)而強(qiáng)制斷開(kāi),出現(xiàn)連接被拒絕的情況,連接斷開(kāi)的處理是IO密集型操作時(shí)也同理,所以如果連接建立和連接斷開(kāi)的處理過(guò)程是IO密集型操作時(shí)應(yīng)該放到連接池或者用IO復(fù)用來(lái)處理,當(dāng)然,應(yīng)該盡量避免連接建立和連接斷開(kāi)的處理過(guò)程有計(jì)算密集型的操作。
     

    事件通知的設(shè)計(jì)分析

    事件通知過(guò)程是偽代碼wait_event(lsfd, fdlist)的部分:

    while (true)
    {
         // 等待套接字事件
         wait_event(lsfd, fdlist);

    在較早的網(wǎng)絡(luò)服務(wù)設(shè)計(jì)中,大多數(shù)使用selectselect主要存在兩個(gè)個(gè)問(wèn)題:一是有套接字?jǐn)?shù)上限,一般是1024個(gè);二是當(dāng)套接字太多時(shí),select反應(yīng)遲鈍,如果select的等待時(shí)間為Tw,系統(tǒng)最快響應(yīng)時(shí)間:
    T(min) = Tw+Tr+Te+Tdb+Tlog+Ts
    系統(tǒng)最慢響應(yīng)時(shí)間必須考慮最壞的情況,雖然N個(gè)請(qǐng)求被同時(shí)發(fā)出,但是wait_event并不一定會(huì)同時(shí)觸發(fā)事件,最壞的情況是wait_event一個(gè)一個(gè)觸發(fā)這N個(gè)請(qǐng)求的事件,所以系統(tǒng)最慢響應(yīng)時(shí)間:
    T(max) = N*Tw+(N-1)*Te+(Tr+Te+Tdb+Tlog+Ts)
    如果N接近select的極限,TwN成線性增長(zhǎng),N*TwN就成平方增長(zhǎng),所以N*Tw往往比(N-1)*Te+(Tr+Te+Tdb+Tlog+Ts)大很多而成為影響系統(tǒng)響應(yīng)速度的主要因素。
     
    主流操作系統(tǒng)都提供了自己的解決方案。
    Windows使用完成端口模型IOCPIO Complete Port),這個(gè)模型基本原理是操作系統(tǒng)維護(hù)一個(gè)套接字的Hash表,就像偽代碼里面的fdlistHash表由<fd, ptr>對(duì)組成,fd是套接字本身,ptr是程序的數(shù)據(jù)或者對(duì)象指針,IOCP本身是基于IO復(fù)用思想的,當(dāng)程序要接收或發(fā)送數(shù)據(jù)時(shí),只是告訴IOCP需要進(jìn)行的操作,具體的接收和發(fā)送數(shù)據(jù)操作由操作系統(tǒng)進(jìn)行,程序通過(guò)調(diào)用系統(tǒng)查詢函數(shù)來(lái)查詢?cè)摬僮魇欠裢瓿伞?font face="Times New Roman">IOCP內(nèi)置了多CPU的支持,查詢函數(shù)通常由線程池的工作線程來(lái)執(zhí)行,使用多線程的偽代碼如下:

    // 監(jiān)聽(tīng)線程
    void listen_thread()
    {
         
    // IOCP對(duì)象
         HANDLE iocp;
         
    // 監(jiān)聽(tīng)套接字
         int lsfd;
         
    // 用戶對(duì)象指針
         void * ptr;
         while (true)
         {
             
    // 接收新的連接
             int fd = accept(lsfd);
             
    // 放入iocp隊(duì)列
             iocp_push(iocp, fd, ptr);
         }
    }
    // 請(qǐng)求處理線程
    void proc_thread()
    {
         
    // IOCP對(duì)象
         HANDLE iocp;
         
    // 連接套接字
         int fd;
         
    // 用戶對(duì)象
         void * ptr;
         
    // 接收的數(shù)據(jù)
         char * data;
         while (iocp_event(&fd, & ptr))
         {
             
    // 如果是連接事件
             if (is_connect(fd))
             {
                  
    // 通知系統(tǒng)要接收數(shù)據(jù)
                  
    // 這個(gè)過(guò)程不阻塞,下次接收完畢會(huì)通知
                  trigger_recv(iocp, data);
             }
             
    // 如果是接收到數(shù)據(jù)的事件
             else if (is_recved(fd, &data))
             {
                  
    // 處理接收的數(shù)據(jù)
                  proc_data(data, ptr);
                  
    // 回復(fù)處理結(jié)果
                  send(data);
             }
             
    // 如果是斷開(kāi)事件
             else if (is_disconnect(fd))
             {
             }
         }
    }

    監(jiān)聽(tīng)線程負(fù)責(zé)處理監(jiān)聽(tīng)套接字的連接,連接成功就將新的連接套接字放入IOCPHash表;處理線程輪詢IOCP等待連接套接字的事件。如果是連接事件,那么向IOCP提交一個(gè)接收數(shù)據(jù)的請(qǐng)求,也就是等待客戶端發(fā)送的數(shù)據(jù),如果客戶端有數(shù)據(jù)發(fā)送過(guò)來(lái),IOCP會(huì)自動(dòng)接收并寫(xiě)入提交接收數(shù)據(jù)請(qǐng)求的緩沖區(qū)data。如果是接收事件,那么data已經(jīng)被填充數(shù)據(jù),可以進(jìn)行請(qǐng)求處理。ptr是用戶對(duì)象指針,IOCP支持fdptr綁定的好處是程序不再需要維護(hù)一個(gè)<fd, ptr>Hash表,這個(gè)Hash表由操作系統(tǒng)維護(hù),這樣在fd上接收到數(shù)據(jù)時(shí),可以立即獲取ptr關(guān)聯(lián)的對(duì)象和資源進(jìn)行處理,以前使用select時(shí),操作系統(tǒng)維護(hù)了一個(gè)<fd>的鏈表,程序要想獲取與fd關(guān)聯(lián)的對(duì)象和資源,必須自己維護(hù)<fd, ptr>,效率很低。
    IOCP是典型的邊緣觸發(fā)事件模式,也就是有了事件以后只通知一次,如果程序不處理,不會(huì)再次觸發(fā)事件。
    IOCP支持更多的套接字,并且具有接近常數(shù)的很短的響應(yīng)時(shí)間用Tw表示,那么:
    T(min) = Tw+Tr+Te+Tdb+Tlog+Ts
    T(max) = N*Tw+(N-1)*Te+(Tr+Te+Tdb+Tlog+Ts)
    因?yàn)?font face="Times New Roman">Tw很小并且可以看作是常數(shù),所以N*Tw對(duì)于N線性增長(zhǎng),系統(tǒng)可以有很短的響應(yīng)時(shí)間。
     
     
    LinuxepollFreeBSDkqueue提供了IOCP類(lèi)似的功能,不同的是epollkqueue只是通知程序有數(shù)據(jù)可接收而并不負(fù)責(zé)接收數(shù)據(jù),而且epollkqueue都是狀態(tài)觸發(fā),也就是事件會(huì)一直通知直到進(jìn)行了響應(yīng)的處理。
    同樣,使用epollkqueue,能保證T(max)對(duì)于N的線性增長(zhǎng)。
     

    網(wǎng)絡(luò)延遲的影響

    實(shí)際的網(wǎng)絡(luò)系統(tǒng)都有延遲,延遲包括從客戶端到服務(wù)器的延遲和服務(wù)器返回處理結(jié)果的延遲,這兩個(gè)延遲通常一樣,用Td表示,所以實(shí)際的網(wǎng)絡(luò)服務(wù)系統(tǒng)響應(yīng)為:
    T(min) = 2*Td+Tw+Tr+Te+Tdb+Tlog+Ts
    T(max) = 2*Td+N*Tw+(N-1)*Te+(Tr+Te+Tdb+Tlog+Ts)
    這就是為什么進(jìn)行數(shù)據(jù)庫(kù)操作時(shí),通常執(zhí)行批量查詢比一條一條執(zhí)行查詢效率要高,因?yàn)榕坎樵儨p少了多次網(wǎng)絡(luò)往返時(shí)間。

    結(jié)論

    影響網(wǎng)絡(luò)服務(wù)的系統(tǒng)響應(yīng)速度的因素有CPU速度、IO速度、處理流程和結(jié)構(gòu)、事件機(jī)制、網(wǎng)絡(luò)延遲等,在CPU速度、IO速度和網(wǎng)絡(luò)延遲確定的情況下,使用多線程或IO復(fù)用改善處理流程和結(jié)構(gòu)能加速系統(tǒng)響應(yīng)速度,使用多線程時(shí)合理的線程數(shù)與處理過(guò)程的CPU操作時(shí)間和IO操作時(shí)間的比例有關(guān),使用高效的事件機(jī)制如IOCPepollkqueue也能改善系統(tǒng)響應(yīng)速度。


    轉(zhuǎn)自:http://blog.chinaunix.net/u1/52224/showart_425281.html

    posted on 2009-07-27 22:13 石頭@ 閱讀(944) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): 基礎(chǔ)技術(shù)

    主站蜘蛛池模板: 久久久久免费看成人影片| 国产午夜不卡AV免费| 9久9久女女免费精品视频在线观看| 亚洲成av人片天堂网| 永久在线观看免费视频 | 亚洲 欧洲 日韩 综合在线| 最近中文字幕电影大全免费版| 亚洲成人网在线观看| 成年轻人网站色免费看| 国产成人亚洲毛片| 亚洲精品视频免费| 精品视频一区二区三区免费| 亚洲视频小说图片| 久久久www成人免费毛片| 亚洲精品精华液一区二区| 亚洲国产精品无码久久青草 | 免费观看在线禁片| 综合自拍亚洲综合图不卡区| 国产乱码免费卡1卡二卡3卡| 亚洲精品无码不卡在线播放| 亚洲国产高清精品线久久| 免费无码又爽又刺激网站| 亚洲综合色丁香麻豆| 成人av免费电影| 国产精品免费久久久久久久久| 亚洲AV无码AV男人的天堂| 黄瓜视频高清在线看免费下载| 国产亚洲精品91| 中文字幕人成人乱码亚洲电影| 无码av免费网站| 亚洲国产精品成人AV在线| 综合亚洲伊人午夜网 | 成人奭片免费观看| 一级特级aaaa毛片免费观看| 亚洲AV乱码一区二区三区林ゆな | 亚洲Av永久无码精品黑人| 亚洲性久久久影院| 91九色精品国产免费| 日本一区二区三区在线视频观看免费| 亚洲第一AAAAA片| 日本免费一区尤物|