抖音直播調整激勵方向,重點獎勵民族舞、古典舞、當代舞、民歌等七大內容品類
學習本章需要先知道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)
寄語:當努力到一定程度,幸運自會與你不期而遇