Netty 是什麼? 這個問題其實百度上一搜一堆。 這是官方話的描述:Netty 是一個基於NIO的客戶、服務器端編程框架,使用Netty 能夠確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶、服務端應用。Netty至關於簡化和流線化了網絡應用的編程開發過程,例如:基於TCP和UDP的socket服務開發。html
其實沒那麼複雜,用通俗易懂的話來說:java
NETTY 是一個框架,web
這個框架作了什麼事情了,作了一件 Rpc 底層通信對接的事情,編程
Rpc 底層通順採用什麼方式呢,就是採用基於SOCKET 的TCP/UDP (NIO+Reactor ) 長鏈接模式進行通信(重點)服務器
那麼NETTY 框架在對接系統級別的通信層以後 又以編程接口的模式提供給App層。網絡
NETTY 是基於NIO 的客戶端,服務端編程框架。正式由於如此,使得NETTY 在處理高併發量的時候,有很大的線程優點。那麼咱們接下來要講講什麼是NIO 模型?併發
對應NIO(Non-Blocking I/O) 模型的 對立模型就是BIO(Blocking I/O) 模型。除此以外還有一種AIO 模型(不作講解)。 咱們先了解下BIO 模型。框架
從上圖中能夠看到:socket
當用戶線程發出IO請求以後,內核會去查看數據是否就緒,若是沒有就緒就會等待數據就緒,而用戶線程就會處於阻塞狀態,用戶線程交出CPU。當數據就緒以後,內核會將數據拷貝到用戶線程,並返回結果給用戶線程,用戶線程才解除block狀態。高併發
典型的阻塞IO模型的例子爲:
data = socket.read();
若是數據沒有就緒,就會一直阻塞在read方法。而且新soket 鏈接過來,都會產生新的socket 線程管理,以下圖所示。
從上面圖中咱們能夠看出在BIO 模型中,能夠清楚的瞭解到 :一個套接字須要使用一個線程處理,它的過程就是創建鏈接、讀數據、寫數據,然而在這些過程當中都有可能會發生阻塞。這就是爲何咱們首先會接觸到這種方式的緣由,由於它簡單,一個線程只處理一個Socket,但若是是Server端,在併發鏈接時就須要更多的線程才能完成工做。咱們熟悉的 .NET FRAMEWORK WEB 應用的 寄宿主 IIS 就使用此種模式。
下面這張圖描述了BIO 在系統內核中的場景圖: 客人來了,就讓一個新的服務員服務,只等到客人走了,這個服務員的工做任務也就完成了。 說明這個餐廳的服務質量很好,可是效率很低,服務員的利用率跟飽和度低,佔用資源浪費,而且成本增長。
NIO 分爲兩種,一種是傳統的NIO 由於傳統的NIO 爲早期的版本,後來系統內核增長了 NIO+Reactor
咱們在來看一張圖早期版本NIO 內核SOCKET 管理模型:
從上圖瞭解到:
早期的NIO 的原理:當用戶線程發起一個read操做後,並不須要等待,而是立刻就獲得了一個結果。若是結果是一個error時,它就知道數據尚未準備好,因而它能夠再次發送read操 做。一旦內核中的數據準備好了,而且又再次收到了用戶線程的請求,那麼它立刻就將數據拷貝到了用戶線程,而後返回。因此事實上,在非阻塞IO模型中,用戶線程須要不斷地詢問內核數據是否就緒,也就說非阻塞IO不會交出CPU,而會一直佔用CPU。
典型的傳統非阻塞IO模型通常以下:
while
(
true
){
data = socket.read();
if
(data!= error){
處理數據
break
;
}
}
可是對於早期傳統非阻塞IO(NIO)就有一個很是嚴重的問題,在while循環中須要不斷地去詢問內核數據是否就緒,這樣會致使CPU佔用率很是高,所以通常狀況下很 少使用while循環這種方式來讀取數據。
爲了解決這個問題,後期的NIO 版本增長 引入了Reactor 模式,也就是 (線程+SELECT)。也就是如今RPC 服務框架基於底層內核用的最多的NIO+Reactor 模型。
一樣咱們瞭解下NIO多路複用模型的系統內核SOCKET維護流程:
(1)當用戶進程調用了select (這個select 很是關鍵,下面會講到),那麼整個進程會被block;
(2)而同時,kernel會「監視」全部select負責的socket;
(3)當任何一個socket中的數據準備好了,select就會返回;
(4)這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程(空間)
(多路複用IO 模型) 是基於事件驅動的思想,採用了Reactor模式,相對於BIO,NIO一個明顯的優點就是不須要爲每個Socket套接字分配一個線程,而是在一個線程中能夠處理多個Socket套接字相關的工做。感興趣的能夠去了解一下Reactor模式,在這裏給一個連接 -> 詳解Reactor
從上面圖中能夠看出:
多路複用IO模式,經過一個線程就能夠管理多個socket,只有當socket真正有讀寫事件發生纔會佔用資源來進行實際的讀寫操做。所以,多路複用IO比較適合鏈接數比較多的狀況。
在多路複用IO模型中,會有一個線程不斷去輪詢多個socket的狀態,只有當socket真正有讀寫事件時,才真正調用實際的IO讀寫操做。由於在多路複用IO模型中,只須要使用一個線程就能夠管理多個socket,系統不須要創建新的進程或者線程,也沒必要維護這些線程和進程,而且只有在真正有socket讀寫事件進行時,纔會使用IO資源,因此它大大減小了資源佔用
另外多路複用IO爲什麼比非阻塞IO模型的效率高是由於在非阻塞IO中,不斷地詢問socket狀態時經過用戶線程去進行的,而在多路複用IO中,輪詢每一個socket狀態是內核在進行的,這個效率要比用戶線程要高的多。
不過要注意的是,多路複用IO模型是經過輪詢的方式來檢測是否有事件到達,而且對到達的事件逐一進行響應。所以對於多路複用IO模型來講,一旦事件響應體很大,那麼就會致使後續的事件遲遲得不處處理,而且會影響新的事件輪詢。
下面這張是對NIO+Reactor 的系統內核的場景描述圖: 多個客人來了,有一個服務員先來接待,而且同步複製全部的客人的信息給相關的處理部門,部門根據本身感興趣的來處理的相對應的的消息。
總結:從場景圖中能夠看出/或者從衆看各個系列文章: NIO多路複用模型是基於 一個線程接待了多個客戶端套接字的管理,那麼反過來講,在這個線程中產生了一個管理者,那麼這個管理者稱之爲 selector 那麼這個 (線程+selector ) 是屬於netty 層呢仍是屬於系統級別層呢? 咱們以上所述的,都是屬於系統自己級別,也就是說 (線程+selector ) 仍是屬於系統級別層的,那麼netty 作了些什麼呢? NETTY 它是基於系統的NIO +Reactor模型構建的,而且實現了 與操做系統給的NIO+Reactor 模型中的 selector 對象 進行對接,將它封裝NETTY框架之中以後,提供了一個易於操做的使用模式和接口,用戶使用起來更加方便便捷。
那麼NETTY 與多路複用模型IO 中的 SELECTOR 對象又有什麼關係呢? 咱們先了解下具體的SELECTOR 管理了哪些內容? 咱們先來看一張圖:
上圖中很清楚的瞭解到: 多路複用模型中的Selector 維護了 每一個socket 鏈接的channel ,併爲這個channel產了相對於的key , 放入 MAP 中,從程序角度講,系統的多路複用器(SELECTOR) 中維護了 map<string ,channel> 對應的集合關係。 從NETTY角度來說,NETTY 關心的是 系統多路複用IO 模型的 SELECTOR ,由於經過它,就能獲取到讀寫消息事件,而且新的鏈接的註冊事件,還有鏈接斷開事件等,而且經過SELECTOR 找到對應的 socket channel ,就能夠對具體的鏈接進行讀寫操做了。
咱們使用通用的應用程序或者類庫來實現互相通信,好比,咱們常用一個 HTTP 客戶端庫來從 web 服務器上獲取信息,或者經過 web 服務來執行一個遠程的調用。
然而,有時候一個通用的協議或他的實現並無很好的知足需求。好比咱們沒法使用一個通用的 HTTP 服務器來處理大文件、電子郵件以及近實時消息,好比金融信息和多人遊戲數據。咱們須要一個高度優化的協議來處理一些特殊的場景。例如你可能想實現一個優化了的 Ajax 的聊天應用、媒體流傳輸或者是大文件傳輸器,你甚至能夠本身設計和實現一個全新的協議來準確地實現你的需求。
另外一個不可避免的狀況是當你不得不處理遺留的專有協議來確保與舊系統的互操做性。在這種狀況下,重要的是咱們如何才能快速實現協議而不犧牲應用的穩定性和性能。
可是:NETTY 也有本身的缺點:
NIO 的 Reactor模式在IO讀寫數據時仍是在同一個線程中實現的,即便使用多個Reactor機制的狀況下,那些共享一個Reactor的Channel若是出現一個長時間的數據讀寫,會影響這個Reactor中其餘Channel的相應時間,好比在大文件傳輸時,IO操做就會影響其餘Client的相應時間,於是對這種操做,使用傳統的Thread-Per-Connection或許是一個更好的選擇,或則此時使用Proactor模式。
所以在開發技術選型的時候,不能盲目的選擇,仍是配合業務場景,根據實際狀況來選擇.