Socket Server 的 N 種併發模型彙總
原創宣告 作者: 劉丹冰 Aceld,微信公眾號同名
本文主要介紹常見的 Server 的併發模型,這些模型與程式語言本身無關,有的程式語言可能在語法上直接透明瞭模型本質,所以開發者沒必要一定要基於模型去編寫,只是需要知道和了解併發模型的構成和特點即可。
那麼在瞭解併發模型之前,我們需要兩個必備的前置知識:
- socket 網路程式設計
- 多路 IO 複用機制
- 多執行緒/多程式等併發程式設計理論
模型一、單執行緒 Accept(無 IO 複用)
(1) 模型結構圖
(2) 模型分析
① 主執行緒main thread
執行阻塞 Accept,每次客戶端 Connect 連結過來,main thread
中 accept 響應並建立連線
② 建立連結成功,得到Connfd1
套接字後, 依然在main thread
序列處理套接字讀寫,並處理業務。
③ 在②處理業務中,如果有新客戶端Connect
過來,Server
無響應,直到當前套接字全部業務處理完畢。
④ 當前客戶端處理完後,完畢連結,處理下一個客戶端請求。
(3) 優缺點
優點:
- socket 程式設計流程清晰且簡單,適合學習使用,瞭解 socket 基本程式設計流程。
缺點:
該模型並非併發模型,是序列的伺服器,同一時刻,監聽並響應最大的網路請求量為
1
。 即併發量為1
。僅適合學習基本 socket 程式設計,不適合任何伺服器 Server 構建。
模型二、單執行緒 Accept+ 多執行緒讀寫業務(無 IO 複用)
(1) 模型結構圖
(2) 模型分析
① 主執行緒main thread
執行阻塞 Accept,每次客戶端 Connect 連結過來,main thread
中 accept 響應並建立連線
② 建立連結成功,得到Connfd1
套接字後,建立一個新執行緒thread1
用來處理客戶端的讀寫業務。main thead
依然回到Accept
阻塞等待新客戶端。
③ thread1
通過套接字Connfd1
與客戶端進行通訊讀寫。
④ server 在②處理業務中,如果有新客戶端Connect
過來,main thread
中Accept
依然響應並建立連線,重複②過程。
(3) 優缺點
優點:
- 基於
模型一:單執行緒Accept(無IO複用)
支援了併發的特性。 - 使用靈活,一個客戶端對應一個執行緒單獨處理,
server
處理業務內聚程度高,客戶端無論如何寫,服務端均會有一個執行緒做資源響應。
缺點:
- 隨著客戶端的數量增多,需要開闢的執行緒也增加,客戶端與 server 執行緒數量
1:1
正比關係,一次對於高併發場景,執行緒數量收到硬體上限瓶頸。 - 對於長連結,客戶端一旦無業務讀寫,只要不關閉,server 的對應執行緒依然需要保持連線 (心跳、健康監測等機制),佔用連線資源和執行緒開銷資源浪費。
- 僅適合客戶端數量不大,並且數量可控的場景使用。
僅適合學習基本 socket 程式設計,不適合任何伺服器 Server 構建。
模型三、單執行緒多路 IO 複用
(1) 模型結構圖
(2) 模型分析
① 主執行緒main thread
建立listenFd
之後,採用多路 I/O 複用機制 (如:select、epoll) 進行 IO 狀態阻塞監控。有Client1
客戶端Connect
請求,I/O 複用機制檢測到ListenFd
觸發讀事件,則進行Accept
建立連線,並將新生成的connFd1
加入到監聽I/O集合
中。
② Client1
再次進行正常讀寫業務請求,main thread
的多路I/O複用機制
阻塞返回,會觸該套接字的讀/寫事件等。
③ 對於Client1
的讀寫業務,Server 依然在main thread
執行流程提繼續執行,此時如果有新的客戶端Connect
連結請求過來,Server 將沒有即時響應。
④ 等到 Server 處理完一個連線的Read+Write
操作,繼續回到多路I/O複用機制
阻塞,其他連結過來重複 ②、③流程。
(3) 優缺點
優點:
- 單流程解決了可以同時監聽多個客戶端讀寫狀態的模型,不需要
1:1
與客戶端的執行緒數量關係。 - 多路 I/O 複用阻塞,非忙詢狀態,不浪費 CPU 資源, CPU 利用率較高。
缺點:
- 雖然可以監聽多個客戶端的讀寫狀態,但是同一時間內,只能處理一個客戶端的讀寫操作,實際上讀寫的業務併發為 1。
- 多客戶端訪問 Server,業務為序列執行,大量請求會有排隊延遲現象,如圖中⑤所示,當
Client3
佔據main thread
流程時,Client1,Client2
流程卡在IO複用
等待下次監聽觸發事件。
模型四、單執行緒多路 IO 複用 + 多執行緒讀寫業務 (業務工作池)
(1) 模型結構圖
(2) 模型分析
① 主執行緒main thread
建立listenFd
之後,採用多路 I/O 複用機制 (如:select、epoll) 進行 IO 狀態阻塞監控。有Client1
客戶端Connect
請求,I/O 複用機制檢測到ListenFd
觸發讀事件,則進行Accept
建立連線,並將新生成的connFd1
加入到監聽I/O集合
中。
② 當connFd1
有可讀訊息,觸發讀事件,並且進行讀寫訊息
③ main thread
按照固定的協議讀取訊息,並且交給worker pool
工作執行緒池, 工作執行緒池在 server 啟動之前就已經開啟固定數量的thread
,裡面的執行緒只處理訊息業務,不進行套接字讀寫操作。
④ 工作池處理完業務,觸發connFd1
寫事件,將回執客戶端的訊息通過main thead
寫給對方。
(3) 優缺點
優點:
- 對於
模型三
, 將業務處理部分,通過工作池分離出來,減少多客戶端訪問 Server,業務為序列執行,大量請求會有排隊延遲時間。 - 實際上讀寫的業務併發為 1,但是業務流程併發為 worker pool 執行緒數量,加快了業務處理並行效率。
缺點:
- 讀寫依然為
main thread
單獨處理,最高讀寫並行通道依然為 1. - 雖然多個 worker 執行緒處理業務,但是最後返回給客戶端,依舊需要排隊,因為出口還是
main thread
的Read + Write
模型五、單執行緒 IO 複用 + 多執行緒 IO 複用 (連結執行緒池)
(1) 模型結構圖
(2) 模型分析
① Server 在啟動監聽之前,開闢固定數量 (N) 的執行緒,用Thead Pool
執行緒池管理
② 主執行緒main thread
建立listenFd
之後,採用多路 I/O 複用機制 (如:select、epoll) 進行 IO 狀態阻塞監控。有Client1
客戶端Connect
請求,I/O 複用機制檢測到ListenFd
觸發讀事件,則進行Accept
建立連線,並將新生成的connFd1
分發給Thread Pool
中的某個執行緒進行監聽。
③ Thread Pool
中的每個thread
都啟動多路I/O複用機制(select、epoll)
,用來監聽main thread
建立成功並且分發下來的 socket 套接字。
④ 如圖, thread
監聽ConnFd1、ConnFd2
, thread2
監聽ConnFd3
,thread3
監聽ConnFd4
. 當對應的ConnFd
有讀寫事件,對應的執行緒處理該套接字的讀寫及業務。
(3) 優缺點
優點:
- 將
main thread
的單流程讀寫,分散到多執行緒完成,這樣增加了同一時刻的讀寫並行通道,並行通道數量N
,N
為執行緒池Thread
數量。 - server 同時監聽的
ConnFd套接字
數量幾乎成倍增大,之前的全部監控數量取決於main thread
的多路I/O複用機制
的最大限制(select 預設為 1024, epoll 預設與記憶體大小相關,約 3~6w 不等),所以理論單點 Server 最高響應併發數量為N*(3~6W)
(N
為執行緒池Thread
數量,建議與 CPU 核心成比例 1:1)。 - 如果良好的執行緒池數量和 CPU 核心數適配,那麼可以嘗試 CPU 核心與 Thread 進行繫結,從而降低 CPU 的切換頻率,提升每個
Thread
處理合理業務的效率,降低 CPU 切換成本開銷。
缺點:
- 雖然監聽的併發數量提升,但是最高讀寫並行通道依然為
N
,而且多個身處同一個 Thread 的客戶端,會出現讀寫延遲現象,實際上每個Thread
的模型特徵與模型三:單執行緒多路IO複用
一致。
模型五 (程式版)、單程式多路 I/O 複用 + 多程式多路 I/O 複用 (程式池)
(1) 模型結構圖
(2) 模型分析
與五、單執行緒IO複用+多執行緒IO複用(連結執行緒池)
無大差異。
不同處
- 程式和執行緒的記憶體佈局不同導致,
main process
(主程式) 不再進行Accept
操作,而是將Accept
過程分散到各個子程式(process)
中. - 程式的特性,資源獨立,所以
main process
如果 Accept 成功的 fd,其他程式無法共享資源,所以需要各子程式自行 Accept 建立連結 -
main process
只是監聽ListenFd
狀態,一旦觸發讀事件 (有新連線請求). 通過一些 IPC(程式間通訊:如訊號、共享記憶體、管道) 等, 讓各自子程式Process
競爭Accept
完成連結建立,並各自監聽。
(3) 優缺點
與五、單執行緒IO複用+多執行緒IO複用(連結執行緒池)
無大差異。
不同處:
多程式記憶體資源空間佔用稍微大一些
多程式模型安全穩定型較強,這也是因為各自程式互不干擾的特點導致。
模型六、單執行緒多路 I/O 複用 + 多執行緒多路 I/O 複用 + 多執行緒
(1) 模型結構圖
(2) 模型分析
① Server 在啟動監聽之前,開闢固定數量 (N) 的執行緒,用Thead Pool
執行緒池管理
② 主執行緒main thread
建立listenFd
之後,採用多路 I/O 複用機制 (如:select、epoll) 進行 IO 狀態阻塞監控。有Client1
客戶端Connect
請求,I/O 複用機制檢測到ListenFd
觸發讀事件,則進行Accept
建立連線,並將新生成的connFd1
分發給Thread Pool
中的某個執行緒進行監聽。
③ Thread Pool
中的每個thread
都啟動多路I/O複用機制(select、epoll)
,用來監聽main thread
建立成功並且分發下來的 socket 套接字。一旦其中某個被監聽的客戶端套接字觸發I/O讀寫事件
,那麼,會立刻開闢一個新執行緒來處理I/O讀寫
業務。
④ 但某個讀寫執行緒完成當前讀寫業務,如果當前套接字沒有被關閉,那麼將當前客戶端套接字如:ConnFd3
重新加回執行緒池的監控執行緒中,同時自身執行緒自我銷燬。
(3) 優缺點
優點:
在
模型五、單執行緒IO複用+多執行緒IO複用(連結執行緒池)
基礎上,除了能夠保證同時響應的最高併發數
,又能解決讀寫並行通道
侷限的問題。同一時刻的讀寫並行通道,達到
最大化極限
,一個客戶端可以對應一個單獨執行流程處理讀寫業務,讀寫並行通道與客戶端數量1:1
關係。
缺點:
- 該模型過於理想化,因為要求 CPU 核心數量足夠大。
- 如果硬體 CPU 數量可數 (目前的硬體情況),那麼該模型將造成大量的 CPU 切換成本浪費。因為為了保證讀寫並行通道與客戶端
1:1
的關係,那麼 Server 需要開闢的Thread
數量就與客戶端一致,那麼執行緒池中做多路I/O複用
的監聽執行緒池繫結 CPU 數量將變得毫無意義。 - 如果每個臨時的讀寫
Thread
都能夠繫結一個單獨的 CPU,那麼此模型將是最優模型。但是目前 CPU 的數量無法與客戶端的數量達到一個量級,目前甚至差的不是幾個量級的事。
總結
綜上,我們整理了 7 中 Server 的伺服器處理結構模型,每個模型都有各自的特點和優勢,那麼對於多少應付高併發和高 CPU 利用率的模型,目前多數採用的是模型五 (或模型五程式版,如 Nginx 就是類似模型五程式版的改版)。
至於併發模型並非設計的約複雜越好,也不是執行緒開闢的越多越好,我們要考慮硬體的利用與和切換成本的開銷。模型六設計就極為複雜,執行緒較多,但以當今的硬體能力無法支撐,反倒導致該模型效能極差。所以對於不同的業務場景也要選擇適合的模型構建,並不是一定固定就要使用某個來應用。
### 關於作者:
mail: danbing.at@gmail.com github: https://github.com/aceld 原創書籍 gitbook: http://legacy.gitbook.com/@aceld
創作不易, 共同學習進步, 歡迎關注作者, 回覆"zinx"有好禮
文章推薦
開源軟體作品
(原創開源) Zinx-基於 Golang 輕量級伺服器併發框架-完整版 (附教程視訊)
(原創開源) Lars-基於 C++ 負載均衡遠端排程系統-完整版
精選文章
典藏版-Golang 三色標記、混合寫屏障 GC 模式圖文全分析
最常用的除錯 golang 的 bug 以及效能問題的實踐方法?
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- Socket Server的N種併發模型彙總Server模型
- 合併JavaScript陣列的N種方法JavaScript陣列
- 我的2017年文章彙總——Java併發篇Java
- Microsoft Sql Server 命令彙總ROSSQLServer
- 每隔N行生成一個彙總行(總結)
- 併發-1-併發模型模型
- SQL Server 最佳化彙總SQLServer
- 資料庫系統的併發控制的兩種實現模型資料庫模型
- epoll+socket實現 socket併發 linux伺服器Linux伺服器
- Golang非CSP併發模型外的其他並行方法總結Golang模型並行
- Ubuntu各種錯誤彙總Ubuntu
- 資料探勘-預測模型彙總模型
- 【高併發】兩種非同步模型與深度解析Future介面非同步模型
- 人工智慧大模型之開源大語言模型彙總(國內外開源專案模型彙總)人工智慧大模型
- 併發模型比較模型
- Python 併發模型Python模型
- 完善的Socket Server程式 (轉)Server
- AI 模型常見的評價指標彙總AI模型指標
- 提高SQL SERVER併發能力SQLServer
- 開發框架彙總框架
- 聊聊 ab 和 jmeter 的併發模型JMeter模型
- Rust語言神奇的併發模型Rust模型
- ArchLinux各種問題彙總Linux
- 使用socket+gevent實現協程併發
- 學習 Go併發模型Go模型
- Spring中的9種設計模式彙總Spring設計模式
- Oracle資料庫中的多種SCN彙總Oracle資料庫
- socket 模型簡介模型
- python中socket+threading的自定義併發Pythonthread
- Golang面向併發的記憶體模型Golang記憶體模型
- Java併發中的記憶體模型Java記憶體模型
- Golang協程併發的流水線模型Golang模型
- Mac開發工具彙總Mac
- JavaScriptMobile開發框架彙總JavaScript框架
- Matlab各種隨機數彙總Matlab隨機
- 關於SQL Server事務日誌的問題彙總SQLServer
- 併發程式背後的故事以及併發當中的記憶體模型記憶體模型
- 併發程式設計---JMM模型程式設計模型