图灵学院Java架构师五期笔记
緣起
日前在看netty的工作原理,對netty的線程模型很是不能理解,查閱了諸多資料,終于有了一些眉目。特此記錄,以備查閱。
閱讀對象
netty中的NIO編程模型是基于java Nio的封裝,所以需要讀者對java NIO有一定的了解,篇幅所限,本文不會對NIO再做詳述,有需要的讀者可以查看??JAVA BIO,NIO,AIO詳解(附代碼實現)以及Netty的簡介??
Netty的NIO線程模型
說netty的線程模型之前,先說傳統NIO的使用方式中的關鍵代碼
可以看到是當前線程獲得了客戶端的連接之后再重新把channel注冊到selector上,同時把事件注冊為讀,這樣就可以開始讀消息了。也就是說獲取tcp連接和讀取消息都是在同一個線程里面處理的。那么這種方式有什么問題呢?對于一些小流量應用場景,可以使用單線程模型。但是對于高負載、大并發的應用場景卻不合適,主要原因如下:
一個 NIO 線程同時處理成百上千的鏈路,性能上無法支撐,即便 NIO 線程的 CPU 負荷達到 100%,也無法滿足海量消息的編碼、解碼、讀取和發送;
當 NIO 線程負載過重之后,處理速度將變慢,這會導致大量客戶端連接超時,超時之后往往會進行重發,這更加重了 NIO 線程的負載,最終會導致大量消息積壓和處理超時,成為系統的性能瓶頸;
可靠性問題:一旦 NIO 線程意外跑飛,或者進入死循環,會導致整個系統通信模塊不可用,不能接收和處理外部消息,造成節點故障。
那么你可能會想,既然一個線程不行,那我在讀取消息的時候采用線程池不就可以了嗎?的確是可行的,事實上,在Netty中也確實是這么做的。
netty的一個啟動程序如下
可以看到開頭new了兩個EventLoopGroup,EventLoopGroup可以暫時理解為一個線程組。其中bossGroup負責處理客戶端的 TCP 連接請求,如果系統只有一個服務端端口需要監聽,則建議 bossGroup 線程組線程數設置為 1,workerGroup 是真正負責 I/O 讀寫操作的線程組
先看一張netty的執行流程圖
圖中可以看到boosGroup在接收到請求后會重新注冊到workerGroup上,也就是對應了前面說的bossGroup負責處理客戶端的 TCP 連接請求,而workerGroup是真正負責 I/O 讀寫操作的線程組。就像軟件開發里面的boss負責分配任務,而worker即程序員負責寫代碼。那么bossGroup是如何處理tcp的連接請求同時把他注冊到workerGroup上的呢?
bossGroup是如何分配任務的
workerGroup中的每一個線程,都有一個多路復用器 Selector,bossGroup每接收到一個客戶端連接,就會從workerGroup選擇一個線程然后把channel注冊到它的Selector上。
偽代碼實現
boss中的代碼
可以看到,bossGroup接收到了新客戶端的請求后,就會調用一個算法,從多個worker中選取一個worker,然后調用worker的注冊方法,把這個新客戶端的channel注冊上去。我們再看workerGroup中的注冊方法實現
可以看到,就是把新的channel注冊到自己的Selector上,注冊好后就會觸發workerGroup的堵塞代碼塊,這樣這個workerGroup中的這個線程就會開始讀取數據,下面看看worker線程讀取數據的偽代碼實現(其實就是普通的NIO中讀取數據的方式)
總結:bossGroup負責處理客戶端的 TCP 連接請求,bossGroup每接收到一個客戶端連接,就會從workerGroup選擇一個線程然后把channel注冊到它的Selector上,這樣的話請求接收和請求處理就通過不同的線程分開了,這也是netty高效的原因之一。當然Netty高效的原因絕不僅僅是由于優秀的線程模型的設計,與Netty的編碼協議,virtual buffer,以及Zero-Copy(零拷貝)也息息相關 。
總結
以上是生活随笔為你收集整理的图灵学院Java架构师五期笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows的进程创建和映像装入
- 下一篇: 记一次CentOS7因Redis配置不当