Netty
是一個高性能、異步事件驅動的NIO
框架,它提供了對TCP
、UDP
和文件傳輸的支持。做爲當前最流行的NIO
框架,Netty
在互聯網領域、大數據分佈式計算領域、遊戲行業、通訊行業等得到了普遍的應用,一些業界著名的開源組件也是基於Netty
的NIO
框架構建。java
Netty
利用 Java
高級網絡的能力,隱藏其背後的複雜性而提供一個易於使用的 API
構建一個客戶端/服務端,其具備高併發、傳輸快、封裝好等特色。編程
高併發 Netty
是一款基於NIO
(Nonblocking I/O
,非阻塞IO
)開發的網絡通訊框架,對比於BIO
(Blocking I/O
,阻塞IO
),它的併發性能獲得了很大提升 。數組
傳輸快 Netty
的傳輸快其實也是依賴了NIO
的一個特性——零拷貝。緩存
封裝好 Netty封裝了NIO操做的不少細節,提供易於使用的API,還有心跳、重連機制、拆包粘包方案等特性,使開發者能可以快速高效的構建一個穩健的高併發應用。安全
爲何要用 Netty ?
JDK
原生 NIO
程序的問題服務器
JDK
原生也有一套網絡應用程序 API
,可是存在一系列問題,主要以下:網絡
NIO
的類庫和API
繁雜,使用麻煩。你須要熟練掌握Selector
、ServerSocketChannel
、SocketChannel
、ByteBuffer
等。- 須要具有其餘的額外技能作鋪墊。例如熟悉
Java
多線程編程,由於NIO
編程涉及到Reactor
模式,你必須對多線程和網路編程很是熟悉,才能編寫出高質量的NIO
程序。 - 可靠性能力補齊,開發工做量和難度都很是大。例如客戶端面臨斷連重連、網絡閃斷、半包讀寫、失敗緩存、網絡擁塞和異常碼流的處理等等。
NIO
編程的特色是功能開發相對容易,可是可靠性能力補齊工做量和難度都很是大。 JDK NIO
的Bug
。例如臭名昭著的Epoll Bug
,它會致使Selector
空輪詢,最終致使CPU 100%
。 官方聲稱在JDK 1.6
版本的update 18
修復了該問題,可是直到JDK 1.7
版本該問題仍舊存在,只不過該Bug
發生機率下降了一些而已,它並無被根本解決。
Netty
的特色多線程
Netty
對 JDK
自帶的 NIO
的 API
進行封裝,解決上述問題,主要特色有:架構
- 設計優雅,適用於各類傳輸類型的統一
API
阻塞和非阻塞Socket
;基於靈活且可擴展的事件模型,能夠清晰地分離關注點;高度可定製的線程模型 - 單線程,一個或多個線程池;真正的無鏈接數據報套接字支持(自3.1
起)。 - 使用方便,詳細記錄的
Javadoc
,用戶指南和示例;沒有其餘依賴項,JDK 5
(Netty 3.x
)或6
(Netty 4.x
)就足夠了。 - 高性能,吞吐量更高,延遲更低;減小資源消耗;最小化沒必要要的內存複製。 安全,完整的
SSL/TLS
和StartTLS
支持。 - 社區活躍,不斷更新,社區活躍,版本迭代週期短,發現的
Bug
能夠被及時修復,同時,更多的新功能會被加入。
Netty 內部執行流程
服務端:
併發
-
建立
ServerBootStrap
實例 -
設置並綁定
Reactor
線程池:EventLoopGroup
,EventLoop
就是處理全部註冊到本線程的Selector
上面的Channel
-
設置並綁定服務端的
Channel
-
建立處理網絡事件的
ChannelPipeline
和handler
,網絡時間以流的形式在其中流轉,handler
完成多數的功能定製:好比編解碼SSl
安全認證 -
綁定並啓動監聽端口
-
當輪訓到準備就緒的
channel
後,由Reactor
線程:NioEventLoop
執行pipline
中的方法,最終調度並執行channelHandler
客戶端:
Netty 架構設計
主要功能特性以下圖:
Netty
功能特性以下:
- 傳輸服務,支持
BIO
和NIO
。 - 容器集成,支持
OSGI
、JBossMC
、Spring
、Guice
容器。 - 協議支持,
HTTP
、Protobuf
、二進制、文本、WebSocket
等一系列常見協議都支持。還支持經過實行編碼解碼邏輯來實現自定義協議。 Core
核心,可擴展事件模型、通用通訊API
、支持零拷貝的ByteBuf
緩衝對象。
模塊組件
Bootstrap
、ServerBootstrap
Bootstrap
意思是引導,一個 Netty
應用一般由一個 Bootstrap
開始,主要做用是配置整個 Netty
程序,串聯各個組件,Netty
中 Bootstrap
類是客戶端程序的啓動引導類,ServerBootstrap
是服務端啓動引導類。
Future
、ChannelFuture
正如前面介紹,在 Netty
中全部的 IO
操做都是異步的,不能馬上得知消息是否被正確處理。
可是能夠過一會等它執行完成或者直接註冊一個監聽,具體的實現就是經過 Future
和 ChannelFutures
,它們能夠註冊一個監聽,當操做執行成功或失敗時監聽會自動觸發註冊的監聽事件。
Channel
Netty
網絡通訊的組件,可以用於執行網絡 I/O
操做。 Channel
爲用戶提供:
- 當前網絡鏈接的通道的狀態(例如是否打開?是否已鏈接?)
- 網絡鏈接的配置參數 (例如接收緩衝區大小)
- 提供異步的網絡
I/O
操做(如創建鏈接,讀寫,綁定端口),異步調用意味着任何I/O
調用都將當即返回,而且不保證在調用結束時所請求的I/O
操做已完成。調用當即返回一個ChannelFuture
實例,經過註冊監聽器到ChannelFuture
上,能夠在I/O
操做成功、失敗或取消時回調通知調用方。 - 支持關聯
I/O
操做與對應的處理程序。
不一樣協議、不一樣的阻塞類型的鏈接都有不一樣的 Channel
類型與之對應。下面是一些經常使用的 Channel
類型:
NioSocketChannel
,異步的客戶端TCP Socket
鏈接。NioServerSocketChannel
,異步的服務器端TCP Socket
鏈接。NioDatagramChannel
,異步的UDP
鏈接。NioSctpChannel
,異步的客戶端Sctp
鏈接。NioSctpServerChannel
,異步的Sctp
服務器端鏈接,這些通道涵蓋了UDP
和TCP
網絡IO
以及文件IO
。
Selector
Netty
基於 Selector
對象實現 I/O
多路複用,經過 Selector
一個線程能夠監聽多個鏈接的 Channel
事件。
當向一個 Selector
中註冊 Channel
後,Selector
內部的機制就能夠自動不斷地查詢(Select
) 這些註冊的 Channel
是否有已就緒的 I/O
事件(例如可讀,可寫,網絡鏈接完成等),這樣程序就能夠很簡單地使用一個線程高效地管理多個 Channel
。
NioEventLoop
NioEventLoop
中維護了一個線程和任務隊列,支持異步提交執行任務,線程啓動時會調用 NioEventLoop
的 run
方法,執行 I/O
任務和非 I/O
任務:
I/O
任務,即selectionKey
中ready
的事件,如accept
、connect
、read
、write
等,由processSelectedKeys
方法觸發。- 非
IO
任務,添加到taskQueue
中的任務,如register0
、bind0
等任務,由runAllTasks
方法觸發。 兩種任務的執行時間比由變量ioRatio
控制,默認爲50
,則表示容許非IO
任務執行的時間與IO
任務的執行時間相等。
NioEventLoopGroup
NioEventLoopGroup
,主要管理 eventLoop
的生命週期,能夠理解爲一個線程池,內部維護了一組線程,每一個線程(NioEventLoop
)負責處理多個 Channel
上的事件,而一個 Channel
只對應於一個線程。
ChannelHandler
ChannelHandler
是一個接口,處理 I/O
事件或攔截 I/O
操做,並將其轉發到其 ChannelPipeline
(業務處理鏈)中的下一個處理程序。
ChannelHandler
自己並無提供不少方法,由於這個接口有許多的方法須要實現,方便使用期間,能夠繼承它的子類:
-
ChannelInboundHandler
用於處理入站I/O
事件。 -
ChannelOutboundHandler
用於處理出站I/O
操做。 或者使用如下適配器類: -
ChannelInboundHandlerAdapter
用於處理入站I/O
事件。 -
ChannelOutboundHandlerAdapter
用於處理出站I/O
操做。 -
ChannelDuplexHandler
用於處理入站和出站事件。 -
ChannelHandlerContext
保存Channel
相關的全部上下文信息,同時關聯一個ChannelHandler
對象。
ChannelPipline
保存 ChannelHandler
的 List
,用於處理或攔截 Channel
的入站事件和出站操做。
ChannelPipeline
實現了一種高級形式的攔截過濾器模式,使用戶能夠徹底控制事件的處理方式,以及 Channel
中各個的 ChannelHandler
如何相互交互。
Netty 高性能設計
Netty
做爲異步事件驅動的網絡,高性能之處主要來自於其 I/O
模型和線程處理模型,前者決定如何收發數據,後者決定如何處理數據。
I/O 模型
用什麼樣的通道將數據發送給對方,BIO
、NIO
或者 AIO
,I/O
模型在很大程度上決定了框架的性能。
阻塞 I/O
傳統阻塞型 I/O
(BIO
)能夠用下圖表示:
特色以及缺點以下:
- 每一個請求都須要獨立的線程完成數據
Read
,業務處理,數據Write
的完整操做問題。 - 當併發數較大時,須要建立大量線程來處理鏈接,系統資源佔用較大。
- 鏈接創建後,若是當前線程暫時沒有數據可讀,則線程就阻塞在
Read
操做上,形成線程資源浪費。
I/O 複用模型 在
I/O
複用模型中,會用到 Select
,這個函數也會使進程阻塞,可是和阻塞 I/O
所不一樣的是這個函數能夠同時阻塞多個 I/O
操做。
並且能夠同時對多個讀操做,多個寫操做的 I/O
函數進行檢測,直到有數據可讀或可寫時,才真正調用 I/O
操做函數。
Netty
的非阻塞 I/O
的實現關鍵是基於 I/O
複用模型,這裏用 Selector
對象表示:
Netty
的 IO
線程 NioEventLoop
因爲聚合了多路複用器 Selector
,能夠同時併發處理成百上千個客戶端鏈接。
當線程從某客戶端 Socket
通道進行讀寫數據時,若沒有數據可用時,該線程能夠進行其餘任務。
線程一般將非阻塞 IO
的空閒時間用於在其餘通道上執行 IO
操做,因此單獨的線程能夠管理多個輸入和輸出通道。
因爲讀寫操做都是非阻塞的,這就能夠充分提高 IO
線程的運行效率,避免因爲頻繁 I/O
阻塞致使的線程掛起。
一個 I/O
線程能夠併發處理 N
個客戶端鏈接和讀寫操做,這從根本上解決了傳統同步阻塞 I/O
一鏈接一線程模型,架構的性能、彈性伸縮能力和可靠性都獲得了極大的提高。
Netty
線程模型
Netty
主要基於主從 Reactors
多線程模型(以下圖)作了必定的修改,其中主從 Reactor
多線程模型有多個 Reactor
:
MainReactor
負責客戶端的鏈接請求,並將請求轉交給 SubReactor
。 SubReactor
負責相應通道的 IO
讀寫請求。 非 IO
請求(具體邏輯處理)的任務則會直接寫入隊列,等待 worker threads
進行處理。 這裏引用 Doug Lee
大神的 Reactor
介紹:Scalable IO in Java
裏面關於主從 Reactor
多線程模型的圖: 特別說明的是:雖然
Netty
的線程模型基於主從 Reactor
多線程,借用了 MainReactor
和 SubReactor
的結構。可是實際實現上 SubReactor
和 Worker
線程在同一個線程池中。
Netty
的零拷貝
是在發送數據的時候,傳統的實現方式是:
File.read(bytes); Socket.send(bytes);
這種方式須要四次數據拷貝和四次上下文切換:
- 數據從磁盤讀取到內核的
read buffer
- 數據從內核緩衝區拷貝到用戶緩衝區
- 數據從用戶緩衝區拷貝到內核的
socket buffer
- 數據從內核的
socket buffer
拷貝到網卡接口(硬件)的緩衝區
零拷貝的概念
明顯上面的第二步和第三步是沒有必要的,經過java
的FileChannel.transferTo
方法,能夠避免上面兩次多餘的拷貝(固然這須要底層操做系統支持)
- 調用
transferTo
,數據從文件由DMA
引擎拷貝到內核read buffer
- 接着
DMA
從內核read buffer
將數據拷貝到網卡接口buffer
上面的兩次操做都不須要CPU
參與,因此就達到了零拷貝。
Netty
中的零拷貝主要體如今三個方面:
bytebuffer
Netty
發送和接收消息主要使用bytebuffer
,bytebuffer
使用對外內存(DirectMemory
)直接進行Socket
讀寫。緣由:若是使用傳統的堆內存進行
Socket
讀寫,JVM
會將堆內存buffer
拷貝一份到直接內存中而後再寫入socket
,多了一次緩衝區的內存拷貝。DirectMemory
中能夠直接經過DMA發送到網卡接口。
Composite Buffers
傳統的
ByteBuffer
,若是須要將兩個ByteBuffer
中的數據組合到一塊兒,咱們須要首先建立一個size=size1+size2
大小的新的數組,而後將兩個數組中的數據拷貝到新的數組中。 可是使用Netty
提供的組合ByteBuf
,就能夠避免這樣的操做,由於CompositeByteBuf
並無真正將多個Buffer
組合起來,而是保存了它們的引用,從而避免了數據的拷貝,實現了零拷貝。
- 對於
FileChannel.transferTo
的使用
Netty
中使用了FileChannel的transferTo
方法,該方法依賴於操做系統實現零拷貝。