伺服器程式設計入門(6)高效能伺服器程式框架
阿新 • • 發佈:2019-01-30
問題聚焦:
核心章節。
伺服器一般分為如下三個主要模組:I/O處理單元(四種I/O模型,兩種高效事件處理模組),邏輯單元(兩種高效併發模式,有效狀態機)和儲存單元(不討論)。
伺服器模型
C/S模型
結構:
特點: 邏輯簡單。 工作流程:
I/O複用技術:select,同時監聽多個客戶請求。 優點:適合資源相對集中的場合。 缺點:當訪問量過大,可能所有客戶都將得到很慢的相應。 P2P模型 結構:兩種結構
結構b比結構a增加了發現伺服器,用於主機之間的互相發現,儘快找到自己需要的資源。 特點: 摒棄了伺服器為中心的格局,讓網路上所有主機處於對等的地位。 每臺機器在消耗服務的同時也給別人提供服務 缺點: 當用戶之間傳輸的請求過多時,網路的負載將加重
伺服器程式設計框架
基本框架:
模組說明:
IO模型
阻塞IO
socket在建立的時候是阻塞的。
阻塞模型和非阻塞模型:
阻塞IO:阻塞的檔案描述符,系統呼叫可能因為無法立即完成而被作業系統掛起。
例如:客戶端connect發起連線,伺服器相應之前的這段時間,connect呼叫將被掛起,直到確認報文段到達將之喚起。
可能被阻塞的系統呼叫包括accept,send,recv和connect
非阻塞IO: 非阻塞的檔案描述符,總是立即返回,不管時間是否發生。
如果事件沒有立即發生,這些系統呼叫返回-1,這是,我們就要確認是延遲還是出錯,確認方式是卻分報錯資訊errno。
對accept.send和recv而言,事件未發生時errno通常被設定成EAGAIN(再來一次)或者EWOULDBLOCK(期望阻塞);對connect而言,errno則被設定成EINPROGRESS(在處理中)。
注意:通常情況下,非阻塞IO要和其他IO通知機制一起使用才能提高程式的效率。
IO複用
常用:IO通知機制
描述:應用程式通過IO複用函式向核心註冊一組事件,核心通過IO複用函式把其中就緒的事件通知應用程式。
IO複用函式 :select、poll和epoll_wait,後面的章節會討論這些函式。
注意:IO複用函式本身是阻塞的,它們能提高程式效率的原因在於它們具有同時監聽多個IO事件的能力。
SIGIO訊號
作用:報告IO事件
描述:我們可以為一個目標檔案描述符指定宿主程序,那麼指定的宿主程序將捕獲到SIGIO訊號,這樣,當目標檔案描述符上有事件發生時,SIGIO訊號的訊號處理函式將被出發,我們也就可以在該訊號處理函式中對目標檔案描述符執行非阻塞IO操作了。
非同步IO模型
上面討論的三種模型都屬於同步IO模型
同步IO模型和非同步IO模型的區別
同步:IO的讀寫操作發生在IO事件之後,由應用程式(使用者程式碼)來完成。
非同步:非同步IO的讀寫操作總是立即返回的,不論IO事件是否被阻塞,因為真正的讀寫操作被核心接管,即核心來執行IO操作,具體表現為資料在核心緩衝區和使用者緩衝區之間的移動。
可以認為,同步IO嚮應用程式通知IO就緒事件,非同步IO嚮應用程式通知IO完成事件(可能並沒有真正的完成)
IO模型對比如下:
兩種高效的事件處理模式
伺服器程式通常需要處理三類事件:IO事件,訊號和定時事件。後面會一次介紹。
這一節先介紹兩種高效的事件處理模式:Reactor(同步IO模型)和Proactor(非同步IO模型)。
Reactor模式
描述:
Proactor模式 描述:將所有IO操作都交給主執行緒和核心來處理,工作執行緒僅僅負責業務邏輯。更符合之前提到的伺服器程式設計框架。 流程:使用非同步IO模型(以aio_read和aio_write為例)實現Proactor模式的工作流程是:
同步IO方式模擬Proactor模式 原理:主執行緒執行資料讀寫操作,讀寫完成之後,主執行緒向工作執行緒通知這一“完成事件”,工作執行緒處理後續邏輯。 流程:
兩種高效的併發模式 併發模式適合:IO密集型任務 方式:多程序和多執行緒(後面討論) 描述:併發模式是指IO處理單元和多個邏輯單元之間協調完成任務的方法。 伺服器主要有兩種併發程式設計模式:
同步執行緒:按照同步方式執行的執行緒稱為同步執行緒 非同步執行緒:按照非同步方式執行的執行緒稱為非同步執行緒 半同步/半非同步模式:同步執行緒用於處理客戶邏輯,非同步執行緒用於處理IO事件。 半同步/半反應堆模式 結合考慮兩種事件處理模式(Reactor和Proactor)和幾種IO模型(阻塞IO,IO複用,SIGIO訊號,非同步IO),則半同步/半非同步就存在多種變體 半同步/半反應堆模式就是其中的一種。 如下圖所示:
特點:
說明: 控制代碼集:表示IO資源,在Linux下通常就是一個檔案描述符。 執行緒集:所有工作執行緒的管理者。負責各執行緒之間的同步和新領導者執行緒的推選。 事件處理器及其子類: 用回撥函式的方式處理某事件發生時對應的業務。 工作流程:
To be continued:後面的專題將介紹有限狀態機和提高伺服器效能的一些建議 小結: 這篇主要介紹了伺服器方面的核心框架和設計模式,是這個系列的核心。後續的篇幅都是實現這些模型的技術相關的介紹。 伺服器程式設計的路很深,但技術方面也是穩定的,不像前端技術那樣技術革新很頻繁和有趣。 參考資料: 《Linux高效能伺服器程式設計》
特點: 邏輯簡單。 工作流程:
I/O複用技術:select,同時監聽多個客戶請求。 優點:適合資源相對集中的場合。 缺點:當訪問量過大,可能所有客戶都將得到很慢的相應。 P2P模型 結構:兩種結構
結構b比結構a增加了發現伺服器,用於主機之間的互相發現,儘快找到自己需要的資源。 特點: 摒棄了伺服器為中心的格局,讓網路上所有主機處於對等的地位。 每臺機器在消耗服務的同時也給別人提供服務 缺點:
模組說明:
模組 | 單個伺服器程式 | 伺服器叢集 |
IO處理單元 | 處理客戶連線,讀寫網路資料 | 作為接入伺服器,實現負載均衡 |
邏輯單元 | 業務程序或執行緒 | 邏輯伺服器 |
網路儲存單元 | 本地資料庫、檔案或快取 | 資料庫伺服器 |
請求佇列 | 各單元之間的通訊方式 | 各伺服器之間的永久TCP連線 |
IO模型 | 讀寫操作和阻塞階段 |
阻塞IO | 程式阻塞於讀寫函式 |
IO複用 | 程式阻塞於IO複用系統呼叫,但可同時監聽多個IO事件,對IO本身的讀寫操作是非阻塞的 |
SIGIO訊號 | 訊號觸發讀寫就緒事件,使用者程式執行讀寫操作,程式沒有阻塞階段 |
非同步IO | 核心執行讀寫操作並觸發讀寫完成事件,程式沒有阻塞階段 |
- 它要求主執行緒只負責監聽檔案描述上是否有事件發生,有的話就立即將該事件通知工作執行緒。
- 除此之外,主執行緒不做任何其他實質性的工作。
- 工作執行緒負責讀寫資料,接受新的連線,以及處理客戶請求。
- 主執行緒往epoll核心事件表中註冊socket上的讀就緒事件
- 主執行緒呼叫epoll_wait等待socket上有資料可讀
- 當socket上有資料可讀時,epoll_wait通知主執行緒,主執行緒則將socket可讀事件放入請求佇列
- 睡眠在請求佇列上的某個工作執行緒被喚醒,它從socket讀取資料,並處理客戶請求,然後往epoll核心事件表中註冊該socket上的寫就緒事件
- 當socket可寫時,epoll_wait通知主執行緒,主執行緒將socket可寫事件放入請求佇列
- 睡眠在請求佇列上的某個工作執行緒被喚醒,它往socket上寫入伺服器處理客戶請求的結果。
Proactor模式 描述:將所有IO操作都交給主執行緒和核心來處理,工作執行緒僅僅負責業務邏輯。更符合之前提到的伺服器程式設計框架。 流程:使用非同步IO模型(以aio_read和aio_write為例)實現Proactor模式的工作流程是:
- 主執行緒呼叫aio_read函式向核心註冊socket上的讀寫完成事件,並告訴核心使用者讀緩衝區的位置,以及讀操作完成後如何通知應用程式
- 主執行緒繼續處理其他邏輯
- 當socket上的資料被讀入使用者緩衝區後,核心將嚮應用程式傳送一個 訊號,以通知應用程式資料可用
- 應用程式預先定義好的訊號處理函式選擇一個工作執行緒來處理客戶請求。工作執行緒處理完客戶請求之後,呼叫aio_write函式想核心註冊socket的寫完成事件,並啊公訴核心使用者寫緩衝區的位置,以及寫操作完成時如何通知應用程式。
- 主執行緒繼續處理其他邏輯
- 當用戶緩衝區的資料被寫入socket之後,核心將嚮應用程式傳送一個訊號,以通知應用程式資料已經發送完畢。
- 應用程式預先定義好的訊號處理函式選擇一個工作執行緒來做善後處理,比如決定是否關閉socket
同步IO方式模擬Proactor模式 原理:主執行緒執行資料讀寫操作,讀寫完成之後,主執行緒向工作執行緒通知這一“完成事件”,工作執行緒處理後續邏輯。 流程:
- 主執行緒往epoll核心事件表中註冊socket上的讀就緒事件
- 主執行緒呼叫epoll_wait等待socket上有資料可讀
- 當socket上有資料可讀時,epoll_wait通知主執行緒。主執行緒從socket迴圈讀取資料,直到沒有更多資料可讀,然後將讀取到的資料封裝成一個請求物件並插入請求佇列
- 睡眠在請求佇列上的某個工作執行緒被喚醒,它獲得請求物件並處理客戶請求,然後往epoll核心事件表中註冊socket上的寫就緒事件
- 主執行緒呼叫epoll_wait等待socket可寫
- 當socket可寫時,epoll_wait通知主執行緒。主執行緒往socket上寫入伺服器處理客戶請求的結果。
兩種高效的併發模式 併發模式適合:IO密集型任務 方式:多程序和多執行緒(後面討論) 描述:併發模式是指IO處理單元和多個邏輯單元之間協調完成任務的方法。 伺服器主要有兩種併發程式設計模式:
- 半同步/半非同步模式
- 領導者/追隨者模式
同步執行緒:按照同步方式執行的執行緒稱為同步執行緒 非同步執行緒:按照非同步方式執行的執行緒稱為非同步執行緒 半同步/半非同步模式:同步執行緒用於處理客戶邏輯,非同步執行緒用於處理IO事件。 半同步/半反應堆模式 結合考慮兩種事件處理模式(Reactor和Proactor)和幾種IO模型(阻塞IO,IO複用,SIGIO訊號,非同步IO),則半同步/半非同步就存在多種變體 半同步/半反應堆模式就是其中的一種。 如下圖所示:
特點:
- 非同步執行緒只有一個,由主執行緒來充當,負責監聽所有socket上的事件。
- 如果有新的連線請求,主執行緒就接受之,以得到新的連線socket
- 在epoll核心事件表中註冊該socket上的讀寫事件
- 如果連線socket上有讀寫事件發生,即有新的客戶請求到來或有資料要傳送到客戶端,主執行緒就將該連線socket插入請求佇列。
- 所有工作執行緒都睡眠在請求佇列上,當有任務到來時,它們將通過競爭獲得任務的接管權。
說明: 控制代碼集:表示IO資源,在Linux下通常就是一個檔案描述符。 執行緒集:所有工作執行緒的管理者。負責各執行緒之間的同步和新領導者執行緒的推選。 事件處理器及其子類: 用回撥函式的方式處理某事件發生時對應的業務。 工作流程:
To be continued:後面的專題將介紹有限狀態機和提高伺服器效能的一些建議 小結: 這篇主要介紹了伺服器方面的核心框架和設計模式,是這個系列的核心。後續的篇幅都是實現這些模型的技術相關的介紹。 伺服器程式設計的路很深,但技術方面也是穩定的,不像前端技術那樣技術革新很頻繁和有趣。 參考資料: 《Linux高效能伺服器程式設計》