Web Server 架構淺談-Simple Event-Driven Achitecture

本節首先給出關於架構評價的一些指標:

      [H. Xie 2002]關於架構提出了micro performance和macro performance,以此爲參考,在本系列中,對架構好壞的評價分析時也是從這兩個層面來討論,但內涵稍有不同:

    用戶角度:公平性,響應時間,吞吐率。其中響應時間有包括等待時間和處理時間。對人來說公平性是第一位的,如果一個架構是歧視性的,往往沒有生命力,先進先出的策略是公認的公平的策略。每個用戶都希望自己提交的任務可以儘快處理,但在耐受力上等待時間和處理時間有不同,去銀行辦業務,等待4分鐘辦理業務1分鐘的體驗和等待1分鐘辦理業務4分鐘的體驗會不同。由於任務的處理時間和任務的複雜程度以及資源的競爭使用有關,因此架構主要考慮的是減少等待時間,減少資源的競爭。吞吐率是單位時間處理的請求數,這也是評價服務質量的重要指標。

    系統角度:每指令需要耗費的指令週期CPI,緩存命中率,資源的飽和使用程度,開發的難易程度,代碼的可維護性等。在資源競爭程度高的架構上CPI會很高,在局部性不強的架構緩存命中率會很低,CPU佔用率低,磁盤的IO低的架構是浪費的架構,開發難度大正確性很難保障的架構維護成本也會很大,因此架構越是自然,越是符合業務規律越是好架構。

    回顧我們此前提到的架構,這裏來做一個定性的評價:


不再展開,開始講Simple Event-Driven Achitecture。

      說事件驅動就不能不說epoll,時下最流行的事件驅動最佳方案,在介紹epoll之前,先區分兩個概念:邊緣觸發通知(edge-triggered readiness notification)和條件觸發通知(level-triggered readiness notification):

 

    邊緣觸發的含義是:在應用程序傳給內核一個文件描述符(FD)後(一個SOCKET鏈接可以看做是一個FD),只有當該FDnot ready切換到ready狀態(有字節可讀)時,內核會通知這個狀態變化,而不會通知是否已經讀完,也就是後面需要由應用程序持續進行讀取,直到讀到EWOULDBLOCK爲止,否則這個SOCKET的狀態就一直保持在ready上,應用程序沒有持續讀到EWOULDBLOCK,出現了交互的僵死,雙方都不明對方的狀態。邊緣觸發讀通知,也有稱作爲準備狀態改變通知(readiness change notification)。  

    條件觸發的含義是:[J Lemon 2001]首次提出該術語,和邊緣觸發不同,條件觸發的含義是“there is unread data in the buffer”,只要在SOCKET上還有未讀完的數據,就會給出條件觸發通知,通知應用程序有數據可讀。

 

    在epoll方案在業界被普遍採用之前,最早是select方法,但由於可擴展性等問題,又推出了poll的方法。然而 無論是Select 還是Poll 在連接數增加時,性能急劇下降。這有兩方面的原因:首先操作系統面對每次的select/poll 操作,都需要重新建立一個當前線程的關心事件列表,並把線程掛在這個複雜的等待隊列上,這是相當耗時的。其次,應用軟件在select/poll 返回後也需要對傳入的句柄列表做一次掃描來dispatch,這也是很耗時的。這兩件事都是和併發數相關,而I/O 事件的密度也和併發數相關,導致CPU 佔用率和併發數近似成O(n2)的關係[C10K問題]。

    我們先從一個圖來看什麼是事件驅動的架構,如下圖所示。



上圖是一個最簡單的單進程事件驅動模型,epoll方法輪詢取獲取可讀的socket,每個socket上的數據是用戶的request。在獲取了一把這樣的socket後,依次進行處理,並返回給用戶端響應的數據。

       在用法上epoll和select,poll類似,[C10K問題]給出了一個簡單的例子:

struct epoll_event ev, *events;

int kdpfd = epoll_create(100);

ev.events = EPOLLIN | EPOLLET;                  // EPOLLET,指定了邊緣觸發

ev.data.fd =listener;

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

for(;;) {

nfds = epoll_wait(kdpfd, events, maxevents, -1);                //和selec,poll類似

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;

}

else

{

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=%d”,client);

return -1;

}

}

else{

do_use_fd(events[n].data.fd);//讀或者寫操作

}

}

                            }

 

      epoll的操作十分簡單,一共就4個API:epoll_create, epoll_ctl, epoll_wait和close。使用方便,本文不再贅述。對於事件驅動架構,我一直很難想到一個生活中的例子來說明,如果有人能給我這方面的提示,非常感謝。

 

     下文在對簡單事件驅動架構做評價,以及分階段的事件驅動架構staged event-driven architecture(SEDA),待續。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章