Netty核心面試題15連問

 

寫在之前

Netty有很多不錯的考察點,今天就來總結一下常見的 Netty 面試題,面試題主要來自於牛客網網友分享的面經,答案爲自己參考《Netty實戰》及衆多資料,避免閉門造車。

由於 Netty 知識可考察的點比較多,本文主要針對於 Netty 基礎提出15連問,基本上都是面試常考題目。

準備發車

1. Netty 是什麼

Netty 是一款異步的事件驅動的網絡應用程序框架,支持快速地開發可維護的高性能的面向協議的服務器 和客戶端。

2. 爲什麼要使用Netty?

這個問題也有其他的問法,比如原生NIO有什麼問題呢

  1. NIO 的類庫和API繁雜,使用麻煩:需要熟練掌握 SelectorServerSocketChannelSocketChannelByteBuffer 等。

  2. 需要具備其他的額外技能:要熟悉 Java 多線程編程,因爲 NIO 編程涉及到 Reactor 模式,你必須對多線程和網絡編程非常熟悉,才能編寫出高質量的 NIO 程序。

  3. 開發工作量和難度都非常大:例如客戶端面臨斷連重連、網絡閃斷、半包讀寫、失敗緩存、網絡擁塞和異常流的處理等等。

  4. JDK NIOBug:例如臭名昭著的 Epoll Bug,它會導致 Selector空輪詢,最終導致 CPU 100%。直到 JDK 1.7 版本該問題仍舊存在,沒有被根本解決。

3. Netty有什麼優點

主要從以下幾個方面展開回答。

設計:

  1. 統一的 API,支持多種傳輸類型,阻塞的和非阻塞的
  2. 簡單而強大的線程模型
  3. 真正的無連接數據報套接字支持
  4. 鏈接邏輯組件以支持複用

易用性:

  1. 詳實的 Javadoc 和大量的示例集

性能:

  1. 擁有比 Java 的核心 API 更高的吞吐量以及更低的延遲
  2. 得益於池化和複用,擁有更低的資源消耗
  3. 最少的內存複製

健壯性:

  1. 不會因爲慢速、快速或者超載的連接而導致 OutOfMemoryError
  2. 消除在高速網絡中 NIO 應用程序常見的不公平讀/寫比率

安全性:

  1. 完整的 SSL/TLS 以及 StartTLS 支持
  2. 可用於受限環境下,如 Applet 和 OSGI

4. netty高性能主要依賴了哪些特性

  1. IO 線程模型:同步非阻塞,用最少的資源做更多的事。
  2. 內存零拷貝:儘量減少不必要的內存拷貝,實現了更高效率的傳輸。
  3. 內存池設計:申請的內存可以重用,主要指直接內存。內部實現是用一顆二叉查找樹管理內存分配情況。
  4. 串形化處理讀寫:避免使用鎖帶來的性能開銷。
  5. 高性能序列化協議:支持 protobuf 等高性能序列化協議。

5. 爲什麼BIO比NIO性能差?簡單講講區別

BIO:服務器實現模式爲一個連接一個線程,即客戶端有連接請求時服務器端就需要啓動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷。

傳統IO的缺點:
第一,在任何 時候都可能有大量的線程處於休眠狀態,只是等待輸 入或者輸出數據就緒,這可能算是一種資源浪費
第 二,需要爲每個線程的調用棧都分配內存,其默認值 大小區間爲 64 KB 到 1 MB,具體取決於操作系統
第 三,即使 Java 虛擬機(JVM)在物理上可以支持非常大數量的線程,但是遠在到達該極限之前,上下文切換所帶來的開銷就會帶來麻煩

NIO:一個請求一個線程,但客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有I/O請求時才啓動一個線程進行處理,相對於BIO來說比較靈活。

6. 簡單說下 BIO、NIO 和 AIO區別

概念本質不同

BIO:一個連接一個線程,客戶端有連接請求時服務器端就需要啓動一個線程進行處理。線程開銷大。

NIO:一個請求一個線程,但客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有I/O請求時才啓動一個線程進行處理。

AIO:一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理。

底層實現區別

  1. BIO 以流的方式處理數據,而 NIO 以塊的方式處理數據,塊 I/O 的效率比流 I/O 高很多

  2. BIO 是阻塞的,NIO 則是非阻塞的

  3. BIO基於字節流和字符流進行操作,而 NIO 基於 Channel(通道)和 Buffer(緩衝區)進行操作,數據總是從通道讀取到緩衝區中,或者從緩衝區寫入到通道中。Selector(選擇器)用於監聽多個通道的事件(比如:連接請求,數據到達等),因此使用單個線程就可以監聽多個客戶端通道

7. 說說NIO的主要組成

1、Buffer

一個可以讀寫數據的內存塊,可以理解成是一個容器對象(含數組),該對象提供了一組方法,可以更輕鬆地使用內存塊,緩衝區對象內置了一些機制,能夠跟蹤和記錄緩衝區的狀態變化情況。與 Channel 進行交互,數據是從Channel 讀入緩衝區,從緩衝區寫入 Channel 中的。

2、Channel

NIO的通道類似於流,但有些區別

  1. 通道可以同時進行讀寫,而流只能讀或者只能寫
  2. 通道可以實現異步讀寫數據
  3. 通道可以從緩存讀數據,也可以寫數據到緩存

channel與Buffer關係

3、Selector

能夠檢測多個註冊的通道上是否有事件發生(注意:多個Channel以事件的方式可以註冊到同一個Selector),如果有事件發生,便獲取事件然後針對每個事件進行相應的處理。這樣就可以只用一個單線程去管理多個通道,也就是管理多個連接和請求。

8. 說說對於Netty的零拷貝理解

什麼是零拷貝?

從操作系統的角度來看,文件的傳輸不存在CPU的拷貝,只存在DMA拷貝(直接內存拷貝,不使用CPU完成)。零拷貝是網絡編程的關鍵,很多性能優化都離不開它。

Netty對於零拷貝方式

  1. Netty 的接收和發送 ByteBuffer 採用 DIRECT BUFFERS,使用堆外直接內存進行 Socket 讀寫,不需要進行字節緩衝區的二次拷貝。如果使用傳統的堆內存(HEAP BUFFERS)進行 Socket 讀寫,JVM 會將堆內存 Buffer 拷貝一份到直接內存中,然後才寫入Socket中。相比於堆外直接內存,消息在發送過程中多了一次緩衝區的內存拷貝。
  2. Netty 提供了組合 Buffer 對象,可以聚合多個 ByteBuffer 對象,用戶可以像操作一個 Buffer 那樣方便的對組合 Buffer 進行操作,避免了傳統通過內存拷貝的方式將幾個小 Buffer 合併成一個大的 Buffer
  3. Netty 的文件傳輸採用了 transferTo 方法,它可以直接將文件緩衝區的數據發送到目標 Channel,避免了傳統通過循環 write 方式導致的內存拷貝問題。

9. 說說Netty線程模型

Netty線程模型主要基於主從 Reactor 多線程模型做了一定的改進,其中主從Reactor多線程模型有多個 Reactor

內部實現了兩個線程池,boss 線程池和 work 線程池,其中 boss 線程池的線程負責處理請求的連接事件,當接收到連接事件的請求時,把對應的socket封裝到一個NioSocketChannel 中,並交給 work 線程池,其中 work 線程池負責請求的 readwrite 事件,由對應的 Handler 處理。

其本質將線程連接和具體的業務處理區分開來。

10. Netty中有哪些重要組件

1、Bootstrap、ServerBootstrap:一個 Netty 應用通常由一個 Bootstrap 開始,主要作用是配置整個 Netty 程序,串聯各個組件,NettyBootstrap 類是客戶端程序的啓動引導類,ServerBootstrap 是服務端啓動引導類。

2、Future、ChannelFuture:Netty 中所有的 IO 操作都是異步的,不能立刻得知消息是否被正確處理。但是可以過一會等它執行完成或者直接註冊一個監聽,具體的實現就是通過 FutureChannelFutures,他們可以註冊一個監聽,當操作執行成功或失敗時監聽會自動觸發註冊的監聽事件。

3、Channel:Netty 網絡操作抽象類,它除了包括基本的 I/O 操作,如 bind、connect、read、write 等

4、Selector:基於 Selector 對象實現I/O多路複用,通過 Selector 一個線程可以監聽多個連接的 Channel 事件,Selector 內部的機制就可以自動不斷地查詢(Select) 這些註冊的 Channel是否有已就緒的I/O 事件(例如可讀,可寫,網絡連接完成等)

5、ChannelHandler:充當了所有處理入站和出站數據的邏輯容器。ChannelHandler 主要用來處理各種事件,這裏的事件很廣泛,比如可以是連接、數據接收、異常、數據轉換等。

6、EventLoop:主要是配合 Channel 處理 I/O 操作,用來處理連接的生命週期中所發生的事情

7、ChannelPipeline:爲 ChannelHandler 鏈提供了容器,當 channel 創建時,就會被自動分配到它專屬的 ChannelPipeline,這個關聯是永久性的。

8、ChannelHandlerContext:包 含 一 個 具 體 的 事 件 處 理 器 ChannelHandler , 同 時ChannelHandlerContext 中也綁定了對應的 pipeline 和 Channel 的信息,方便對 ChannelHandler進行調用。

11. 說說什麼是拆包和粘包

TCP 是面向連接的,面向流的,提供高可靠***。

收發兩端(客戶端和服務器端)都要有一一成對的socket,因此,發送端爲了將多個發給接收端的包,更有效的發給對方,使用了優化方法(Nagle算法),將多次間隔較小且數據量小的數據,合併成一個大的數據塊,然後進行封包。這樣做雖然提高了效率,但是接收端就難於分辨出完整的數據包了,由於TCP無消息保護邊界,需要在接收端處理消息邊界問題。這就是拆包和粘包問題。

比如:

拆包和粘包圖解

假設客戶端同時發送了兩個數據包D1和D2給服務端,由於服務端一次讀取到字節數是不確定的,固可能存在以下四種情況:

  1. 服務端分兩次讀取到了兩個獨立的數據包,分別是 D1 和 D2 ,沒有粘包和拆包
  2. 服務端一次接受到了兩個數據包,D1 和 D2 粘合在一起,稱之爲 TCP 粘包
  3. 服務端分兩次讀取到了數據包,第一次讀取到了完整的 D1 包和 D2 包的部分內容,第二次讀取到了 D2 包的剩餘內容,這稱之爲 TCP 拆包
  4. 服務端分兩次讀取到了數據包,第一次讀取到了 D1 包的部分內容 D1_1 ,第二次讀取到了D1包的剩餘部分內容D1_2和完整的D2包。

12. Netty如何解決拆包和粘包問題

主要思路:在數據包的前面加上一個固定字節數的數據長度,如加上一個 int(固定四個字節)類型的數據內容長度。

就算客戶端同時發送兩個數據包到服務端,當服務端接受時,也可以先讀取四個字節的長度,然後根據長度獲取消息的內容,這樣就不會出現多讀取或者少讀取的情況了。

Netty解決拆包和粘包問題示意圖

13. Netty主要採用了哪種設計模式

Netty中利用到了衆多的設計模式,有很多常見的設計模式,比如觀察者模式、策略模式(在初始化 EventLoopGroup 時選擇何種 DefaultEventExecutorChooserFactor-newChooser 時使用了),但是使用的最多的還是屬於責任鏈模式,pipeline 就像一個責任鏈,ChannelHandler 就是其中處理邏輯的節點,通過自定義 Handler 來決定每個業務的執行邏輯。

14. 說說netty中的責任鏈設計模式

nettypipeline 設計,就採用了責任鏈設計模式,底層採用雙向鏈表的數據結構,將鏈上的各個處理器(Handler)串聯起來。

客戶端每一個請求的到來,netty 認爲,pipeline 中的所有的處理器都有機會處理它,因此,對於入棧的請求,全部從頭節點開始往後傳播,一直傳播到尾節點。

開發者可以自主的刪除或者添加責任鏈中的某個節點。

15. Netty 是如何保持長連接的

什麼是長連接?

客戶端和服務器之間定期發送的一種特殊的數據包,通知對方自己還在線, 以確保 TCP 連接的有效性。但是由於網絡不穩定性,有可能在 TCP 保持長連接的過程中,由於某些突發情況, 例如網線被拔出, 突然掉電等。 會造成服務器和客戶端的連接中斷。在這些突發情況下, 如果恰好服務器和客戶端之間沒有交互的話,那麼它們是不能在短時間內發現對方已經掉線的。

如何保持長連接?

利用心跳維護長連接信息。

在服務器和客戶端之間一定時間內沒有數據交互時,即處於 idle 狀態時,客戶端或服務器會發送一個特殊的數據包給對方,當接收方收到這個數據報文後, 也立即發送一個特殊的數據報文, 迴應發送方, 此即一個 PING-PONG 交互。

當某一端收到心跳消息後, 就知道了對方仍然在線, 這就確保 TCP 連接的有效性。

Netty有三種類型保持心跳類型

  • readerIdleTime:爲讀超時時間(即測試端一定時間內未接受到被測試端消息)。
  • writerIdleTime:爲寫超時時間(即測試端一定時間內向被測試端發送消息)。
  • allIdleTime:所有類型的超時時間。

總結

針對於 Netty ,本身利用比較廣泛,比如國內流行的 RPC 框架 Dubbo ,由於開發者本身無須深入瞭解其原理就可以很好的進行業務開發,因此許多人對於Netty瞭解甚少,但是想要了解一些進階的 Java 編程,Netty 是一個不錯的學習框架,本篇文章結合面試題開發,整體串起 Netty 的核心知識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章