對Netty的一些理解

Netty是一個高性能、異步事件驅動的NIO框架,它提供了對TCPUDP和文件傳輸的支持。做爲當前最流行的NIO框架,Netty在互聯網領域、大數據分佈式計算領域、遊戲行業、通訊行業等得到了普遍的應用,一些業界著名的開源組件也是基於NettyNIO框架構建。java

Netty 利用 Java 高級網絡的能力,隱藏其背後的複雜性而提供一個易於使用的 API 構建一個客戶端/服務端,其具備高併發、傳輸快、封裝好等特色。編程

高併發 Netty是一款基於NIONonblocking I/O,非阻塞IO)開發的網絡通訊框架,對比於BIOBlocking I/O,阻塞IO),它的併發性能獲得了很大提升 。數組

傳輸快 Netty的傳輸快其實也是依賴了NIO的一個特性——零拷貝緩存

封裝好 Netty封裝了NIO操做的不少細節,提供易於使用的API,還有心跳、重連機制、拆包粘包方案等特性,使開發者能可以快速高效的構建一個穩健的高併發應用。安全

爲何要用 Netty ?

JDK 原生 NIO 程序的問題服務器

JDK 原生也有一套網絡應用程序 API,可是存在一系列問題,主要以下:網絡

  • NIO 的類庫和 API 繁雜,使用麻煩。你須要熟練掌握 SelectorServerSocketChannelSocketChannelByteBuffer 等。
  • 須要具有其餘的額外技能作鋪墊。例如熟悉 Java 多線程編程,由於 NIO 編程涉及到 Reactor 模式,你必須對多線程和網路編程很是熟悉,才能編寫出高質量的 NIO 程序。
  • 可靠性能力補齊,開發工做量和難度都很是大。例如客戶端面臨斷連重連、網絡閃斷、半包讀寫、失敗緩存、網絡擁塞和異常碼流的處理等等。NIO 編程的特色是功能開發相對容易,可是可靠性能力補齊工做量和難度都很是大。
  • JDK NIOBug。例如臭名昭著的 Epoll Bug,它會致使 Selector 空輪詢,最終致使 CPU 100%。 官方聲稱在 JDK 1.6 版本的 update 18 修復了該問題,可是直到 JDK 1.7 版本該問題仍舊存在,只不過該 Bug 發生機率下降了一些而已,它並無被根本解決。

Netty 的特色多線程

NettyJDK 自帶的 NIOAPI 進行封裝,解決上述問題,主要特色有:架構

  • 設計優雅,適用於各類傳輸類型的統一 API 阻塞和非阻塞 Socket;基於靈活且可擴展的事件模型,能夠清晰地分離關注點;高度可定製的線程模型 - 單線程,一個或多個線程池;真正的無鏈接數據報套接字支持(自 3.1 起)。
  • 使用方便,詳細記錄的 Javadoc,用戶指南和示例;沒有其餘依賴項,JDK 5Netty 3.x)或 6Netty 4.x)就足夠了。
  • 高性能,吞吐量更高,延遲更低;減小資源消耗;最小化沒必要要的內存複製。 安全,完整的 SSL/TLSStartTLS 支持。
  • 社區活躍,不斷更新,社區活躍,版本迭代週期短,發現的 Bug 能夠被及時修復,同時,更多的新功能會被加入。

Netty 內部執行流程

服務端: image.png image.png併發

  • 建立ServerBootStrap實例

  • 設置並綁定Reactor線程池:EventLoopGroupEventLoop就是處理全部註冊到本線程的Selector上面的Channel

  • 設置並綁定服務端的Channel

  • 建立處理網絡事件的ChannelPipelinehandler,網絡時間以流的形式在其中流轉,handler完成多數的功能定製:好比編解碼 SSl安全認證

  • 綁定並啓動監聽端口

  • 當輪訓到準備就緒的channel後,由Reactor線程:NioEventLoop執行pipline中的方法,最終調度並執行channelHandler

客戶端: Netty客戶端建立時序圖.png image.png

Netty 架構設計

主要功能特性以下圖: image.png

Netty 功能特性以下:

  • 傳輸服務,支持 BIONIO
  • 容器集成,支持 OSGIJBossMCSpringGuice 容器。
  • 協議支持,HTTPProtobuf、二進制、文本、WebSocket 等一系列常見協議都支持。還支持經過實行編碼解碼邏輯來實現自定義協議。
  • Core 核心,可擴展事件模型、通用通訊 API、支持零拷貝的 ByteBuf 緩衝對象。

模塊組件

BootstrapServerBootstrap

Bootstrap 意思是引導,一個 Netty 應用一般由一個 Bootstrap 開始,主要做用是配置整個 Netty 程序,串聯各個組件,NettyBootstrap 類是客戶端程序的啓動引導類,ServerBootstrap 是服務端啓動引導類。

FutureChannelFuture

正如前面介紹,在 Netty 中全部的 IO 操做都是異步的,不能馬上得知消息是否被正確處理。

可是能夠過一會等它執行完成或者直接註冊一個監聽,具體的實現就是經過 FutureChannelFutures,它們能夠註冊一個監聽,當操做執行成功或失敗時監聽會自動觸發註冊的監聽事件。

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 服務器端鏈接,這些通道涵蓋了 UDPTCP 網絡 IO 以及文件 IO

Selector

Netty 基於 Selector 對象實現 I/O 多路複用,經過 Selector 一個線程能夠監聽多個鏈接的 Channel 事件。

當向一個 Selector 中註冊 Channel 後,Selector 內部的機制就能夠自動不斷地查詢(Select) 這些註冊的 Channel 是否有已就緒的 I/O 事件(例如可讀,可寫,網絡鏈接完成等),這樣程序就能夠很簡單地使用一個線程高效地管理多個 Channel

NioEventLoop

NioEventLoop 中維護了一個線程和任務隊列,支持異步提交執行任務,線程啓動時會調用 NioEventLooprun 方法,執行 I/O 任務和非 I/O 任務:

  • I/O 任務,即 selectionKeyready 的事件,如 acceptconnectreadwrite 等,由 processSelectedKeys 方法觸發。
  • IO 任務,添加到 taskQueue 中的任務,如 register0bind0 等任務,由 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

保存 ChannelHandlerList,用於處理或攔截 Channel 的入站事件和出站操做。

ChannelPipeline 實現了一種高級形式的攔截過濾器模式,使用戶能夠徹底控制事件的處理方式,以及 Channel 中各個的 ChannelHandler 如何相互交互。

Netty 高性能設計

Netty 做爲異步事件驅動的網絡,高性能之處主要來自於其 I/O 模型和線程處理模型,前者決定如何收發數據,後者決定如何處理數據。

I/O 模型

用什麼樣的通道將數據發送給對方,BIONIO 或者 AIOI/O 模型在很大程度上決定了框架的性能。

阻塞 I/O

傳統阻塞型 I/O(BIO)能夠用下圖表示: image.png

特色以及缺點以下:

  • 每一個請求都須要獨立的線程完成數據 Read,業務處理,數據 Write 的完整操做問題。
  • 當併發數較大時,須要建立大量線程來處理鏈接,系統資源佔用較大。
  • 鏈接創建後,若是當前線程暫時沒有數據可讀,則線程就阻塞在 Read 操做上,形成線程資源浪費。

I/O 複用模型 image.pngI/O 複用模型中,會用到 Select,這個函數也會使進程阻塞,可是和阻塞 I/O 所不一樣的是這個函數能夠同時阻塞多個 I/O 操做。

並且能夠同時對多個讀操做,多個寫操做的 I/O 函數進行檢測,直到有數據可讀或可寫時,才真正調用 I/O 操做函數。

Netty 的非阻塞 I/O 的實現關鍵是基於 I/O 複用模型,這裏用 Selector 對象表示: image.png

NettyIO 線程 NioEventLoop 因爲聚合了多路複用器 Selector,能夠同時併發處理成百上千個客戶端鏈接。

當線程從某客戶端 Socket 通道進行讀寫數據時,若沒有數據可用時,該線程能夠進行其餘任務。

線程一般將非阻塞 IO 的空閒時間用於在其餘通道上執行 IO 操做,因此單獨的線程能夠管理多個輸入和輸出通道。

因爲讀寫操做都是非阻塞的,這就能夠充分提高 IO 線程的運行效率,避免因爲頻繁 I/O 阻塞致使的線程掛起。

一個 I/O 線程能夠併發處理 N 個客戶端鏈接和讀寫操做,這從根本上解決了傳統同步阻塞 I/O 一鏈接一線程模型,架構的性能、彈性伸縮能力和可靠性都獲得了極大的提高。

Netty 線程模型

Netty 主要基於主從 Reactors 多線程模型(以下圖)作了必定的修改,其中主從 Reactor 多線程模型有多個 Reactor

MainReactor 負責客戶端的鏈接請求,並將請求轉交給 SubReactorSubReactor 負責相應通道的 IO 讀寫請求。 非 IO 請求(具體邏輯處理)的任務則會直接寫入隊列,等待 worker threads 進行處理。 這裏引用 Doug Lee 大神的 Reactor 介紹:Scalable IO in Java 裏面關於主從 Reactor 多線程模型的圖: image.png 特別說明的是:雖然 Netty 的線程模型基於主從 Reactor 多線程,借用了 MainReactorSubReactor 的結構。可是實際實現上 SubReactorWorker 線程在同一個線程池中。

Netty 的零拷貝

是在發送數據的時候,傳統的實現方式是:

File.read(bytes);
Socket.send(bytes);

這種方式須要四次數據拷貝和四次上下文切換:

  • 數據從磁盤讀取到內核的read buffer
  • 數據從內核緩衝區拷貝到用戶緩衝區
  • 數據從用戶緩衝區拷貝到內核的socket buffer
  • 數據從內核的socket buffer拷貝到網卡接口(硬件)的緩衝區

零拷貝的概念

明顯上面的第二步和第三步是沒有必要的,經過javaFileChannel.transferTo方法,能夠避免上面兩次多餘的拷貝(固然這須要底層操做系統支持)

  • 調用transferTo,數據從文件由DMA引擎拷貝到內核read buffer
  • 接着DMA從內核read buffer將數據拷貝到網卡接口buffer

上面的兩次操做都不須要CPU參與,因此就達到了零拷貝。

Netty中的零拷貝主要體如今三個方面:

  • bytebuffer

Netty發送和接收消息主要使用bytebufferbytebuffer使用對外內存(DirectMemory)直接進行Socket讀寫。

緣由:若是使用傳統的堆內存進行Socket讀寫,JVM會將堆內存buffer拷貝一份到直接內存中而後再寫入socket,多了一次緩衝區的內存拷貝。DirectMemory中能夠直接經過DMA發送到網卡接口。

  • Composite Buffers

傳統的ByteBuffer,若是須要將兩個ByteBuffer中的數據組合到一塊兒,咱們須要首先建立一個size=size1+size2大小的新的數組,而後將兩個數組中的數據拷貝到新的數組中。 可是使用Netty提供的組合ByteBuf,就能夠避免這樣的操做,由於CompositeByteBuf並無真正將多個Buffer組合起來,而是保存了它們的引用,從而避免了數據的拷貝,實現了零拷貝。

  • 對於FileChannel.transferTo的使用

Netty中使用了FileChannel的transferTo方法,該方法依賴於操做系統實現零拷貝。

本文由博客一文多發平臺 OpenWrite 發佈! 更多內容請點擊個人博客 沐晨

相關文章
相關標籤/搜索