高併發服務器IO模型

  • 高併發服務器 IO 模型
  • 一、 什麼是 IO?
  • 二、 IO 的 5 種模型
  • 三、Linux 併發網絡編程模型
  • 四、select、epoll、kqueue、IOCP
  • 更多c/c++ Linux服務器高階知識、電子書籍、視頻等等請後臺私信【架構】獲取

    知識點有C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK等等。

    高併發服務器IO模型

    高併發服務器IO模型

    一、什麼是 IO?

    IO (Input/Output,輸入/輸出)即數據的讀取(接收)或寫入(發送)操作,通常用戶進程中的一個完整 IO 分為兩階段:用戶進程空間內核空間、內核空間設備空間(磁盤、網絡等)。IO 有內存 IO、網絡 IO 和磁盤 IO 三種,通常我們說的 IO 指的是後兩者。

    二、 IO 的 5 種模型

    5 種模型可以分為同步 IO 和異步 IO 兩大類,1~4 為同步 IO 模型,5 位異步 IO 模型

    阻塞 IO 模型

    阻塞 IO 模型 , 進程發起 IO 系統調用後,進程被阻塞,轉到內核空間處理,整個 IO 處理完畢後返回進程。操作成功則進程獲取到數據。資源不可用時,IO 請求一直阻塞,直到反饋結果(有數據或超時)。

    典型應用:阻塞 socket、Java BIO;

    特點:

    進程阻塞掛起不消耗 CPU 資源,及時響應每個操作;

    實現難度低、開發應用較容易;

    適用併發量小的網絡應用開發;

    不適用併發量大的應用:因為一個請求 IO 會阻塞進程所有操作。

    非阻塞 IO 模型

    非阻塞 IO 模型,進程發起 IO 系統調用後,如果內核緩衝區沒有數據,需要到 IO 設備中讀取,進程返回一個錯誤而不會被阻塞;進程發起 IO 系統調用後,如果內核緩衝區有數據,內核就會把數據返回進程。

    對於上面的阻塞 IO 模型來說,內核數據沒準備好需要進程阻塞的時候,就返回一個錯誤,以使得進程不被阻塞。

    典型應用:socket 是非阻塞的方式(設置為 NONBLOCK)

    特點:

    進程輪詢(重複)調用,消耗 CPU 的資源;

    實現難度低、開發應用相對阻塞 IO 模式較難;

    適用併發量較小、且不需要及時響應的網絡應用開發;

    IO 複用模型

    IO 複用模型,多個的進程的 IO 可以註冊到一個複用器(select)上,然後用一個進程調用該 select, select 會監聽所有註冊進來的 IO;如果 select 沒有監聽的 IO 在內核緩衝區都沒有可讀數據,select 調用進程會被阻塞;而當任一 IO 在內核緩衝區中有可數據時,select 調用就會返回;而後 select 調用進程可以自己或通知另外的進程(註冊進程)來再次發起讀取 IO,讀取內核中準備好的數據。

    典型應用:select、poll、epoll 三種方案,JAVA NIO 特點:

    專一進程解決多個進程 IO 的阻塞問題,性能好;Reactor 模式;

    實現、開發應用難度較大;

    適用高併發服務應用開發:一個進程(線程)響應多個請求;

    Linux 中 IO 複用的實現方式主要有 select、poll 和 epoll:

    Select:註冊 IO、阻塞掃描,監聽的 IO 最大連接數不能多於 FD_SIZE;

    Poll:原理和 Select 相似,沒有數量限制,但 IO 數量大掃描線性性能下降;

    Epoll :事件驅動不阻塞,mmap 實現內核與用戶空間的消息傳遞,數量很大,Linux2.6 後內核支持;(Windows 有 IOCP、FreeBSD 下有 Kqueue)

    信號驅動的 IO 模型

    信號驅動的 IO 模型,當進程發起一個 IO 操作,會向內核註冊一個信號處理函數,然後進程返回不阻塞;當內核數據就緒時會發送一個信號給進程,進程便在信號處理函數中調用 IO 讀取數據。

    特點:回調機制,實現、開發應用難度大;

    異步 IO 模型

    異步 IO 模型,當進程發起一個 IO 操作,進程返回(不阻塞),但也不能返回果結;內核把整個 IO 處理完後,會通知進程結果。如果 IO 操作成功則進程直接獲取到數據。

    典型應用:JAVA7 AIO、高性能服務器應用

    不阻塞,數據一步到位;Proactor 模式;

    需要操作系統的底層支持,LINUX 2.5 版本內核首現,2.6 版本產品的內核標準特性;

    實現、開發應用難度大;

    非常適合高性能高併發應用;

    五種 IO 模型比較

    高併發服務器IO模型

    IOCompare.png

    三、Linux 併發網絡編程模型

    Apache 模型

    Apache 模型,簡稱 PPC(Process Per Connection):為每個連接分配一個進程。主機分配給每個連接的時間和空間上代價較大,並且隨著連接的增多,大量進程間切換開銷也增長了。很難應對大量的客戶併發連接。

    TPC 模型

    TPC 模型(Thread Per Connection ):每個連接一個線程。和 PCC 類似。

    select 模型

    select 模型:I/O 多路複用技術。

    • 每個連接對應一個描述。select 模型受限於 FD_SETSIZE 即進程最大打開的描述符數 linux2.6.35 為 1024,實際上 linux 每個進程所能打開描數字的個數僅受限於內存大小,然而在設計 select 的系統調用時,卻是參考 FD_SETSIZE 的值。可通過重新編譯內核更改此值,但不能根治此問題,對於百萬級的用戶連接請求 即便增加相應 進程數, 仍顯得杯水車薪呀。
    • select 每次都會掃描一個文件描述符的集合,這個集合的大小是作為 select 第一個參數傳入的值。但是每個進程所能打開文件描述符若是增加了 ,掃描的效率也將減小。
    • 內核到用戶空間,採用內存複製傳遞文件描述上發生的信息。

    poll 模型

    poll 模型:I/O 多路複用技術。poll 模型將不會受限於 FD_SETSIZE,因為內核所掃描的文件 描述符集合的大小是由用戶指定的,即 poll 的第二個參數。但仍有掃描效率和內存拷貝問題。

    pselect 模型

    pselect 模型:I/O 多路複用技術。同 select。

    epoll 模型

    • 無文件描述字大小限制僅與內存大小相關
    • epoll 返回時已經明確的知道哪個 socket fd 發生了什麼事件,不用像 select 那樣再一個個比對。
    • 內核到用戶空間採用共享內存方式,傳遞消息。

    四、select、epoll、kqueue、IOCP

    select()和 poll()

    在 linux 沒有實現 epoll 事件驅動機制之前,我們一般選擇用 select 或者 poll 等 IO 多路複用的方法來實現併發服務程序。在大數據、高併發、集群等一些名詞唱得火熱之年代,select 和 poll 的用武之地越來越有限,風頭已經被 epoll 佔盡。

    select 的缺點:

    單個進程能夠監視的文件描述符的數量存在最大限制,通常是 1024,當然可以更改數量,但由於 select 採用輪詢的方式掃描文件描述符,文件描述符數量越多,性能越差;(在 linux 內核頭文件中,有這樣的定義:#define __FD_SETSIZE 1024)

    內核 / 用戶空間內存拷貝問題,select 需要複製大量的句柄數據結構,產生巨大的開銷;

    select 返回的是含有整個句柄的數組,應用程序需要遍歷整個數組才能發現哪些句柄發生了事件;

    select 的觸發方式是水平觸發,應用程序如果沒有完成對一個已經就緒的文件描述符進行 IO 操作,那麼之後每次 select 調用還是會將這些文件描述符通知進程。

    相比 select 模型,poll 使用鏈表保存文件描述符,因此沒有了監視文件數量的限制,但其他三個缺點依然存在。

    拿 select 模型為例,假設我們的服務器需要支持 100 萬的併發連接,則在__FD_SETSIZE 為 1024 的情況下,則我們至少需要開闢 1k 個進程才能實現 100 萬的併發連接。除了進程間上下文切換的時間消耗外,從內核/用戶空間大量的無腦內存拷貝、數組輪詢等,是系統難以承受的。因此,基於 select 模型的服務器程序,要達到 10 萬級別的併發訪問,是一個很難完成的任務。

    epoll

    epoll 是 Linux 內核為處理大批量文件描述符而作了改進的 poll,是 Linux 下多路複用 IO 接口 select/poll 的增強版本,它能顯著提高程序在大量併發連接中只有少量活躍的情況下的系統 CPU 利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核 IO 事件異步喚醒而加入 Ready 隊列的描述符集合就行了。epoll 除了提供 select/poll 那種 IO 事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存 IO 狀態,減少 epoll_wait/epoll_pwait 的調用,提高應用程序效率。

    epoll 有兩種工作方式

    ET:Edge Triggered,邊緣觸發。僅當狀態發生變化時才會通知,epoll_wait 返回。換句話,就是對於一個事件,只通知一次。且只支持非阻塞的 socket。

    LT:Level Triggered,電平觸發(默認工作方式)。類似 select/poll,只要還有沒有處理的事件就會一直通知,以 LT 方式調用 epoll 接口的時候,它就相當於一個速度比較快的 poll.支持阻塞和不阻塞的 socket。

    kqueue

    kqueue 與 epoll 非常相似,最初是 2000 年 Jonathan Lemon 在 FreeBSD 系統上開發的一個高性能的事件通知接口。註冊一批 socket 描述符到 kqueue 以後,當其中的描述符狀態發生變化時,kqueue 將一次性通知應用程序哪些描述符可讀、可寫或出錯了。

    IOCP

    IOCP 全稱 I/O Completion Port,中文譯為 I/O 完成端口。IOCP 是一個異步 I/O 的 API,它可以高效地將 I/O 事件通知給應用程序。一個套接字[socket]與一個完成端口關聯了起來,然後就可繼續進行正常的 Winsock 操作了。然而,當一個事件發生的時候,此完成端口就將被操作系統加入一個隊列中。然後應用程序可以對核心層進行查詢以得到此完成端口。

    誠然,Windows 的 IOCP 非常出色,目前很少有支持 asynchronous I/O 的系統,但是由於其系統本身的侷限性,大型服務器還是在 UNIX 下。而且正如上面所述,kqueue/epoll 與 IOCP 相比,就是多了一層從內核 copy 數據到應用層的阻塞,從而不能算作 asynchronous I/O 類。但是,這層小小的阻塞無足輕重,kqueue 與 epoll 已經做得很優秀了。

    select、epoll、kqueue、IOCP 對比

    只有 IOCP 是 asynchronous I/O,其他機制或多或少都會有一點阻塞。

    select 低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善

    epoll, kqueue、select 是 Reacor 模式,IOCP 是 Proactor 模式。

    java nio 包是 select 模型。。



    分享到:


    相關文章: