Netty
是一個高性能、異步事件驅動的NIO
框架,它提供了對TCP
、UDP
和文件傳輸的支持。做爲當前最流行的NIO
框架,Netty
在互聯網領域、大數據分佈式計算領域、遊戲行業、通訊行業等得到了普遍的應用,一些業界著名的開源組件也是基於Netty
的NIO
框架構建。java
Netty
利用 Java
高級網絡的能力,隱藏其背後的複雜性而提供一個易於使用的 API
構建一個客戶端/服務端,其具備高併發、傳輸快、封裝好等特色。編程
高併發 Netty
是一款基於NIO
(Nonblocking I/O
,非阻塞IO
)開發的網絡通訊框架,對比於BIO
(Blocking I/O
,阻塞IO
),它的併發性能獲得了很大提升 。segmentfault
傳輸快 Netty
的傳輸快其實也是依賴了NIO
的一個特性——零拷貝。數組
封裝好
Netty封裝了NIO操做的不少細節,提供易於使用的API,還有心跳、重連機制、拆包粘包方案等特性,使開發者能可以快速高效的構建一個穩健的高併發應用。緩存
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
能夠被及時修復,同時,更多的新功能會被加入。服務端:
ServerBootStrap
實例Reactor
線程池:EventLoopGroup
,EventLoop
就是處理全部註冊到本線程的Selector
上面的Channel
Channel
ChannelPipeline
和handler
,網絡時間以流的形式在其中流轉,handler
完成多數的功能定製:好比編解碼 SSl
安全認證channel
後,由Reactor
線程:NioEventLoop
執行pipline
中的方法,最終調度並執行channelHandler
客戶端:
主要功能特性以下圖:
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
做爲異步事件驅動的網絡,高性能之處主要來自於其 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
方法,該方法依賴於操做系統實現零拷貝。