Netty源碼分析系列之Reactor線程模型

掃描下方二維碼或者微信搜索公衆號菜鳥飛呀飛,即可關注微信公衆號,閱讀更多Spring源碼分析Java併發編程文章。

微信公衆號

對於網絡編程而言,一方面需要保證基本功能的正確性,另一方面還需要保證程序的高性能。而網絡程序高性能的主題之一就是網絡IO,不同的IO模型,對程序的性能影響是非常明顯的。

BIO線程模型

對於傳統的網絡框架而言,服務端通常採用的是BIO的通信模型。對於BIO通信模型,它通常使用一個專門的線程來負責接收網絡連接,然後再爲每一個連接單獨創建一個線程來進行數據的讀寫、編解碼、業務處理等操作。其IO模型可以用如下圖表示。

BIO線程模型

顯然,BIO的通信模型的優缺點很明顯。優點就是程序的代碼簡單,複雜度低,開發人員容易上手。缺點是,對於每一個客戶端連接,服務端都需要爲其創建一個線程來處理數據,當併發較高時,就會創建很多線程,這對服務端而言簡直就是災難。因爲創建太多線程後,系統資源會佔用較高,而且CPU在多個線程之間進行切換時,頻繁的切換上下文,會嚴重影響服務的性能。

既然線程創建太多了,那我們是不是可以使用線程池來解決問題呢?當一個新連接創建好以後,我們不再爲其單獨創建一個連接,而是將其交由線程池來處理,那這種方案是否可行呢?

答案是不行。爲什麼呢?線程池在這裏只能解決線程無限增長的問題,但是在進行讀寫數據時,由於read操作和write操作都是阻塞的,在這段期間,線程會掛起,什麼事情也幹不了。當多個客戶端來連接時,由於線程池中的線程,都阻塞在前面連接的讀寫數據操作上了,此時新來的連接,只能等待,所以對於BIO而言,使用線程池最終還是無法解決高併發的問題。

那麼怎麼辦呢?這個時候NIO出現了,NIO是非阻塞IO。對於NIO而言,服務端和客戶端之間的讀寫數據,不再是阻塞的了。基於NIO實現的網絡框架,它們底層的IO模型通常是基於Reactor模型來實現的。Reactor模型又可以分爲三種:單線程模型、多線程模型、主從多線程模型。

Reactor單線程模型

Reactor單線程模型中,只有一個線程。這個線程既負責客戶端的接入,還負責數據的讀寫、編解碼、業務邏輯處理等工作。IO模型示意圖如下。

Reactor單線程模型

Reactor單線程使用的是異步非阻塞IO,所有的讀寫操作都是非阻塞的,因此理論上一個線程可以完成所有的工作。當一個新連接來接入時,通過Acceptor類可以進行TCP的連接,當TCP連接創建完成後,可以通過Dispatcher類將對應的請求數據(即ByteBuffer)派發到指定的Handler上進行編解碼操作,最後再通過該線程將數據發送給客戶端。
對於一個併發量較小的場景,可以使用單線程模型來處理。但是當併發較高時,單線程就無法滿足了。理由如下:

  1. 一個NIO線程顯然無法支撐多個連接的接入,即便NIO線程的CPU負荷達到100%,也無法滿足海量數據的編解碼、讀取和發送。
  2. 當CPU負載較高後,處理就會變慢,這樣就會造成大量的客戶端出現連接超時。當客戶端發現連接超時後,又會嘗試進行重新請求,這樣更加會加重NIO線程所在的CPU的負載,最終就會導致系統負載高,處理慢,成爲系統的性能瓶頸。
  3. 可靠性低。一旦NIO線程因爲處理數據中出現異常,或者進入到死循環,那將導致整個系統不可用。

Reactor多線程模型

爲了解決Reactor單線程模型的問題,Reactor多線程模型出現了。在Reactor多線程模型中,由一個NIO線程來負責客戶端的接入,連接創建完成後,再由一組線程來處理數據的讀寫、編解碼、業務處理等操作。示意圖如下。

Reactor多線程

在Reactor多線程模型中,由一個單獨的NIO線程來充當Acceptor的角色,它負責監聽服務端的端口,並接收客戶端的連接。然後由一個線程池來處理數據的讀寫、編解碼等操作。線程池可以採用Java中的線程池,它有一個任務隊列和多個NIO線程,因此一個NIO線程可以同時處理多個連接,但是一個連接只屬於一個NIO線程。
Reactor多線程完美的解決了單線程存在的問題,它也幾乎能滿足大部分應用場景。但是由於它只使用一個NIO線程來負責處理新連接的接入,因此在特殊場景下,例如新連接的創建,服務端需要進行安全認證等操作,由於認證可能會耗時較長,這個時候再使用一個線程來負責處理百萬連接,顯然無法滿足要求,這最終會成爲系統的性能瓶頸。

Reactor主從多線程模型

Reactor主從多線程模型則解決了多線程模型的缺點,主從多線程模型中由一組NIO線程來負責處理新連接的接入,另外一組NIO線程來處理IO讀寫、編解碼、業務邏輯處理等操作。因此它是兩個線程池,負責新連接接入的線程池稱之爲主線程池,負責數據讀寫、編解碼操作的線程池稱之爲從線程池。其示意圖如下:

Reactor主從多線程

當一個客戶端來連接服務端時,主線程池會從線程池中選擇出一個NIO線程,來充當Acceptor的角色,負責新連接的接入。當連接創建完成後,再將其通過Dispatcher派發到主線程池,由主線程來進行安全認證等操作,當安全認證等操作完成後,會將這個新連接綁定到從線程池的一個NIO線程上,後續則由這個NIO線程來進行數據的讀寫、編解碼等操作。

Netty對Reactor三種線程模型的支持

Netty作爲一款高性能的網絡框架,它底層的網絡IO模型採用了Reactor線程模型。在Netty中,它能很方便的在Reactor三種IO模型中進行切換。下面就來看看它是如何進行切換的。

  1. 在Netty中使用Reactor單線程模型

Netty對Reactor單線程模型的支持

  1. 在Netty中使用Reactor多線程模型

Netty對Reactor多線程模型的支持

  1. 在Netty中使用Reactor主從多線程模型

Netty對Reactor主從多線程模型的支持

總結

  • 本文主要介紹了BIO的IO模型,以及Reactor的三種IO模型,最後通過代碼,簡單演示了在Netty如何使用Reactor三種線程模型。關於BIO、NIO更詳細的介紹,以及BIO、NIO和Netty的代碼示例,可以參考這篇文章:如何從BIO演進到NIO,再到Netty

參考

  • 本文參考了李林鋒所編寫的《Netty權威指南》、《Netty進階之路-跟着案例學Netty》、《分佈式服務框架原理與實踐》。
  • 文中的圖片來源於極客時間《Netty源碼剖析與實戰》

微信公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章