愛伊米

你要的Netty常見面試題總結,我面試回來整理好了!

最近經常會再面試中碰到 Netty 相關的問題。

全文采用大家喜歡的與面試官對話的形式展開。 如果大家覺得總結的不錯的話,不妨點一個贊鼓勵一下!這是我繼續堅持很重要的動力來源。

概覽:

Netty 是什麼?

為什麼要用 Netty?

Netty 應用場景瞭解麼?

Netty 核心元件有哪些?分別有什麼作用?

EventloopGroup 瞭解麼?和 EventLoop 啥關係?

Bootstrap 和 ServerBootstrap 瞭解麼?

NioEventLoopGroup 預設的建構函式會起多少執行緒?

Netty 執行緒模型瞭解麼?

Netty 服務端和客戶端的啟動過程瞭解麼?

Netty 長連線、心跳機制瞭解麼?

Netty 的零複製瞭解麼?

Netty 是什麼?

面試官

:介紹一下自己對 Netty 的認識吧!小夥子。

:好的!那我就簡單用 3 點來概括一下 Netty 吧!

Netty 是一個

基於 NIO

的 client-server(客戶端伺服器)框架,使用它可以快速簡單地開發網路應用程式。

它極大地簡化並優化了 TCP 和 UDP 套接字伺服器等網路程式設計,並且效能以及安全性等很多方面甚至都要更好。

支援多種協議

如 FTP,SMTP,HTTP 以及各種二進位制和基於文字的傳統協議。

用官方的總結就是:

Netty 成功地找到了一種在不妥協可維護性和效能的情況下實現易於開發,效能,穩定性和靈活性的方法。

除了上面介紹的之外,很多開源專案比如我們常用的 Dubbo、RocketMQ、Elasticsearch、gRPC 等等都用到了 Netty。

網路程式設計我願意稱 Netty 為王 。

為什麼要用 Netty?

面試官

:為什麼要用 Netty 呢?能不能說一下自己的看法。

:因為 Netty 具有下面這些優點,並且相比於直接使用 JDK 自帶的 NIO 相關的 API 來說更加易用。

統一的 API,支援多種傳輸型別,阻塞和非阻塞的。

簡單而強大的執行緒模型。

自帶編解碼器解決 TCP 粘包/拆包問題。

自帶各種協議棧。

真正的無連線資料包套接字支援。

比直接使用 Java 核心 API 有更高的吞吐量、更低的延遲、更低的資源消耗和更少的記憶體複製。

安全性不錯,有完整的 SSL/TLS 以及 StartTLS 支援。

社群活躍

成熟穩定,經歷了大型專案的使用和考驗,而且很多開源專案都使用到了 Netty, 比如我們經常接觸的 Dubbo、RocketMQ 等等。

……

Netty 應用場景瞭解麼?

面試官

:能不能通俗地說一下使用 Netty 可以做什麼事情?

:憑藉自己的瞭解,簡單說一下吧!理論上來說,NIO 可以做的事情 ,使用 Netty 都可以做並且更好。Netty 主要用來做

網路通訊

作為 RPC 框架的網路通訊工具

:我們在分散式系統中,不同服務節點之間經常需要相互呼叫,這個時候就需要 RPC 框架了。不同服務節點之間的通訊是如何做的呢?可以使用 Netty 來做。比如我呼叫另外一個節點的方法的話,至少是要讓對方知道我呼叫的是哪個類中的哪個方法以及相關引數吧!

實現一個自己的 HTTP 伺服器

:透過 Netty 我們可以自己實現一個簡單的 HTTP 伺服器,這個大家應該不陌生。說到 HTTP 伺服器的話,作為 Java 後端開發,我們一般使用 Tomcat 比較多。一個最基本的 HTTP 伺服器可要以處理常見的 HTTP Method 的請求,比如 POST 請求、GET 請求等等。

實現一個即時通訊系統

:使用 Netty 我們可以實現一個可以聊天類似微信的即時通訊系統,這方面的開源專案還蠻多的,可以自行去 Github 找一找。

實現訊息推送系統

:市面上有很多訊息推送系統都是基於 Netty 來做的。

……

Netty 核心元件有哪些?分別有什麼作用?

面試官

:Netty 核心元件有哪些?分別有什麼作用?

:表面上,嘴上開始說起 Netty 的核心元件有哪些,實則,內心已經開始 mmp 了,深度懷疑這面試官是存心搞我啊!

1。Channel

Channel 介面是 Netty 對網路操作抽象類,它除了包括基本的 I/O 操作,如 bind()、connect()、read()、write() 等。

比較常用的Channel介面實現類是NioServerSocketChannel(服務端)和NioSocketChannel(客戶端),這兩個 Channel 可以和 BIO 程式設計模型中的ServerSocket以及Socket兩個概念對應上。Netty 的 Channel 介面所提供的 API,大大地降低了直接使用 Socket 類的複雜性。

2。EventLoop

這麼說吧!EventLoop(事件迴圈)介面可以說是 Netty 中最核心的概念了!

《Netty 實戰》這本書是這樣介紹它的:

EventLoop 定義了 Netty 的核心抽象,用於處理連線的生命週期中所發生的事件。

你要的Netty常見面試題總結,我面試回來整理好了!

是不是很難理解?說實話,我學習 Netty 的時候看到這句話是沒太能理解的。

說白了,

EventLoop 的主要作用實際就是負責監聽網路事件並呼叫事件處理器進行相關 I/O 操作的處理。

那 Channel 和 EventLoop 直接有啥聯絡呢?

Channel 為 Netty 網路操作(讀寫等操作)抽象類,EventLoop 負責處理註冊到其上的Channel 處理 I/O 操作,兩者配合參與 I/O 操作。

3。ChannelFuture

Netty 是非同步非阻塞的,所有的 I/O 操作都為非同步的。

因此,我們不能立刻得到操作是否執行成功,但是,你可以透過 ChannelFuture 介面的 addListener() 方法註冊一個 ChannelFutureListener,當操作執行成功或者失敗時,監聽就會自動觸發返回結果。

並且,你還可以透過ChannelFuture 的 channel() 方法獲取關聯的Channel

public interface ChannelFuture extends Future { Channel channel(); ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> var1); …… ChannelFuture sync() throws InterruptedException;}

另外,我們還可以透過 ChannelFuture 介面的 sync()方法讓非同步的操作變成同步的。

4。ChannelHandler 和 ChannelPipeline

下面這段程式碼使用過 Netty 的小夥伴應該不會陌生,我們指定了序列化編解碼器以及自定義的 ChannelHandler 處理訊息。

b。group(eventLoopGroup) 。handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { ch。pipeline()。addLast(new NettyKryoDecoder(kryoSerializer, RpcResponse。class)); ch。pipeline()。addLast(new NettyKryoEncoder(kryoSerializer, RpcRequest。class)); ch。pipeline()。addLast(new KryoClientHandler()); } });

ChannelHandler 是訊息的具體處理器。他負責處理讀寫操作、客戶端連線等事情。

ChannelPipeline 為 ChannelHandler 的鏈,提供了一個容器並定義了用於沿著鏈傳播入站和出站事件流的 API 。當 Channel 被建立時,它會被自動地分配到它專屬的 ChannelPipeline。

我們可以在 ChannelPipeline 上透過 addLast() 方法新增一個或者多個ChannelHandler ,因為一個數據或者事件可能會被多個 Handler 處理。當一個 ChannelHandler 處理完之後就將資料交給下一個 ChannelHandler 。

EventloopGroup 瞭解麼?和 EventLoop 啥關係?

面試官

:剛剛你也介紹了 EventLoop。那你再說說 EventloopGroup 吧!和 EventLoop 啥關係?

你要的Netty常見面試題總結,我面試回來整理好了!

EventLoopGroup 包含多個 EventLoop(每一個 EventLoop 通常內部包含一個執行緒),上面我們已經說了 EventLoop 的主要作用實際就是負責監聽網路事件並呼叫事件處理器進行相關 I/O 操作的處理。

並且 EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理,即 Thread 和 EventLoop 屬於 1 : 1 的關係,從而保證執行緒安全。

上圖是一個服務端對 EventLoopGroup 使用的大致模組圖,其中 Boss EventloopGroup 用於接收連線,Worker EventloopGroup 用於具體的處理(訊息的讀寫以及其他邏輯處理)。

從上圖可以看出:當客戶端透過 connect 方法連線服務端時,bossGroup 處理客戶端連線請求。當客戶端處理完成後,會將這個連線提交給 workerGroup 來處理,然後 workerGroup 負責處理其 IO 相關操作。

Bootstrap 和 ServerBootstrap 瞭解麼?

面試官

:你再說說自己對 Bootstrap 和 ServerBootstrap 的瞭解吧!

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 ,一個用於接收連線,一個用於具體的處理。

NioEventLoopGroup 預設的建構函式會起多少執行緒?

面試官

:看過 Netty 的原始碼了麼?NioEventLoopGroup 預設的建構函式會起多少執行緒呢?

:嗯嗯!看過部分。

回顧我們在上面寫的伺服器端的程式碼:

// 1。bossGroup 用於接收連線,workerGroup 用於具體的處理EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();

為了搞清楚NioEventLoopGroup 預設的建構函式 到底建立了多少個執行緒,我們來看一下它的原始碼。

/** * 無參建構函式。 * nThreads:0 */ public NioEventLoopGroup() { //呼叫下一個構造方法 this(0); } /** * Executor:null */ public NioEventLoopGroup(int nThreads) { //繼續呼叫下一個構造方法 this(nThreads, (Executor) null); } //中間省略部分建構函式 /** * RejectedExecutionHandler():RejectedExecutionHandlers。reject() */ public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,final SelectStrategyFactory selectStrategyFactory) { //開始呼叫父類的建構函式 super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers。reject()); }

一直向下走下去的話,你會發現在 MultithreadEventLoopGroup 類中有相關的指定執行緒數的程式碼,如下:

// 從1,系統屬性,CPU核心數*2 這三個值中取出一個最大的 //可以得出 DEFAULT_EVENT_LOOP_THREADS 的值為CPU核心數*2 private static final int DEFAULT_EVENT_LOOP_THREADS = Math。max(1, SystemPropertyUtil。getInt(“io。netty。eventLoopThreads”, NettyRuntime。availableProcessors() * 2)); // 被呼叫的父類建構函式,NioEventLoopGroup 預設的建構函式會起多少執行緒的秘密所在 // 當指定的執行緒數nThreads為0時,使用預設的執行緒數DEFAULT_EVENT_LOOP_THREADS protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object。。。 args) { super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args); }

綜上,我們發現 NioEventLoopGroup 預設的建構函式實際會起的執行緒數為

CPU核心數*2

另外,如果你繼續深入下去看建構函式的話,你會發現每個NioEventLoopGroup物件內部都會分配一組NioEventLoop,其大小是 nThreads, 這樣就構成了一個執行緒池, 一個NIOEventLoop 和一個執行緒相對應,這和我們上面說的 EventloopGroup 和 EventLoop關係這部分內容相對應。

Netty 執行緒模型瞭解麼?

面試官

:說一下 Netty 執行緒模型吧!

:大部分網路框架都是基於 Reactor 模式設計開發的。

Reactor 模式基於事件驅動,採用多路複用將事件分發給相應的 Handler 處理,非常適合處理海量 IO 的場景。

在 Netty 主要靠 NioEventLoopGroup 執行緒池來實現具體的執行緒模型的 。

我們實現服務端的時候,一般會初始化兩個執行緒組:

bossGroup

:接收連線。

workerGroup

:負責具體的處理,交由對應的 Handler 處理。

下面我們來詳細看一下 Netty 中的執行緒模型吧!

1。

單執行緒模型

一個執行緒需要執行處理所有的 accept、read、decode、process、encode、send 事件。對於高負載、高併發,並且對效能要求比較高的場景不適用。

對應到 Netty 程式碼是下面這樣的

使用 NioEventLoopGroup 類的無參建構函式設定執行緒數量的預設值就是

CPU 核心數 *2

//1。eventGroup既用於處理客戶端連線,又負責具體的處理。 EventLoopGroup eventGroup = new NioEventLoopGroup(1); //2。建立服務端啟動引導/輔助類:ServerBootstrap ServerBootstrap b = new ServerBootstrap(); boobtstrap。group(eventGroup, eventGroup) //……

2。

多執行緒模型

一個 Acceptor 執行緒只負責監聽客戶端的連線,一個 NIO 執行緒池負責具體處理:accept、read、decode、process、encode、send 事件。滿足絕大部分應用場景,併發連線量不大的時候沒啥問題,但是遇到併發連線大的時候就可能會出現問題,成為效能瓶頸。

對應到 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) //……

你要的Netty常見面試題總結,我面試回來整理好了!

3.主從多執行緒模型

從一個 主執行緒 NIO 執行緒池中選擇一個執行緒作為 Acceptor 執行緒,繫結監聽埠,接收客戶端連線的連線,其他執行緒負責後續的接入認證等工作。連線建立完成後,Sub NIO 執行緒池負責具體處理 I/O 讀寫。如果多執行緒模型無法滿足你的需求的時候,可以考慮使用主從多執行緒模型 。

// 1。bossGroup 用於接收連線,workerGroup 用於具體的處理EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try { //2。建立服務端啟動引導/輔助類:ServerBootstrap ServerBootstrap b = new ServerBootstrap(); //3。給引導類配置兩大執行緒組,確定了執行緒模型 b。group(bossGroup, workerGroup) //……

你要的Netty常見面試題總結,我面試回來整理好了!

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() { @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(); }

簡單解析一下服務端的建立過程具體是怎樣的:

1。首先你建立了兩個 NioEventLoopGroup 物件例項:bossGroup 和 workerGroup。

bossGroup : 用於處理客戶端的 TCP 連線請求。

workerGroup :負責每一條連線的具體讀寫資料的處理邏輯,真正負責 I/O 讀寫操作,交由對應的 Handler 處理。

舉個例子:我們把公司的老闆當做 bossGroup,員工當做 workerGroup,bossGroup 在外面接完活之後,扔給 workerGroup 去處理。一般情況下我們會指定 bossGroup 的 執行緒數為 1(併發連線量不大的時候) ,workGroup 的執行緒數量為

CPU 核心數 *2

。另外,根據原始碼來看,使用 NioEventLoopGroup 類的無參建構函式設定執行緒數量的預設值就是

CPU 核心數 *2

2。接下來 我們建立了一個服務端啟動引導/輔助類:ServerBootstrap,這個類將引導我們進行服務端的啟動工作。

3。透過 。group() 方法給引導類 ServerBootstrap 配置兩大執行緒組,確定了執行緒模型。

透過下面的程式碼,我們實際配置的是多執行緒模型,這個在上面提到過。

EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup();

4。透過channel()方法給引導類 ServerBootstrap指定了 IO 模型為NIO

NioServerSocketChannel :指定服務端的 IO 模型為 NIO,與 BIO 程式設計模型中的ServerSocket對應

NioSocketChannel : 指定客戶端的 IO 模型為 NIO, 與 BIO 程式設計模型中的Socket對應5。透過 。childHandler()給引導類建立一個ChannelInitializer ,然後制定了服務端訊息的業務處理邏輯 HelloServerHandler 物件6。呼叫 ServerBootstrap 類的 bind()方法繫結埠

客戶端

//1。建立一個 NioEventLoopGroup 物件例項 EventLoopGroup group = new NioEventLoopGroup(); try { //2。建立客戶端啟動引導/輔助類:Bootstrap Bootstrap b = new Bootstrap(); //3。指定執行緒組 b。group(group) //4。指定 IO 模型 。channel(NioSocketChannel。class) 。handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch。pipeline(); // 5。這裡可以自定義訊息的業務處理邏輯 p。addLast(new HelloClientHandler(message)); } }); // 6。嘗試建立連線 ChannelFuture f = b。connect(host, port)。sync(); // 7。等待連線關閉(阻塞,直到Channel關閉) f。channel()。closeFuture()。sync(); } finally { group。shutdownGracefully(); }

繼續分析一下客戶端的建立流程:

1。建立一個 NioEventLoopGroup 物件例項

2。建立客戶端啟動的引導類是 Bootstrap

3。透過 。group() 方法給引導類 Bootstrap 配置一個執行緒組

4。透過channel()方法給引導類 Bootstrap指定了 IO 模型為NIO

5。透過 。childHandler()給引導類建立一個ChannelInitializer ,然後制定了客戶端訊息的業務處理邏輯 HelloClientHandler 物件

6。呼叫 Bootstrap 類的 connect()方法進行連線,這個方法需要指定兩個引數:

inetHost : ip 地址

inetPort : 埠號

public ChannelFuture connect(String inetHost, int inetPort) { return this。connect(InetSocketAddress。createUnresolved(inetHost, inetPort)); } public ChannelFuture connect(SocketAddress remoteAddress) { ObjectUtil。checkNotNull(remoteAddress, “remoteAddress”); this。validate(); return this。doResolveAndConnect(remoteAddress, this。config。localAddress()); }

connect 方法返回的是一個 Future 型別的物件

public interface ChannelFuture extends Future { ……}

也就是說這個方是非同步的,我們透過 addListener 方法可以監聽到連線是否成功,進而打印出連線資訊。具體做法很簡單,只需要對程式碼進行以下改動:

ChannelFuture f = b。connect(host, port)。addListener(future -> { if (future。isSuccess()) { System。out。println(“連線成功!”); } else { System。err。println(“連線失敗!”); }})。sync();

什麼是 TCP 粘包/拆包?有什麼解決辦法呢?

面試官

:什麼是 TCP 粘包/拆包?

:TCP 粘包/拆包 就是你基於 TCP 傳送資料的時候,出現了多個字串“粘”在了一起或者一個字串被“拆”開的問題。比如你多次傳送:“你好,你真帥啊!哥哥!”,但是客戶端接收到的可能是下面這樣的:

你要的Netty常見面試題總結,我面試回來整理好了!

面試官

:那有什麼解決辦法呢?

1.使用 Netty 自帶的解碼器

LineBasedFrameDecoder

: 傳送端傳送資料包的時候,每個資料包之間以換行符作為分隔,LineBasedFrameDecoder 的工作原理是它依次遍歷 ByteBuf 中的可讀位元組,判斷是否有換行符,然後進行相應的擷取。

DelimiterBasedFrameDecoder

: 可以自定義分隔符解碼器,

LineBasedFrameDecoder

實際上是一種特殊的 DelimiterBasedFrameDecoder 解碼器。

FixedLengthFrameDecoder

: 固定長度解碼器,它能夠按照指定的長度對訊息進行相應的拆包。

LengthFieldBasedFrameDecoder

2.自定義序列化編解碼器

在 Java 中自帶的有實現 Serializable 介面來實現序列化,但由於它效能、安全性等原因一般情況下是不會被使用到的。

通常情況下,我們使用 Protostuff、Hessian2、json 序列方式比較多,另外還有一些序列化效能非常好的序列化方式也是很好的選擇:

專門針對 Java 語言的:Kryo,FST 等等

跨語言的:Protostuff(基於 protobuf 發展而來),ProtoBuf,Thrift,Avro,MsgPack 等等

由於篇幅問題,這部分內容會在後續的文章中詳細分析介紹~~~

Netty 長連線、心跳機制瞭解麼?

面試官

:TCP 長連線和短連線瞭解麼?

:我們知道 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 的零複製瞭解麼?

面試官

:講講 Netty 的零複製?

維基百科是這樣介紹零複製的:

零複製(英語:Zero-copy;也譯零複製)技術是指計算機執行操作時,CPU 不需要先將資料從某處記憶體複製到另一個特定區域。這種技術通常用於透過網路傳輸檔案時節省 CPU 週期和記憶體頻寬。

在 OS 層面上的 Zero-copy 通常指避免在 使用者態(User-space) 與 核心態(Kernel-space) 之間來回複製資料。而在 Netty 層面 ,零複製主要體現在對於資料操作的最佳化。

Netty 中的零複製體現在以下幾個方面:

使用 Netty 提供的 CompositeByteBuf 類, 可以將多個ByteBuf 合併為一個邏輯上的 ByteBuf, 避免了各個 ByteBuf 之間的複製。

ByteBuf 支援 slice 操作, 因此可以將 ByteBuf 分解為多個共享同一個儲存區域的 ByteBuf, 避免了記憶體的複製。

透過 FileRegion 包裝的FileChannel。tranferTo 實現檔案傳輸, 可以直接將檔案緩衝區的資料傳送到目標 Channel, 避免了傳統透過迴圈 write 方式導致的記憶體複製問題。