• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            網(wǎng)絡(luò)服務(wù)器軟件開(kāi)發(fā)/中間件開(kāi)發(fā),關(guān)注ACE/ICE/boost

            C++博客 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
              152 Posts :: 3 Stories :: 172 Comments :: 0 Trackbacks
            摘要


            編寫(xiě)連接數(shù)巨大的高負(fù)載服務(wù)器程序時(shí),經(jīng)典的多線(xiàn)程模式和select模式都不再適用。
            應(yīng)當(dāng)拋棄它們,采用epoll/kqueue/dev_poll來(lái)捕獲I/O事件。最后簡(jiǎn)要介紹了AIO。


            由來(lái)


            網(wǎng)絡(luò)服務(wù)在處理數(shù)以萬(wàn)計(jì)的客戶(hù)端連接時(shí),往往出現(xiàn)效率低下甚至完全癱瘓,這被稱(chēng)為
            C10K問(wèn)題。隨著互聯(lián)網(wǎng)的迅速發(fā)展,越來(lái)越多的網(wǎng)絡(luò)服務(wù)開(kāi)始面臨C10K問(wèn)題,作為大型
            網(wǎng)站的開(kāi)發(fā)人員有必要對(duì)C10K問(wèn)題有一定的了解。本文的主要參考文獻(xiàn)是
            <http://www.kegel.com/c10k.htmlhttp://www.kegel.com/c10k.htmls

            C10K問(wèn)題的最大特點(diǎn)是:設(shè)計(jì)不夠良好的程序,其性能和連接數(shù)及機(jī)器性能的關(guān)系往往
            是非線(xiàn)性的。舉個(gè)例子:如果沒(méi)有考慮過(guò)C10K問(wèn)題,一個(gè)經(jīng)典的基于select的程序能在
            舊服務(wù)器上很好處理1000并發(fā)的吞吐量,它在2倍性能新服務(wù)器上往往處理不了并發(fā)
            2000的吞吐量。

            這是因?yàn)樵诓呗圆划?dāng)時(shí),大量操作的消耗和當(dāng)前連接數(shù)n成線(xiàn)性相關(guān)。會(huì)導(dǎo)致單個(gè)任務(wù)
            的資源消耗和當(dāng)前連接數(shù)的關(guān)系會(huì)是O(n)。而服務(wù)程序需要同時(shí)對(duì)數(shù)以萬(wàn)計(jì)的socket進(jìn)
            行I/O處理,積累下來(lái)的資源消耗會(huì)相當(dāng)可觀,這顯然會(huì)導(dǎo)致系統(tǒng)吞吐量不能和機(jī)器性
            能匹配。為解決這個(gè)問(wèn)題,必須改變對(duì)連接提供服務(wù)的策略。


            基本策略


            主要有兩方面的策略:1.應(yīng)用軟件以何種方式和操作系統(tǒng)合作,獲取I/O事件并調(diào)度多
            個(gè)socket上的I/O操作;2. 應(yīng)用軟件以何種方式處理任務(wù)和線(xiàn)程/進(jìn)程的關(guān)系。前者主
            要有阻塞I/O、非阻塞I/O、異步I/O這3種方案,后者主要有每任務(wù)1進(jìn)程、每任務(wù)1線(xiàn)
            程、單線(xiàn)程、多任務(wù)共享線(xiàn)程池以及一些更復(fù)雜的變種方案。常用的經(jīng)典策略如下:

            1.         Serve one client with each thread/process, and use blocking I/O
            這是小程序和java常用的策略,對(duì)于交互式的長(zhǎng)連接應(yīng)用也是常見(jiàn)的選擇(比如BBS)。
            這種策略很能難足高性能程序的需求,好處是實(shí)現(xiàn)極其簡(jiǎn)單,容易嵌入復(fù)雜的交互邏
            輯。Apache、ftpd等都是這種工作模式。

            2.         Serve many clients with single thread, and use nonblocking I/O
            and readiness notification
            這是經(jīng)典模型,datapipe等程序都是如此實(shí)現(xiàn)的。優(yōu)點(diǎn)在于實(shí)現(xiàn)較簡(jiǎn)單,方便移植,也
            能提供足夠的性能;缺點(diǎn)在于無(wú)法充分利用多CPU的機(jī)器。尤其是程序本身沒(méi)有復(fù)雜的
            業(yè)務(wù)邏輯時(shí)。

            3.         Serve many clients with each thread, and use nonblocking I/O and
            readiness notification
            對(duì)經(jīng)典模型2的簡(jiǎn)單改進(jìn),缺點(diǎn)是容易在多線(xiàn)程并發(fā)上出bug,甚至某些OS不支持多線(xiàn)程
            操作readiness notification。

            4.         Serve many clients with each thread, and use asynchronous I/O
            在有AI/O支持的OS上,能提供相當(dāng)高的性能。不過(guò)AI/O編程模型和經(jīng)典模型差別相當(dāng)
            大,基本上很難寫(xiě)出一個(gè)框架同時(shí)支持AI/O和經(jīng)典模型,降低了程序的可移植性。在
            Windows上,這基本上是唯一的可選方案。

            本文主要討論模型2的細(xì)節(jié),也就是在模型2下應(yīng)用軟件如何處理Socket I/O。


            select 與 poll


            最原始的同步阻塞 I/O 模型的典型流程如下:

            同步阻塞 I/O 模型的典型流程

            從應(yīng)用程序的角度來(lái)說(shuō),read 調(diào)用會(huì)延續(xù)很長(zhǎng)時(shí)間,應(yīng)用程序需要相當(dāng)多線(xiàn)程來(lái)解決
            并發(fā)訪(fǎng)問(wèn)問(wèn)題。同步非阻塞I/O對(duì)此有所改進(jìn):

            經(jīng)典的單線(xiàn)程服務(wù)器程序結(jié)構(gòu)往往如下:


            do {

                     Get Readiness Notification of all sockets

                     Dispatch ready handles to corresponding handlers

                               If (readable) {
                                        read the socket

                                        If (read done)

                                                 Handler process the request
                               }

                               if (writable)

                                        write response

                               if (nothing to do)

                                        close socket

            } while(True)

            非阻塞 I/O 模型的典型流程:

            異步阻塞 I/O 模型的典型流程

            其中關(guān)鍵的部分是readiness notification,找出哪一個(gè)socket上面發(fā)生了I/O事件。
            一般從教科書(shū)和例子程序中首先學(xué)到的是用select來(lái)實(shí)現(xiàn)。Select定義如下:


            int select(int n, fd_set *rd_fds, fd_set *wr_fds, fd_set *ex_fds, struct
            timeval *timeout);

            Select用到了fd_set結(jié)構(gòu),從man page里可以知道fd_set能容納的句柄和FD_SETSIZE相
            關(guān)。實(shí)際上fd_set在*nix下是一個(gè)bit標(biāo)志數(shù)組,每個(gè)bit表示對(duì)應(yīng)下標(biāo)的fd是不是在
            fd_set中。fd_set只能容納編號(hào)小于 FD_SETSIZE的那些句柄。

            FD_SETSIZE默認(rèn)是1024,如果向fd_set里放入過(guò)大的句柄,數(shù)組越界以后程序就會(huì)垮
            掉。系統(tǒng)默認(rèn)限制了一個(gè)進(jìn)程最大的句柄號(hào)不超過(guò)1024,但是可以通過(guò)ulimit -n命令
            /setrlimit函數(shù)來(lái)擴(kuò)大這一限制。如果不幸一個(gè)程序在FD_SETSIZE=1024的環(huán)境下編
            譯,運(yùn)行時(shí)又遇到ulimit –n > 1024的,那就只有祈求上帝保佑不會(huì)垮掉了。


            在ACE環(huán)境中,ACE_Select_Reactor針對(duì)這一點(diǎn)特別作了保護(hù)措施,但是還是有recv_n
            這樣的函數(shù)間接的使用了select,這需要大家注意。

            針對(duì)fd_set的問(wèn)題,*nix提供了poll函數(shù)作為select的一個(gè)替代品。Poll的接口如下:


            int poll(struct pollfd *ufds, unsigned int nfds, int timeout);

            第1個(gè)參數(shù)ufds是用戶(hù)提供的一個(gè)pollfd數(shù)組,數(shù)組大小由用戶(hù)自行決定,因此避免了
            FD_SETSIZE帶來(lái)的麻煩。Ufds是fd_set的一個(gè)完全替代品,從select到poll的移植很方
            便。到此為止,至少我們面對(duì)C10K,可以寫(xiě)出一個(gè)能work的程序了。

            然而Select和Poll在連接數(shù)增加時(shí),性能急劇下降。這有兩方面的原因:首先操作系統(tǒng)
            面對(duì)每次的select/poll操作,都需要重新建立一個(gè)當(dāng)前線(xiàn)程的關(guān)心事件列表,并把線(xiàn)
            程掛在這個(gè)復(fù)雜的等待隊(duì)列上,這是相當(dāng)耗時(shí)的。其次,應(yīng)用軟件在select/poll返回
            后也需要對(duì)傳入的句柄列表做一次掃描來(lái)dispatch,這也是很耗時(shí)的。這兩件事都是和
            并發(fā)數(shù)相關(guān),而I/O事件的密度也和并發(fā)數(shù)相關(guān),導(dǎo)致CPU占用率和并發(fā)數(shù)近似成O(n2)
            的關(guān)系。


            epoll, kqueue, /dev/poll


            因?yàn)橐陨系脑颍?nix的hacker們開(kāi)發(fā)了epoll, kqueue, /dev/poll這3套利器來(lái)幫助
            大家,讓我們跪拜三分鐘來(lái)感謝這些大神。其中epoll是linux的方案,kqueue是
            freebsd的方案,/dev/poll是最古老的Solaris的方案,使用難度依次遞增。

            簡(jiǎn)單的說(shuō),這些api做了兩件事:1.避免了每次調(diào)用select/poll時(shí)kernel分析參數(shù)建立
            事件等待結(jié)構(gòu)的開(kāi)銷(xiāo),kernel維護(hù)一個(gè)長(zhǎng)期的事件關(guān)注列表,應(yīng)用程序通過(guò)句柄修改這
            個(gè)列表和捕獲I/O事件。2.避免了select/poll返回后,應(yīng)用程序掃描整個(gè)句柄表的開(kāi)
            銷(xiāo),Kernel直接返回具體的事件列表給應(yīng)用程序。

            在接觸具體api之前,先了解一下邊緣觸發(fā)(edge trigger)和條件觸發(fā)(level trigger)
            的概念。邊緣觸發(fā)是指每當(dāng)狀態(tài)變化時(shí)發(fā)生一個(gè)io事件,條件觸發(fā)是只要滿(mǎn)足條件就發(fā)
            生一個(gè)io事件。舉個(gè)讀socket的例子,假定經(jīng)過(guò)長(zhǎng)時(shí)間的沉默后,現(xiàn)在來(lái)了100個(gè)字
            節(jié),這時(shí)無(wú)論邊緣觸發(fā)和條件觸發(fā)都會(huì)產(chǎn)生一個(gè)read ready notification通知應(yīng)用程
            序可讀。應(yīng)用程序讀了50個(gè)字節(jié),然后重新調(diào)用api等待io事件。這時(shí)條件觸發(fā)的api會(huì)
            因?yàn)檫€有50個(gè)字節(jié)可讀從而立即返回用戶(hù)一個(gè)read ready notification。而邊緣觸發(fā)
            的api會(huì)因?yàn)榭勺x這個(gè)狀態(tài)沒(méi)有發(fā)生變化而陷入長(zhǎng)期等待。

            因此在使用邊緣觸發(fā)的api時(shí),要注意每次都要讀到socket返回EWOULDBLOCK為止,否則
            這個(gè)socket就算廢了。而使用條件觸發(fā)的api時(shí),如果應(yīng)用程序不需要寫(xiě)就不要關(guān)注
            socket可寫(xiě)的事件,否則就會(huì)無(wú)限次的立即返回一個(gè)write ready notification。大家
            常用的select就是屬于條件觸發(fā)這一類(lèi),以前本人就犯過(guò)長(zhǎng)期關(guān)注socket寫(xiě)事件從而
            CPU 100%的毛病。

            epoll的相關(guān)調(diào)用如下:


            int epoll_create(int size)


            int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)


            int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int
            timeout)

            epoll_create創(chuàng)建kernel中的關(guān)注事件表,相當(dāng)于創(chuàng)建fd_set。

            epoll_ctl修改這個(gè)表,相當(dāng)于FD_SET等操作

            epoll_wait等待I/O事件發(fā)生,相當(dāng)于select/poll函數(shù)

            epoll完全是select/poll的升級(jí)版,支持的事件完全一致。并且epoll同時(shí)支持邊緣觸
            發(fā)和條件觸發(fā),一般來(lái)講邊緣觸發(fā)的性能要好一些。這里有個(gè)簡(jiǎn)單的例子:


            struct epoll_event ev, *events;

            int kdpfd = epoll_create(100);

            ev.events = EPOLLIN | EPOLLET;  // 注意這個(gè)EPOLLET,指定了邊緣觸發(fā)

            ev.data.fd =listener;

            epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev);

            for(;;) {

               nfds = epoll_wait(kdpfd, events, maxevents, -1);

             

               for(n = 0; n < nfds; ++n) {

                   if(events[n].data.fd == listener) {

                       client = accept(listener, (struct sockaddr *) &local,

                                       &addrlen);

                       if(client < 0){

                           perror("accept");

                           continue;

                       }

                       setnonblocking(client);

                       ev.events = EPOLLIN | EPOLLET;

                       ev.data.fd = client;

                       if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {

                           fprintf(stderr, "epoll set insertion error: fd=%d0,

                                   client);

                           return -1;

                       }

                   }

                   else

                       do_use_fd(events[n].data.fd);

               }

            }

            簡(jiǎn)單介紹一下kqueue和/dev/poll

            kqueue是freebsd的寵兒,kqueue實(shí)際上是一個(gè)功能相當(dāng)豐富的kernel事件隊(duì)列,它不
            僅僅是select/poll的升級(jí),而且可以處理signal、目錄結(jié)構(gòu)變化、進(jìn)程等多種事件。
            Kqueue是邊緣觸發(fā)的

            /dev/poll是Solaris的產(chǎn)物,是這一系列高性能API中最早出現(xiàn)的。Kernel提供一個(gè)特
            殊的設(shè)備文件/dev/poll。應(yīng)用程序打開(kāi)這個(gè)文件得到操縱fd_set的句柄,通過(guò)寫(xiě)入
            pollfd來(lái)修改它,一個(gè)特殊ioctl調(diào)用用來(lái)替換select。由于出現(xiàn)的年代比較早,所以
            /dev/poll的接口現(xiàn)在看上去比較笨拙可笑。

            C++開(kāi)發(fā):ACE 5.5以上版本提供了ACE_Dev_Poll_Reactor封裝了epoll和/dev/poll兩種
            api,需要分別在config.h中定義ACE_HAS_EPOLL和ACE_HAS_DEV_POLL來(lái)啟用。

            Java開(kāi)發(fā): JDK 1.6的Selector提供了對(duì)epoll的支持,JDK1.4提供了對(duì)/dev/poll的支
            持。只要選擇足夠高的JDK版本就行了。


            異步I/O以及Windows


            和經(jīng)典模型不同,異步I/O提供了另一種思路。和傳統(tǒng)的同步I/O不同,異步I/O允許進(jìn)
            程發(fā)起很多 I/O 操作,而不用阻塞或等待任何操作完成。稍后或在接收到 I/O 操作完
            成的通知時(shí),進(jìn)程就可以檢索 I/O 操作的結(jié)果。

            異步非阻塞 I/O 模型是一種處理與 I/O 重疊進(jìn)行的模型。讀請(qǐng)求會(huì)立即返回,說(shuō)明
            read 請(qǐng)求已經(jīng)成功發(fā)起了。在后臺(tái)完成讀操作時(shí),應(yīng)用程序然后會(huì)執(zhí)行其他處理操
            作。當(dāng) read 的響應(yīng)到達(dá)時(shí),就會(huì)產(chǎn)生一個(gè)信號(hào)或執(zhí)行一個(gè)基于線(xiàn)程的回調(diào)函數(shù)來(lái)完成
            這次 I/O 處理過(guò)程。異步I/O 模型的典型流程:

            異步非阻塞 I/O 模型的典型流程

            對(duì)于文件操作而言,AIO有一個(gè)附帶的好處:應(yīng)用程序?qū)⒍鄠€(gè)細(xì)碎的磁盤(pán)請(qǐng)求并發(fā)的提
            交給操作系統(tǒng)后,操作系統(tǒng)有機(jī)會(huì)對(duì)這些請(qǐng)求進(jìn)行合并和重新排序,這對(duì)同步調(diào)用而言
            是不可能的——除非創(chuàng)建和請(qǐng)求數(shù)目同樣多的線(xiàn)程。

            Linux Kernel 2.6提供了對(duì)AIO的有限支持——僅支持文件系統(tǒng)。libc也許能通過(guò)來(lái)線(xiàn)
            程來(lái)模擬socket的AIO,不過(guò)這對(duì)性能沒(méi)意義。總的來(lái)說(shuō)Linux的aio還不成熟

            Windows對(duì)AIO的支持很好,有IOCP隊(duì)列和IPCP回調(diào)兩種方式,甚至提供了用戶(hù)級(jí)異步調(diào)
            用APC功能。Windows下AIO是唯一可用的高性能方案,詳情請(qǐng)參考MSDN 

            Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1537545


            posted on 2007-04-05 09:00 true 閱讀(1236) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): ACE
            无码任你躁久久久久久老妇App| 少妇人妻综合久久中文字幕| 狠狠色丁香婷婷综合久久来来去 | 潮喷大喷水系列无码久久精品 | 久久噜噜久久久精品66| 久久人人爽人人爽人人片AV东京热| 久久香蕉超碰97国产精品| 精品久久人人妻人人做精品| 少妇无套内谢久久久久| 美女写真久久影院| 久久久久久精品久久久久| 国产精品成人精品久久久| 久久中文字幕人妻丝袜| 99久久精品免费看国产免费| 久久综合国产乱子伦精品免费| 久久婷婷色综合一区二区| 久久精品国产99国产精偷 | 久久成人18免费网站| 久久九九精品99国产精品| 思思久久99热只有频精品66| 久久精品这里热有精品| 蜜臀av性久久久久蜜臀aⅴ麻豆 | 久久成人小视频| 久久99精品久久久久久噜噜| AV色综合久久天堂AV色综合在| 久久人人爽人人爽人人片AV不 | 久久99国产精品久久久| 久久天天躁狠狠躁夜夜躁2O2O| 久久久这里有精品| 亚洲精品tv久久久久| 久久久久亚洲?V成人无码| 精品久久久久久国产牛牛app| 91精品国产综合久久精品| 精品人妻久久久久久888| 久久香蕉国产线看观看精品yw| 久久人人爽人人爽人人片AV东京热| 亚洲欧美一区二区三区久久| 久久久久久久久66精品片| 亚洲性久久久影院| 精品久久久无码人妻中文字幕| 久久人做人爽一区二区三区 |