學習本章需要先知道IO多路復用,不清楚的請移步:IO多路復用
網路通信中,阻塞IO兩大阻塞的地方:socket鏈接阻塞,等待讀取檔案阻塞, 本地檔案io就只有一個等待檔案阻塞
一.Reactor模型(Netty執行緒模型)
說Netty之前先說一下高性能網路模式Reactor,由于NIO是面向程序撰寫,效率太低,大佬們基于面向物件的思想,對 I/O 多路復用作了一層封裝,讓使用者不用考慮底層網路 API 的細節,只需要關注應用代碼的撰寫,大佬們還為這種模式取了個讓人第一時間難以理解的名字:Reactor 模式,意思對事件的反應,來了一個事件(連接請求,讀寫請求),Reactor 就有相對應的反應/回應,
Reactor有3種經典方案:
單Reactor單執行緒:

- Reactor 物件通過 select (IO 多路復用介面) 監聽事件,收到事件后通過 dispatch 進行分發,具體分發給 Acceptor 物件還是 Handler 物件,還要看收到的事件型別;
- 如果是連接建立的事件,則交由 Acceptor 物件進行處理,Acceptor 物件會通過 accept 方法 獲取連接,并創建一個 Handler 物件來處理后續的回應事件;
- 如果不是連接建立事件, 則交由當前連接對應的 Handler 物件來進行回應;
- Handler 物件通過 read -> 業務處理 -> send 的流程來完成完整的業務流程,
缺點:
- 因為只有一個行程,無法充分利用 多核 CPU 的性能;
- Handler 物件在業務處理時,整個行程是無法處理其他連接的事件的,如果業務處理耗時比較長,那么就造成回應的延遲;
適用場景:單 Reactor 單行程的方案不適用計算機密集型的場景,只適用于業務處理非常快速的場景,
單Reactor多執行緒:

前面的三個步驟和單 Reactor 單執行緒方案是一樣的,我們只看不一樣的部分:
- Handler 物件不再負責業務處理,只負責資料的接收和發送,Handler 物件通過 read 讀取到資料后,會將資料發給執行緒池中的執行緒進行業務處理;
- 處理完后,將結果發給主執行緒中的 Handler 物件,接著由 Handler 通過 send 方法將回應結果發送給 client;
缺點:
- 雖然充分了利用了多核CPU,但是會涉及共享資源的競爭,
- 因為一個 Reactor 物件承擔所有事件的監聽和回應,而且只在主執行緒中運行,在面對瞬間高并發的場景時,容易成為性能的瓶頸的地方,
多Reactor多執行緒(主從Reactor多執行緒):

和單Reactor多執行緒唯一不同的是,將連接請求和讀寫請求分別發給不同的Reactor進行處理,該模式應用于Netty,Memcache等專案中
那么我們看看Netty到底是什么樣的模型:

- Netty將主Reactor和從Reactor抽象成倆個組:Boss Group專門負責連接請求,Worker Group專門負責讀寫請求,
- 每一個組包含多個NioEventLoopGroup,這是一個事件回圈組,這個組內有多個事件回圈(NioEventLoop),每一個NioEventLoop都有一個selector,用于監聽網路的連接,讀寫請求,
- 每一個連接請求被Boss Group的selector監測到,都會交給Worker Group中的一個事件回圈組,由Worker Group的selector監測是否有讀寫操作,
- 最后交給Pipeline的handler進行業務處理
具體細節下面詳細說明!
二.Netty概述
Netty 是什么?
- Netty 是一個 基于 NIO 的 client-server(客戶端服務器)框架,使用它可以快速簡單地開發網路應用程式,
- 它極大地簡化并優化了 TCP 和 UDP 套接字服務器等網路編程,并且性能以及安全性等很多方面甚至都要更好,
- 支持多種協議 如 FTP,SMTP,HTTP 以及各種二進制和基于文本的傳統協議,
為什么要用 Netty?因為 Netty 具有下面這些優點,并且相比于直接使用 JDK 自帶的 NIO 相關的 API 來說更加易用,
- 統一的 API,支持多種傳輸型別,阻塞和非阻塞的,
- 簡單而強大的執行緒模型,
- 自帶編解碼器解決 TCP 粘包/拆包問題,
- 自帶各種協議堆疊,
- 真正的無連接資料包套接字支持,
- 比直接使用 Java 核心 API 有更高的吞吐量、更低的延遲、更低的資源消耗和更少的記憶體復制,
- 安全性不錯,有完整的 SSL/TLS 以及 StartTLS 支持,
- 社區活躍
- 成熟穩定,經歷了大型專案的使用和考驗,而且很多開源專案都使用到了 Netty, 比如我們經常接觸的 Dubbo、RocketMQ 等等,
- ......
Netty 應用場景了解么?理論上來說,NIO 可以做的事情 ,使用 Netty 都可以做并且更好,Netty 主要用來做網路通信 :
- 作為 RPC 框架的網路通信工具
- 實作一個自己的 HTTP 服務器:說到 HTTP 服務器的話,作為 Java 后端開發,我們一般使用 Tomcat 比較多,一個最基本的 HTTP 服務器可要以處理常見的 HTTP Method 的請求,比如 POST 請求、GET 請求等等,
- 實作一個即時通訊系統 :使用 Netty 我們可以實作一個可以聊天類似微信的即時通訊系統,這方面的開源專案還蠻多的,可以自行去 Github 找一找,
- 實作訊息推送系統 :市面上有很多訊息推送系統都是基于 Netty 來做的,
- ...
三.Netty 核心組件有哪些?分別有什么作用?
Bootstrap,ServerBootstrap:
引導,一個Netty通常又一個Bootstrap開始,主要作用是配置整個Netty程式,串聯各個組件,
Bootstrap 是客戶端的啟動引導類/輔助類,具體使用方法如下:
EventLoopGroup group = new NioEventLoopGroup(); try { //創建客戶端啟動引導/輔助類:Bootstrap Bootstrap b = new Bootstrap(); //指定執行緒模型 b.group(group). ...... // 嘗試建立連接 ChannelFuture f = b.connect(host, port).sync(); f.channel().closeFuture().sync(); } finally { // 優雅關閉相關執行緒組資源 group.shutdownGracefully();
ServerBootstrap 客戶端的啟動引導類/輔助類,具體使用方法如下:
// 1.bossGroup 用于接收連接,workerGroup 用于具體的處理 EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //2.創建服務端啟動引導/輔助類:ServerBootstrap ServerBootstrap b = new ServerBootstrap(); //3.給引導類配置兩大執行緒組,確定了執行緒模型 b.group(bossGroup, workerGroup). ...... // 6.系結埠 ChannelFuture f = b.bind(port).sync(); // 等待連接關閉 f.channel().closeFuture().sync(); } finally { //7.優雅關閉相關執行緒組資源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }
說明:
Bootstrap通常使用connet()方法連接到遠程的主機和埠,作為一個 Netty TCP 協議通信中的客戶端,另外,Bootstrap也可以通過bind()方法系結本地的一個埠,作為 UDP 協議通信中的一端,ServerBootstrap通常使用bind()方法系結本地的埠上,然后等待客戶端的連接,Bootstrap只需要配置一個執行緒組—EventLoopGroup,而ServerBootstrap需要配置兩個執行緒組—EventLoopGroup,一個用于接收連接,一個用于具體的處理,
Channel:(在代碼層面,讀寫,連接操作看似交給BootStrap,其實是交給Channel)
Channel 介面是 Netty 對網路操作抽象類,它除了包括基本的 I/O 操作,如 bind()、connect()、read()、write() 等,
比較常用的Channel介面實作類是NioServerSocketChannel(服務端)和NioSocketChannel(客戶端),這兩個 Channel 可以和 BIO 編程模型中的ServerSocket以及Socket兩個概念對應上,
ChannelFuture:
Netty 是異步非阻塞的,所有的 I/O 操作都為異步的,因此,我們不能立刻得到操作是否執行成功,但是,可通過下面2種方式知道:
- 可以通過
ChannelFuture介面的addListener()方法注冊一個ChannelFutureListener,當操作執行成功或者失敗時,監聽就會自動觸發回傳結果, 可以通過ChannelFuture介面的sync()方法讓異步的操作變成同步的,
EventLoop 與 EventLoopGroup:
- EventLoopGroup 是一個 EventLoop 池,包含很多的 EventLoop,
- EventLoop的主要作用實際就是負責監聽網路事件并呼叫事件處理器進行相關 I/O 操作的處理,因為每一個EventLoop有一個自己的selector,
- 每一個EventLoop都對應一個Thread,
- 每一個Channel都需要注冊到EventLoop上進行IO操作,
- 對應到的實作類就是NioEventLoop和NioEventLoopGroup,
Selector和TaskQueue:
- 都屬于EventLoop 內的組件,
- Selector就是NIO 的selector,這里不多介紹,詳細請看NIO多路復用
- 關于TaskQueue:當handler里面有長時間的任務時候,可以把這個任務放到TaskQueue中,先回應客戶端,再執行長任務,,相當于異步執行,但是其實還是這一個執行緒在執行,
ChannelPipeline和ChannelHandler :
- ChannelPipeline是一個雙向鏈表,里面就是一些ChannelHandler,用來處理對應的Channel的業務,
- ChannelPipeline有Channel的資訊,Channel中也有ChannelPipeline的資訊,
- 我們可以在 ChannelPipeline上通過
addLast()方法添加一個或者多個ChannelHandler,因為一個資料或者事件可能會被多個 Handler 處理,當一個ChannelHandler 處理完之后就將資料交給下一個ChannelHandler ,

四.Netty服務端和客戶端的啟動程序了解么?
// 1.bossGroup 用于接收連接,workerGroup 用于具體的處理 EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //2.創建服務端啟動引導/輔助類:ServerBootstrap ServerBootstrap b = new ServerBootstrap(); //3.給引導類配置兩大執行緒組,確定了執行緒模型 b.group(bossGroup, workerGroup) // (非必備)列印日志 .handler(new LoggingHandler(LogLevel.INFO)) // 4.指定 IO 模型 .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); //5.可以自定義客戶端訊息的業務處理邏輯 p.addLast(new HelloServerHandler()); } }); // 6.系結埠,呼叫 sync 方法阻塞直到系結完成 ChannelFuture f = b.bind(port).sync(); // 7.阻塞等待直到服務器Channel關閉(closeFuture()方法獲取Channel 的CloseFuture物件,然后呼叫sync()方法) f.channel().closeFuture().sync(); } finally { //8.優雅關閉相關執行緒組資源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }
五.其他小問題
Netty 長連接?
- 我們知道 TCP 在進行讀寫之前,server 與 client 之間必須提前建立一個連接,建立連接的程序,需要我們常說的三次握手,釋放/關閉連接的話需要四次揮手,這個程序是比較消耗網路資源并且有時間延遲的,
- 所謂,短連接說的就是 server 端 與 client 端建立連接之后,讀寫完成之后就關閉掉連接,如果下一次再要互相發送訊息,就要重新連接,短連接的有點很明顯,就是管理和實作都比較簡單,缺點也很明顯,每一次的讀寫都要建立連接必然會帶來大量網路資源的消耗,并且連接的建立也需要耗費時間,
- 長連接說的就是 client 向 server 雙方建立連接之后,即使 client 與 server 完成一次讀寫,它們之間的連接并不會主動關閉,后續的讀寫操作會繼續使用這個連接,長連接的可以省去較多的 TCP 建立和關閉的操作,降低對網路資源的依賴,節約時間,對于頻繁請求資源的客戶來說,非常適用長連接,
Netty心跳機制了解么?
- 在 TCP 保持長連接的程序中,可能會出現斷網等網路例外出現,例外發生的時候, client 與 server 之間如果沒有互動的話,它們是無法發現對方已經掉線的,為了解決這個問題, 我們就需要引入 心跳機制 ,
- 心跳機制的作業原理是: 在 client 與 server 之間在一定時間內沒有資料互動時, 即處于 idle 狀態時, 客戶端或服務器就會發送一個特殊的資料包給對方, 當接收方收到這個資料報文后, 也立即發送一個特殊的資料報文, 回應發送方, 此即一個 PING-PONG 互動,所以, 當某一端收到心跳訊息后, 就知道了對方仍然在線, 這就確保 TCP 連接的有效性.
- TCP 實際上自帶的就有長連接選項,本身是也有心跳包機制,也就是 TCP 的選項:
SO_KEEPALIVE,但是,TCP 協議層面的長連接靈活性不夠,所以,一般情況下我們都是在應用層協議上實作自定義心跳機制的,也就是在 Netty 層面通過編碼實作,通過 Netty 實作心跳機制的話,核心類是IdleStateHandler,
Netty 的零拷貝了解么?
在 OS 層面上的 Zero-copy 通常指避免在 用戶態(User-space) 與 內核態(Kernel-space) 之間來回拷貝資料,而在 Netty 層面 ,零拷貝主要體現在對于資料操作的優化,
- 使用 Netty 提供的
CompositeByteBuf類, 可以將多個ByteBuf合并為一個邏輯上的ByteBuf, 避免了各個ByteBuf之間的拷貝, ByteBuf支持 slice 操作, 因此可以將 ByteBuf 分解為多個共享同一個存盤區域的ByteBuf, 避免了記憶體的拷貝,- 通過
FileRegion包裝的FileChannel.tranferTo實作檔案傳輸, 可以直接將檔案緩沖區的資料發送到目標Channel, 避免了傳統通過回圈 write 方式導致的記憶體拷貝問題,
NioEventLoopGroup 默認的建構式會起多少執行緒?
- 默認是cpu核數*2個子執行緒(也就是nioEventLoop)
寄語:當努力到一定程度,幸運自會與你不期而遇
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/423642.html
標籤:其他
