文章首發於51CTO技術棧公衆號
做者 陳彩華
文章轉載交流請聯繫 caison@aliyun.com
複製代碼
隨着互聯網的發展,面對海量用戶高併發業務,傳統的阻塞式的服務端架構模式已經無能爲力,由此,本文旨在爲你們提供有用的概覽以及網絡服務模型的比較,以揭開設計和實現高性能網絡架構的神祕面紗編程
首先看看服務端處理網絡請求的典型過程:緩存
能夠看到,主要處理步驟包括:bash
設計服務端併發模型時,主要有以下兩個關鍵點:服務器
以上兩個關鍵點最終都與操做系統的I/O模型以及線程(進程)模型相關,下面詳細介紹這兩個模型網絡
介紹操做系統的I/O模型以前,先了解一下幾個概念:多線程
二者的最大區別在於被調用方在收到請求到返回結果以前的這段時間內,調用方是否一直在等待。阻塞是指調用方一直在等待並且別的事情什麼都不作。非阻塞是指調用方先去忙別的事情架構
同步處理與異步處理併發
阻塞、非阻塞和同步、異步的區別 阻塞、非阻塞和同步、異步其實針對的對象是不同的: 阻塞、非阻塞的討論對象是調用者 同步、異步的討論對象是被調用者異步
recvfrom函數 recvfrom函數(經socket接收數據),這裏把它視爲系統調用socket
一個輸入操做一般包括兩個不一樣的階段
對於一個套接字上的輸入操做,第一步一般涉及等待數據從網絡中到達。當所等待分組到達時,它被複制到內核中的某個緩衝區。第二步就是把數據從內核緩衝區複製到應用進程緩衝區
實際應用程序在系統調用完成上面2步操做時,調用方式的阻塞、非阻塞,操做系統在處理應用程序請求時處理方式的同步、異步處理的不一樣,參考**《UNIX網絡編程卷1》**,能夠分爲5種I/O模型
簡介 在阻塞式I/O模型中,應用程序在從調用recvfrom開始到它返回有數據報準備好這段時間是阻塞的,recvfrom返回成功後,應用進程開始處理數據報
比喻 一我的在釣魚,當沒魚上鉤時,就坐在岸邊一直等
優勢 程序簡單,在阻塞等待數據期間進程/線程掛起,基本不會佔用CPU資源
缺點 每一個鏈接須要獨立的進程/線程單獨處理,當併發請求量大時爲了維護程序,內存、線程切換開銷較大,這種模型在實際生產中不多使用
簡介 在非阻塞式I/O模型中,應用程序把一個套接口設置爲非阻塞就是告訴內核,當所請求的I/O操做沒法完成時,不要將進程睡眠,而是返回一個錯誤,應用程序基於I/O操做函數將不斷的輪詢數據是否已經準備好,若是沒有準備好,繼續輪詢,直到數據準備好爲止
比喻 邊釣魚邊玩手機,隔會再看看有沒有魚上鉤,有的話就迅速拉桿
優勢 不會阻塞在內核的等待數據過程,每次發起的I/O請求能夠當即返回,不用阻塞等待,實時性較好
缺點輪詢將會不斷地詢問內核,這將佔用大量的CPU時間,系統資源利用率較低,因此通常Web服務器不使用這種I/O模型
簡介 在I/O複用模型中,會用到select或poll函數或epoll函數(Linux2.6之後的內核開始支持),這兩個函數也會使進程阻塞,可是和阻塞I/O所不一樣的的,這兩個函數能夠同時阻塞多個I/O操做,並且能夠同時對多個讀操做,多個寫操做的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操做函數
比喻 放了一堆魚竿,在岸邊一直守着這堆魚竿,有魚上鉤就玩手機
優勢 能夠基於一個阻塞對象,同時在多個描述符上等待就緒,而不是使用多個線程(每一個文件描述符一個線程),這樣能夠大大節省系統資源
缺點 當鏈接數較少時效率相比多線程+阻塞I/O模型效率較低,可能延遲更大,由於單個鏈接處理須要2次系統調用,佔用時間會有增長
簡介 在信號驅動式I/O模型中,應用程序使用套接口進行信號驅動I/O,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據準備好時,進程會收到一個SIGIO信號,能夠在信號處理函數中調用I/O操做函數處理數據
比喻 魚竿上繫了個鈴鐺,當鈴鐺響,就知道魚上鉤,而後能夠專心玩手機
優勢 線程並無在等待數據時被阻塞,能夠提升資源的利用率
缺點
簡介 由POSIX規範定義,應用程序告知內核啓動某個操做,並讓內核在整個操做(包括將數據從內核拷貝到應用程序的緩衝區)完成後通知應用程序。這種模型與信號驅動模型的主要區別在於:信號驅動I/O是由內核通知應用程序什麼時候啓動一個I/O操做,而異步I/O模型是由內核通知應用程序I/O操做什麼時候完成
優勢 異步 I/O 可以充分利用 DMA 特性,讓 I/O 操做與計算重疊
缺點 要實現真正的異步 I/O,操做系統須要作大量的工做。目前 Windows 下經過 IOCP 實現了真正的異步 I/O,而在 Linux 系統下,Linux2.6才引入,目前 AIO 並不完善,所以在 Linux 下實現高併發網絡編程時都是以 IO複用模型模式爲主
從上圖中咱們能夠看出,能夠看出,越日後,阻塞越少,理論上效率也是最優。其五種I/O模型中,前四種屬於同步I/O,由於其中真正的I/O操做(recvfrom)將阻塞進程/線程,只有異步I/O模型才於POSIX定義的異步I/O相匹配
介紹完服務器如何基於I/O模型管理鏈接,獲取輸入數據,下面介紹基於進程/線程模型,服務器如何處理請求
值得說明的是,具體選擇線程仍是進程,更可能是與平臺及編程語言相關,例如C語言使用線程和進程均可以(例如Nginx使用進程,Memcached使用線程),Java語言通常使用線程(例如Netty),爲了描述方便,下面都使用線程來進程描述
特色
存在問題
針對傳統傳統阻塞I/O服務模型的2個缺點,比較常見的有以下解決方案:
I/O複用結合線程池,這就是Reactor模式基本設計思想
Reactor模式,是指經過一個或多個輸入同時傳遞給服務處理器的服務請求的事件驅動處理模式。 服務端程序處理傳入多路請求,並將它們同步分派給請求對應的處理線程,Reactor模式也叫Dispatcher模式,即I/O多了複用統一監聽事件,收到事件後分發(Dispatch給某進程),是編寫高性能網絡服務器的必備技術之一
Reactor模式中有2個關鍵組成:
Reactor Reactor在一個單獨的線程中運行,負責監聽和分發事件,分發給適當的處理程序來對IO事件作出反應。 它就像公司的電話接線員,它接聽來自客戶的電話並將線路轉移到適當的聯繫人
Handlers 處理程序執行I/O事件要完成的實際事件,相似於客戶想要與之交談的公司中的實際官員。Reactor經過調度適當的處理程序來響應I/O事件,處理程序執行非阻塞操做
根據Reactor的數量和處理資源池線程的數量不一樣,有3種典型的實現:
下面詳細介紹這3種實現
其中,select是前面I/O複用模型介紹的標準網絡編程API,能夠實現應用程序經過一個阻塞對象監聽多路鏈接請求,其餘方案示意圖相似
方案說明
優勢 模型簡單,沒有多線程、進程通訊、競爭的問題,所有都在一個線程中完成
缺點
使用場景 客戶端的數量有限,業務處理很是快速,好比Redis,業務處理的時間複雜度O(1)
方案說明
優勢 能夠充分利用多核CPU的處理能力
缺點
針對單Reactor多線程模型中,Reactor在單線程中運行,高併發場景下容易成爲性能瓶頸,可讓Reactor在多線程中運行
方案說明
優勢
這種模型在許多項目中普遍使用,包括Nginx主從Reactor多進程模型,Memcached主從多線程,Netty主從多線程模型的支持
3種模式能夠用個比喻來理解: 餐廳經常僱傭接待員負責迎接顧客,當顧客入坐後,侍應生專門爲這張桌子服務
Reactor模式具備以下的優勢:
在Reactor模式中,Reactor等待某個事件或者可應用或個操做的狀態發生(好比文件描述符可讀寫,或者是socket可讀寫),而後把這個事件傳給事先註冊的Handler(事件處理函數或者回調函數),由後者來作實際的讀寫操做,其中的讀寫操做都須要應用程序同步操做,因此Reactor是非阻塞同步網絡模型。若是把I/O操做改成異步,即交給操做系統來完成就能進一步提高性能,這就是異步網絡模型Proactor
Proactor是和異步I/O相關的,詳細方案以下:
能夠看出Proactor和Reactor的區別:Reactor是在事件發生時就通知事先註冊的事件(讀寫在應用程序線程中處理完成);Proactor是在事件發生時基於異步I/O完成讀寫操做(由內核完成),待I/O操做完成後纔回調應用程序的處理器來處理進行業務處理
理論上Proactor比Reactor效率更高,異步I/O更加充分發揮DMA(Direct Memory Access,直接內存存取)的優點,可是有以下缺點:
所以在Linux下實現高併發網絡編程都是以Reactor模型爲主
UNIX網絡編程卷1:套接字聯網API(第3版)