常見開源服務器模型學習 未完待續

  https://blog.csdn.net/answer3y/article/details/48276687html

 

本文要描述的主要有以下6種模型:
1)epoll 1線程(listen+accept+epoll_wait+處理) 模型 ...........................................表明產品redis
2)epoll 1線程(listen+accept+epoll_wait) + 1隊列通知 + n線程(處理) 模型............表明產品thrift-nonblocking-server
2)epoll 1線程(listen+accept+epoll_wait) + n隊列通知 + n線程(處理) 模型............表明產品memcached 
4)epoll 1進程(listen) + n進程(accept+epoll_wait+處理) 模型...............................表明產品nginx
5)epoll 1線程(listen) + n線程(accept+epoll_wait+處理) 模型
6)  epoll 1線程(listen+accept) + n線程(epoll_wait+處理) 模型
最後還有一個章節,對6種模型作一下統一的總結。
--------------------- linux

epoll網絡編程幾個主要函數的用途,這樣能更好的理解下面6種模型
listen_fd = socket(...); // 建立listen_fd
bind(listen_fd, addr); // 把listen_fd綁定到本地地址addr
listen(listen_fd, ...); // 監聽listen_fd
fd = accept(listen_fd, ...); // 從listen_fd接受一個新接進來的鏈接套接字fd
epfd = epoll_create(...); // 建立指定大小的epoll句柄epfd
epoll_ctl(epfd, op, fd, event); // 對epfd作op操做,操做涉及監聽fd的event事件
// 好比op是EPOLL_CTL_ADD,意思是把 「對fd監聽event事件」 註冊到 epfd裏面
num = epoll_wait(epfd, events, ...); // 監聽句柄epfd上的事件,並把事件經過event數組返回,num返回值是發生事件的個數
---------------------

nginx

 

1、epoll 1線程(listen+accept+epoll_wait+處理) 模型
一、表明開源產品:redis
二、基本原理:
這種模型基本就是教科書上的epoll使用方式:
socket -> bind -> listen -> epoll_wait ->  accept或者處理讀寫事件 -> epoll_wait ......
redis基本遵循這樣循環處理 網絡事件和定時器事件
三、echo server測試:10萬QPS
四、優勢:
1)模型簡單。這個模型是最簡單的,代碼實現方便,(因此這個單線程)適合 計算密集型應用                          若是是 IO 密集型那麼最好就要使用多線程來提升cpu的利用率來
2)不用考慮併發問題。模型自己是單線程的,使得服務的主邏輯也是單線程的,那麼就不用考慮許多併發的問題,好比鎖和同步
3)適合短耗時服務。對於像redis這種每一個事件基本都是查內存,是十分適合的,一來併發量能夠接受,二來redis內部衆多數據結構都是很是簡單地實現
五、缺點:
1)順序執行影響後續事件。由於全部處理都是順序執行的,因此若是面對長耗時的事件,會延遲後續的全部任務,特別對於io密集型的應用,是沒法承受的
---------------------

web

 

2、epoll   1線程(listen+accept+epoll_wait)    +    1隊列通知    +    n線程(處理) 模型
一、表明開源產品:thrift-nonblocking-server
二、基本原理:
1)在這種模型中,有1+n個線程。
2)有1個線程執行端口的listen並把listen_fd加入該線程的epoll_set,而後循環去作以下事情:1)epoll_wait監聽新鏈接的到來,2)調用accept得到新到的fd,3)把fd放入隊列,4) 回到 「1)」 繼續epoll_wait
3)另外有n個工做線程,從隊列裏面獲取文件描述符,而後執行:1)讀取數據,2)執行任務
三、echo server測試:6萬QPS
四、優勢:
1)模型簡單。這種模型的代碼實現也是很是方便的
2)併發能力強。對於任務耗時或者IO密集的服務,能夠充分利用多核cpu的性能實現異步併發處理
3)適合生產環境。雖然QPS沒有特別高,可是對於目前大部分大型網站的吞吐量,這種網絡處理能力也是足夠了的,這也是爲何thrift nonblocking server能夠用這種模型的緣由
4)負載均衡。在這個模型,每一個工做工做線程完成任務以後就會去隊列裏主動獲取文件描述符,這個模型自然地就實現了負載均衡的功能。緣由有2,一來是隻有空閒的線程會拿到任務,二來是全部fd的事件監聽都是由監聽線程完成
五、缺點:
1)隊列是性能瓶頸。
俗話說,不怕鎖,只怕鎖競爭。這個模型在運行過程當中,n+1個線程競爭於隊列之上,因此隊列的訪問是須要加鎖的。對於echo server這種每次任務耗時都極短的服務,每次處理完任務就很快就會回到隊列鎖的爭搶行列。大量的鎖競爭直接拖垮了QPS。
不過好在常見的生產環境web服務都不是echo server,每次請求都會是毫秒級的,不會在鎖競爭上產生問題。
---------------------

redis

 

3、epoll   1線程(listen+accept+epoll_wait)  +  n隊列通知  +  n線程(處理) 模型
一、表明開源產品:memcached
二、基本原理:
這種模型基本相似於 上一種模型,   區別在於 把1個隊列換成n個隊列,每一個工做線程綁定一個隊列,每一個工做線程從本身的隊列消費數據,其餘的保持一致
三、echo server測試:20萬QPS
四、優勢:
1)併發能力更強。相比於單隊列的模型,多隊列的好處是減小了隊列的鎖競爭。 對於短耗時任務能獲得比較多的提高,很適合緩存類應用
五、缺點:
1)有可能致使負載不均。由於監聽線程是不會去根據不一樣線程的處理速度決定把任務分配給哪一個線程的,  若是每一個任務的耗時不均衡,那麼就可能致使有些線程累死,有些線程餓死
六、memcached對該模型改進:
memcached是多線程且是緩存類應用,很是適合這個模型。改進以下:
1)工做線程拿到的fd,這個fd會加到本線程的epoll_set裏面,這個fd的後續讀寫事件都由該線程處理
2)工做線程和監聽線程之間創建了管道,工做線程的管道fd也加入到工做線程的epoll_set裏面,那麼就可讓 ‘新fd到來’和‘舊fd可讀寫’ 這兩種事件都由epoll_set監聽,減小調度的複雜性
3)由於memcached的任務基本是查內存的工做,耗時短並且相對均勻,因此對負載問題不是很敏感,可使用該模型編程

---------------------

segmentfault

4、epoll 1進程(listen) + n進程(accept+epoll_wait+處理) 模型
一、表明開源產品:nginx
二、基本原理:(依據nginx的設計分析)
1)master進程監聽新鏈接的到來,並讓其中一個worker進程accept。這裏須要處理驚羣效應問題,詳見nginx的accept_mutex設計
2)worker進程accept到fd以後,把fd註冊到到本進程的epoll句柄裏面,由本進程處理這個fd的後續讀寫事件
3)worker進程根據自身負載狀況,選擇性地不去accept新fd,從而實現負載均衡
三、echo server測試:後續補充
四、優勢:
1)進程掛掉不會影響這個服務
2)和第二種模型同樣,是由worker主動實現負載均衡的,這種負載均衡方式比由master來處理更簡單
五、缺點:
1)多進程模型編程比較複雜,進程間同步沒有線程那麼簡單
2)進程的開銷比線程更多
---------------------

數組

5、epoll 1線程(listen) + n線程(accept+epoll_wait+處理) 模型
一、表明開源產品:無
二、基本原理:
1)該設計和第四種的多進程模型基本一致,每一個worker進程換成worker線程
三、echo server測試:後續補充
四、優勢:
1)多線程模型更簡單,線程同步方便
2)多線程模型線程開銷比進程更少
--------------------- 瀏覽器

 

 

6、epoll 1線程(listen+accept) + n線程(epoll_wait+處理) 模型 (200萬QPS實現echo server方案)
一、對應開源產品:無
二、基本原理:
1)1個線程監聽端口並accept新fd,把fd的監聽事件round robin地註冊到n個worker線程的epoll句柄上
2)若是worker線程是多個線程共享一個epoll句柄,那麼事件須要設置EPOLLONESHOT,避免在讀寫fd的時候,事件在其餘線程被觸發
3)worker線程epoll_wait得到讀寫事件並處理之
三、echo server測試:200萬QPS(由於資源有限,測試client和server放在同一個物理機上,實際能達到的上限應該還會更多)
四、優勢:
1)減小競爭。在第四和第五種模型中,worker須要去爭着完成accept,這裏有競爭。而在這種模型中,就消除了這種競爭
五、缺點:
1)負載均衡。這種模型的鏈接分配,又回到了由master分配的模式,依然會存在負載不均衡的問題。可讓若干個線程共享一個epoll句柄,從而把任務分配均衡到多個線程,實現全局更好的負載均衡效果
---------------------

緩存

 

7、總結和啓示
一、除了第一個模型,其餘模型都是多線程/多進程的,因此都採用了「1個master-n個worker」結構
二、若是accept由master執行,那麼master就須要執行分配fd的任務,這種設計會存在負載不均的問題可是這種狀況下,accept由誰執行不會存在競爭,性能更好
三、若是accept讓worker去執行,那麼同一個listen_fd,這個時候由哪一個worker來執行accept,便產生了競爭;產生了競爭就使得性能下降
四、對於通常的邏輯性web業務,選擇由master去accept並分配任務更合適一些。 由於大部分web業務請求耗時都較長(毫秒級別),並且請求的耗時不盡相同,因此對負載均衡要求也較高; 另一般的web業務,也不可能到單機10萬QPS的量級
五、這幾個模型的變化,無非就是三個問題的選擇:
1) 多進程,多線程,仍是單線程?
2)每一個worker本身管理事件,仍是由master統一管理?
3)accept由master仍是worker執行?
六、在系統設計中,須要根據實際狀況和需求,選擇合適的網絡事件方案
---------------------

 

https://blog.csdn.net/zmzsoftware/article/details/17356199

  

void ET( epoll_event* events, int number, int epollfd, int listenfd )//邊沿非阻塞 方式
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, true );
        }
        else if ( events[i].events & EPOLLIN )
        {//這段代碼不會被重複觸發  因此循環取出數據 確保把socket緩衝區存儲的數據讀出來
            printf( "event trigger once\n" );
            while( 1 )
            {
                memset( buf, '\0', BUFFER_SIZE );
                int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
                if( ret < 0 )
                {//對於非阻塞IO 下面成立表示數據讀取ok 此後epoll就能再一次觸發fd 上的EPOLLIN事件
                    if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                    {   //無需關閉socket   說明還有數據未接收 等待下一次處理
                        printf( "read later\n" );
                        break;
                    }
                    //返回-1 那麼壽命發生錯誤直接中止
                    close( sockfd );
                    break;
                }
                else if( ret == 0 )
                {
                    close( sockfd );// ==0 ET模式返回0 表示已經接受了全部數據了
                }
                else
                {//接收ok
                    printf( "get %d bytes of content: %s\n", ret, buf );
                }
            }
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}

 

 

 connect()函數

1.阻塞模式

客戶端調用connect()函數將激發TCP的三路握手過程,但僅在鏈接創建成功或出錯時才返回。返回的錯誤可能有如下幾種狀況:

    1>.若是TCP客戶端沒有接收到SYN分節的響應即(ACK),則返回ETIMEDOUT,阻塞模式的超時時間在75秒(4.4BSD內核)到幾分鐘之間。

    2>.若是對客戶的SYN的響應時RST,則代表該服務器主機在咱們指定的端口上沒有進程在等待與之鏈接(例如服務器進程也許沒有啓動),這稱爲硬錯,客戶一接收到RST,立刻就返回錯誤 ECONNREFUSED.

    3>.若是某客戶發出的SYN在中間的路由器上引起了一個目的地不可達ICMP錯誤,屢次嘗試發送失敗後返回錯誤號爲EHOSTUNREACH或ENETUNREACH.

附加產生RST的三種狀況,一是SYN到達某端口但此端口上沒有正在偵聽的服務器、 二是TCP想取消一個已有鏈接、  三是TCP接收了一個根本不存在的鏈接上的分節

 

 2 非阻塞connect:

當一個非阻塞的tcp套接字上調用connect時,connect將當即返回一個EINPROGRESS錯誤,不過已經發起的tcp三路握手繼續進行。咱們接着使用select檢測這個鏈接或成功或失敗的已創建條件。非阻塞connect有三個用途:

(1) 咱們能夠把三路握手疊加在其餘處理上,完成一個connect要花的RTT時間,而RTT波動很大,從局域網上的幾毫秒到幾百毫秒甚至是廣域網的幾秒。這段時間內也許有咱們想要執行的其餘工做可執行;

(2) 咱們可以使用這個技術同時創建多個鏈接;這個技術隨着web瀏覽器流行起來;

(3) 既然使用select等待鏈接創建,咱們能夠給select指定一個時間限制,使得咱們可以縮短connect的超時。

非阻塞connect細節:

(1) 儘管套接字是非阻塞的,若是鏈接到的服務器在同一個主機上,那麼當咱們調用connect時候,鏈接一般馬上創建,咱們必須處理這種情形;

(2) 源自Berkeley的實現(和posix)有關select和非阻塞connect的如下兩個原則:

--(a) 當鏈接成功創建時,描述符變爲可寫;

--(b) 當鏈接創建遇到錯誤時,描述符變爲既可讀又可寫;

 

 

關於accept函數調用的理解和整理      也能夠閱讀  unix網絡編程p362       非阻塞accept 

                                 accept以前內核有兩個隊列 要知道

.accept()函數

1.阻塞模式

          第一種狀況:阻塞模式下調用accept()函數,並且沒有新鏈接時,進程會進入睡眠狀態。  (最多見狀況 也是最好理解的一種狀況

     第二種狀況:就是,當使用IO多路複用的函數如select/EPOLL_WAIT函數等方法去檢測監聽套接字一個外來鏈接時  不少時候咱們將監聽套接字設置爲非阻塞     不少人以爲 IO多路複用函數會告訴咱們該套接字上已有鏈接就緒  那麼accept函數調用就不該該阻塞啊!  但是這裏有bug啊 !

在比較忙的服務器中,在創建三次握手以後調用accept以前可能出現客戶端斷開鏈接的狀況,再這樣的狀況下;如,三次握手以後,客戶端發送rst,而後服務器調用accept,  即  服務器在io多路複用函數返回到調用accept以前 服務器收到客戶端的RST   這是已經完成三次握手的 鏈接會被  服務器驅除出隊列   假設隊列中沒有其餘完成的鏈接了     服務器此時調用accept  可是沒有已經完成3次握手的鏈接   因而服務器阻塞了  沒法處理其餘已經就緒的鏈接了     值到有其客戶端創建一個鏈接位置

      解決辦法以下        就是下面的那樣 

 

2.非阻塞模式

非阻塞模式下調用accept()函數,並且沒有新鏈接時,將返回  EWOULDBLOCK 錯誤。

 

非阻塞模式select() + accept() 

非阻塞accept:

在比較忙的服務器中,在創建三次握手以後,調用accept以前,可能出現客戶端斷開鏈接的狀況,再這樣的狀況下;如,三次握手以後,客戶端發送rst,而後服務器調用accept。posix指出這種狀況errno設置爲CONNABORTED;

注意Berkeley實現中,沒有返回這個錯誤,而是EPROTO,同時完成三次握手的鏈接會從已完成隊列中移除;在這種狀況下,若是咱們用select監聽到有新的鏈接完成,但以後又被從完成隊列中刪除,此時若是調用阻塞accept就會產生阻塞;

解決辦法:

(1) 使用select監聽套接字是否有完成鏈接的時候,老是把這個監聽套接字設置爲非阻塞;

(2) 在後續的accept調用中忽略如下錯誤,EWOULDBLOCK(Berkeley實現,客戶停止鏈接),  ECONNABORTED(posix實現,客戶停止鏈接), EPROTO(serv4實現,客戶停止鏈接)和EINTR(若是有信號被捕獲);

 

 

 

 http://www.cnblogs.com/fighting-boy/p/9875288.html         (linux網絡編程    裏面有兩個大一點的demo  web服務器  一個防火牆)

 

     epoll     EPOLLONESHOT

https://segmentfault.com/a/1190000003063859

https://blog.csdn.net/linuxheik/article/details/73294658

https://www.cnblogs.com/charlesblc/p/5538363.html

https://blog.csdn.net/liuhengxiao/article/details/46911129

相關文章
相關標籤/搜索