前言:
服務端一般須要支持高併發業務訪問,如何設計優秀的服務端網絡IO工做線程/進程模型對業務的高併發訪問需求起着相當重要的核心做用。mysql
本文總結了了不一樣場景下的多種網絡IO線程/進程模型,並給出了各類模型的優缺點及其性能優化方法,很是適合服務端開發、中間件開發、數據庫開發等開發人員借鑑。nginx
經常使用高併發網絡線程模型設計及mongodb線程 模型優化實踐(最全高併發網絡IO線程模型設計)git
1. 線程模型一. 單線程網絡IO複用模型github
1.1 說明:web
1. 全部網絡IO事件(accept事件、讀事件、寫事件)註冊到epoll事件集redis
2. 主循環中經過epoll_wait一次性獲取內核態收集到的epoll事件信息,而後輪詢執行各個事件對應的回調。sql
3. 事件註冊、epoll_wait事件獲取、事件回調執行所有由一個線程執行mongodb
1.2 該網絡線程模型缺陷數據庫
1. 全部工做都由一個線程執行,只要任一一個請求的事件回調處理阻塞,其餘請求都會阻塞。例如redis的hash結構,若是filed過多,例如一個hash key包含數百萬filed,則該Hash key過時的時候,整個redis阻塞。apache
2. 單線程工做模型,CPU會成爲瓶頸,若是QPS超過10萬,整個CPU負載會達到100%。
1.3 典型案例
1. redis緩存
1.4 主循環工做流程:
while (1) { //epoll_wait等待網絡事件,若是有網絡事件則返回,或者超時範圍 size_t numevents= epoll_wait(); //遍歷前面epoll獲取到的網絡事件,執行對應事件回調 for (j = 0; j < numevents; j++) { if(讀事件) { //讀數據 readData() //解析 parseData() //讀事件處理、讀到數據後的業務邏輯處理 requestDeal() } else if(寫事件) { //寫事件處理,寫數據邏輯處理 writeEentDeal() } else { //異常事件處理 errorDeal() } } }
說明:後續多線程/進程模型中,每一個線程/進程的主流程和該while()流程一致。
2. 線程模型二. 單listener+固定worker線程
1. listener線程負責接受全部的客戶端連接
2. listener線程每接收到一個新的客戶端連接產生一個新的fd,而後經過分發器發送給對應的工做線程(hash方式)
3. 工做線程獲取到對應的新連接fd後,後續該連接上的全部網絡IO讀寫都由該線程處理
4. 假設有32個連接,則32個連接創建成功後,每一個線程平均處理4個連接上的讀寫、報文處理、業務邏輯處理
1.5 redis源碼分析及異步網絡IO複用精簡版demo
因爲以前工做須要,須要對redis內核作二次優化開發,所以對整個redis代碼作了部分代碼註釋,同時把redis的網絡模塊獨立出來作成了簡單demo,該demo對理解epoll網絡事件處理及Io複用實現會有幫助,代碼比較簡短,能夠參考以下地址:
2.1 該網絡線程模型缺陷
1. 進行accept處理的listener線程只有一個,在瞬間高併發場景容易成爲瓶頸
2. 一個線程經過IO複用方式處理多個連接fd的數據讀寫、報文解析及後續業務邏輯處理,這個過程會有嚴重的排隊現象,例如某個連接的報文接收解析完畢後的內部處理時間過長,則其餘連接的請求就會阻塞排隊
2.2 典型案例
memcache緩存,適用於內部處理比較快的緩存場景、代理中間場景。memcache源碼實現中文分析能夠詳見: memcache源碼實現分析
3. 線程模型三. 固定worker線程模型
該模型原型圖以下:
說明:
1. Linux kernel 3.9開始支持reuseport功能,內核協議棧每獲取到一個新連接自動均衡分發給用戶態worker線程。
2. 該模型解決了模型一的listener單點瓶頸問題
3.1 該網絡線程模型缺陷
reuseport支持後,內核經過負載均衡的方式分發不一樣新連接到多個用戶態worker進程/線程,每一個進程/線程經過IO複用方式處理多個客戶端新連接fd的數據讀寫、報文解析、解析後的業務邏輯處理。每一個工做進程/線程同時處理多個連接的請求,若是某個連接的報文接收解析完畢後的內部處理時間過長,則其餘連接的請求就會阻塞排隊。
該模型雖然解決了listener單點瓶頸問題,可是工做線程內部的排隊問題沒有解決。
不過,Nginx做爲七層轉發代理,因爲都是內存處理,因此內部處理時間比較短,因此適用於該模型。
3.2 典型案例
1. nginx(nginx用的是進程,模型原理同樣),該模型適用於內部業務邏輯簡單的場景,如nginx代理等
2. reuseport支持性能提高過程能夠參考我另外一篇分享: http://www.javashuo.com/article/p-ofgalmlt-eh.html
Nginx多進程高併發、低時延、高可靠機制在緩存(redis、memcache)twemproxy代理中的應用
4. 線程模型四. 一個連接一個線程模型
該線程模型圖以下圖:
說明:
1. listener線程負責接受全部的客戶端連接
2. listener線程每接收到一個新的客戶端連接就建立一個線程,該線程只負責處理該連接上的數據讀寫、報文解析、業務邏輯處理。
4.1 該網絡線程模型缺陷:
1. 一個連接建立一個線程,若是10萬個連接,那麼就須要10萬個線程,線程數太多,系統負責、內存消耗也會不少
2. 當連接關閉的時候,線程也須要銷燬,頻繁的線程建立和消耗進一步增長系統負載
4.2 典型案例:
1. mysql默認方式、mongodb同步線程模型配置,適用於請求處理比較耗時的場景,如數據庫服務
2. Apache web服務器,該模型限制了apache性能,nginx優點會更加明顯
5. 線程模型五. 單listener+動態worker線程(單隊列)
該線程模型圖以下圖所示:
說明:
1. listener線程接收到一個新連接fd後,把該fd交由線程池處理,後續該連接的全部讀寫、報文解析、業務處理都由線程池中多個線程處理。
2. 該模型把一次請求轉換爲多個任務(網絡數據讀寫、報文解析、報文解析後的業務邏輯處理)入隊到全局隊列,線程池中的線程從隊列中獲取任務執行。
3. 同一個請求訪問被拆分爲多個任務,一次請求可能由多個線程處理。
4. 當任務太多,系統壓力大的時候,線程池中線程數動態增長
5. 當任務減小,系統壓力減小的時候,線程池中線程數動態減小
5.1 工做線程運行時間相關的幾個統計:
T1:調用底層asio庫接收一個完整mongodb報文的時間
T2:接收到報文後的後續全部處理(含報文解析、認證、引擎層處理、發送數據給客戶端等)
T3: 線程等待數據的時間(例如:長時間沒有流量,則如今等待讀取數據)
5.2單個工做線程如何判斷本身處於」空閒」狀態:
線程運行總時間=T1 + T2 +T3,其中T3是無用等待時間。若是T3的無用等待時間佔比很大,則說明線程比較空閒。工做線程每一次循環處理後判斷有效時間佔比,若是小於指定閥值,則本身直接exit退出銷燬
5.3 如何判斷線程池中工做線程「太忙」:
控制線程專門用於判斷線程池中工做線程的壓力狀況,以此來決定是否在線程池中建立新的工做線程來提高性能。
控制線程每過必定時間循環檢查線程池中的線程壓力狀態,實現原理就是簡單的實時記錄線程池中的線程當前運行狀況,爲如下兩類計數:總線程數_threadsRunning、當前正在運行task任務的線程數_threadsInUse。若是_threadsRunning=_threadsRunning,說明全部工做線程當前都在處理task任務,線程池中線程壓力大,這時候控制線程就開始增長線程池中線程數。該模型詳細源碼實現過程更多細節詳見:https://my.oschina.net/u/4087916/blog/4295038
5.4 該網絡線程模型缺陷:
1. 線程池獲取任務執行,有鎖競爭,這裏就會成爲系統瓶頸
5.5 典型案例:
5.5 典型案例:
mongodb動態adaptive線程模型,適用於請求處理比較耗時的場景,如數據庫服務
該模型詳細源碼優化分析實現過程參考:
http://www.javashuo.com/article/p-obucbjcr-nu.html
Mongodb網絡傳輸處理源碼實現及性能調優-體驗內核性能極致設計
6. 線程模型六. 單listener+動態worker線程(多隊列)
該線程模型圖以下:
說明:
把一個全局隊列拆分爲多個隊列,任務入隊的時候按照hash散列到各自的隊列,工做線程獲取獲取任務的時候,同理經過hash的方式去對應的隊列獲取任務,經過這種方式減小鎖競爭,同時提高總體性能。
6.1 典型案例:
OPPO自研mongodb內核多隊列adaptive線程模型優化,性能有很好的提高,適用於請求處理比較耗時的場景,如數據庫服務。該模型詳細源碼優化分析實現過程參考:https://my.oschina.net/u/4087916/blog/4295038