Netty常见面试题 与 答案
生活随笔
收集整理的這篇文章主要介紹了
Netty常见面试题 与 答案
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Netty基礎知識
什么是Netty?
- Netty 是一款用于高效開發網絡應用的 NIO 網絡框架,它大大簡化了網絡應用的開發過程;
- 封裝了JDK底層的NIO模型,提供高度可用的API,用于快速開發高性能服務端和客戶端;
- 精心設計的 Reactor 線程模型支持高并發海量連接;
- 自帶編解碼器解決拆包和粘包問題,用戶只關心業務邏輯即可;
- 自帶各種協議棧,讓你處理任何一種通用協議幾乎都不用親自動手。
Netty對比Java NIO有哪些優勢?
- 易用性:
- 使用 JDK NIO 編程需要了解很多復雜的概念,比如 Channels、Selectors、Sockets、Buffers 等,編碼復雜程度令人發指;
- Netty 在 NIO 基礎上封裝了更加人性化的 API,統一的 API(阻塞/非阻塞) 大大降低了開發者的上手難度;
- Netty 提供了很多開箱即用的工具,例如常用的行解碼器、長度域解碼器等,而這些在 JDK NIO 中都需要你自己實現。
- 穩定性:
- Netty 更加可靠穩定,修復和完善了 JDK NIO 較多已知問題,例如臭名昭著的 select 空轉導致 CPU 消耗 100%,TCP 斷線重連,keep-alive 檢測等問題;
- 可擴展性:
- 一個是可定制化的線程模型,用戶可以通過啟動的配置參數選擇 Reactor 線程模型;
- 另一個是可擴展的事件驅動模型,將框架層和業務層的關注點分離。大部分情況下,開發者只需要關注 ChannelHandler 的業務邏輯實現。
- 更低的資源消耗:
- 對象池復用技術。 Netty 通過復用對象,避免頻繁創建和銷毀帶來的開銷;
- 零拷貝技術。 除了操作系統級別的零拷貝技術外,Netty 提供了更多面向用戶態的零拷貝技術,例如 Netty 在 I/O 讀寫時直接使用 DirectBuffer,從而避免了數據在堆內存和堆外內存之間的拷貝。
Netty與Tomcat的區別是什么?
- Netty 和 Tomcat 最大的區別在于對通信協議的支持;
- Tomcat 是一個 HTTP Server,它主要解決 HTTP 協議層的傳輸,而 Netty 不僅支持 HTTP 協議,還支持 SSH、TLS/SSL 等多種應用層的協議,而且能夠自定義應用層協議。
- Tomcat 需要遵循 Servlet 規范(HTTP協議的請求/響應模型),然而 Netty 與 Tomcat 側重點不同,所以不需要受到 Servlet 規范的約束,可以最大化發揮 NIO 特性;
- 如果僅僅需要一個 HTTP 服務器,那么推薦使用 Tomcat。術業有專攻,Tomcat 在這方面的成熟度和穩定性更好。但如果要做面向 TCP 的網絡應用開發,那么 Netty 才是最佳選擇。
什么是 Reactor 線程模型?
- 上圖是主從Reactor多線程模型:
- MainReactor 只負責監聽連接建立事件;
- SubReactor 只負責監聽讀寫事件;
- Reactor 主線程負責通過 Acceptor 對象處理 MainReactor 監聽到的連接建立事件,當Acceptor 完成網絡連接的建立之后,MainReactor 會將建立好的連接分配給 SubReactor 進行后續監聽;
- 當一個連接被分配到一個 SubReactor 之上時,會由 SubReactor 負責監聽該連接上的讀寫事件。當有新的讀事件(OP_READ)發生時,SubReactor就會調用對應的 Handler 讀取數據,然后分發給 Worker 線程池中的線程進行處理并返回結果。待處理結束之后,Handler 會根據處理結果調用 send 將響應返回給客戶端,當然此時連接要有可寫事件(OP_WRITE)才能發送數據。
- Reactor的工作流程主要分為四步:
- 連接注冊:Channel 建立后,注冊至 Reactor 線程中的 Selector 選擇器;
- 事件輪詢:輪詢 Selector 選擇器中已注冊的所有 Channel 的 I/O 事件;
- 事件分發:為準備就緒的 I/O 事件分配相應的處理線程;
- 任務處理:Reactor 線程還負責任務隊列中的非 I/O 任務,每個 Worker 線程從各自維護的任務隊列中取出任務異步執行。
Netty的架構與核心主件
Netty的工作模型是什么?
- Netty 抽象出兩組線程池:BossGroup 專門用于接收客戶端的連接,WorkerGroup 專門用于網絡的讀寫;
- BossGroup 和 WorkerGroup 類型都是 NioEventLoopGroup,相當于一個事件循環組,其中包含多個事件循環NioEventLoop;
- NioEventLoop 表示一個不斷循環的、執行處理任務的線程,每個 NioEventLoop 都有一個Selector 對象與之對應,用于監聽綁定在其上的連接,這些連接上的事件由 Selector 對應的這條線程處理;
- 每個 Boss NioEventLoop 會監聽 Selector 上連接建立的 accept 事件,然后處理 accept 事件與客戶端建立網絡連接,生成相應的 NioSocketChannel 對象,一個 NioSocketChannel 就表示一條網絡連接。之后會將 NioSocketChannel 注冊到某個 Worker NioEventLoop 上的 Selector 中。
- 每個 Worker NioEventLoop 會監聽對應 Selector 上的 read/write 事件,當監聽到 read/write 事件的時候,會通過 Pipeline 進行處理。一個 Pipeline 與一個 Channel 綁定,在 Pipeline 上可以添加多個 ChannelHandler,每個 ChannelHandler 中都可以包含一定的邏輯,例如編解碼等。Pipeline 在處理請求的時候,會按照我們指定的順序調用 ChannelHandler。
Netty的邏輯架構是怎樣的?有哪些核心組件?
- Netty 可分為網絡通信層、事件調度層、服務編排層,每一層各司其職。
- 網絡通信層:核心組件包含BootStrap、ServerBootStrap、Channel;
- Bootstrap:客戶端啟動器,只綁定一個 EventLoopGroup;
- ServerBootStrap:服務端啟動器,監聽本地端口,會綁定兩個 EventLoopGroup,分別是 Boss 和 Worker;
- Channel:可以理解為是對Socket的封裝,一個Channel代表一條新連接,對于數據的讀寫都可以在這條連接上操作;:
- 事件調度層:核心組件包含EventLoopGroup、EventLoop;
- EventLoopGroup的本質是線程池組,一個 EventLoopGroup 往往包含一個或者多個 EventLoop。EventLoop 用于處理 Channel 生命周期內的所有 I/O 事件,如 accept、connect、read、write 等 I/O 事件;
- EventLoop 的本質是線程池,每個 EventLoop 負責處理多個 Channel;
- 服務編排層:核心組件包括 ChannelPipeline、ChannelHandler、ChannelHandlerContext;
- ChannelPipeline:負責組裝各種 ChannelHandler,內部通過雙向鏈表將不同的 ChannelHandler 鏈接在一起。
- ChannelHandler:字面含義上可以反映出,ChannelHandler是用來操作Channel的,ChannelPipeline與ChannelHandler組成了一種責任鏈模式,一般ChannelHandler是直接面對開發者的,數據的編碼解碼等都是由ChannelHandler完成;
- ChannelHandlerContext:用于保存 ChannelHandler 上下文,通過 ChannelHandlerContext 我們可以知道 ChannelPipeline 和 ChannelHandler 的關聯關系。
Netty組件的工作流程是怎樣的?
- 服務端啟動初始化時有 Boss EventLoopGroup 和 Worker EventLoopGroup 兩個組件,其中 Boss 負責監聽網絡連接事件。當有新的網絡連接事件到達時,則將 Channel 注冊到 Worker EventLoopGroup;
- Worker EventLoopGroup 會被分配一個 EventLoop 負責處理該 Channel 的讀寫事件。每個 EventLoop 都是單線程的,通過 Selector 進行事件循環;
- 當客戶端發起 I/O 讀寫事件時,服務端 EventLoop 會進行數據的讀取,然后通過 Pipeline 觸發各種監聽器進行數據的加工處理;
- 客戶端數據會被傳遞到 ChannelPipeline 的第一個 ChannelInboundHandler 中,數據處理完成后,將加工完成的數據傳遞給下一個 ChannelInboundHandler;
- 當數據寫回客戶端時,會將處理結果在 ChannelPipeline 的 ChannelOutboundHandler 中傳播,最后到達客戶端。
EventLoop 是一種什么模型?
- EventLoop 這個概念其實并不是 Netty 獨有的,它是一種事件等待和處理的程序模型,可以解決多線程資源消耗高的問題。
- 每當事件發生時,應用程序都會將產生的事件放入事件隊列當中,然后 EventLoop 會輪詢從隊列中取出事件執行或者將事件分發給相應的事件監聽者執行。事件執行的方式通常分為立即執行、延后執行、定期執行幾種。
- 在Netty 中EventLoop的實現類叫做NioEventLoop,是 Reactor 線程模型的事件處理引擎。
Netty 的無鎖化設計體現在哪?
- 當accept事件觸發時,事件會被注冊到WorkerEventLoopGroup 中的一個 NioEventLoop 上;
- 由于每個請求的Channel都只與一個NioEventLoop綁定,所以說 Channel 生命周期的所有事件處理都是線程獨立的,不同的 NioEventLoop 線程之間不會發生任何交集;
- NioEventLoop 完成數據讀取后,會調用綁定的 ChannelPipeline 進行事件傳播,數據在傳播過程中由具體的ChannelHandler處理,整個過程是串行化執行,沒有線程安全問題。
Netty 是如何解決 JDK epoll 空輪詢的 Bug 的?
- Selector每次執行 select 操作之前記錄當前時間 currentTimeNanos;
- 然后計算本次select的截止時間deadline;
- 根據當前時間與截止時間比較,如果超時,結束本次select輪詢操作;
- 如果沒有超時,且任務隊列中出現任務需要處理,結束select輪詢開始處理任務;
- 如果沒有超時,且任務隊列沒有任務, 調用NIO底層的select方法進行阻塞,會一直阻塞到截止時間,同時記錄輪詢次數。阻塞可以被外部任務喚醒;
- 阻塞結束后,如果阻塞時間小于截止時間,說明阻塞被提前喚醒,如果喚醒沒有任務,說明可能觸發了空輪詢的Bug;
- Netty會對空輪詢次數進行統計,當次數達到一定閾值(512)時,重建Selector,將老的Selector上的Channel注冊到新Selector上。
ChannelPipeline中的Inbound事件與Outbound事件的區別是什么?
- ChannelPipeline是一個雙向鏈表結構,頭尾分別維護了Head節點與Tail節點,用戶自定義的ChannelHandler會被插入到Head與Tail之間;
- Inbound事件與Outbound事件是ChannelPipeline最主要的兩種事件傳播方式,兩者的主要區別是事件類型與傳播順序;
- 傳播順序:
- Inbound事件的傳播順序為:Head -> h1 -> h2 -> h3 -> Tail;
- Outbount事件的傳播順序正好相反: Tail -> h3 -> h2 -> h1 -> Head;
- 事件類型:
- Inbound事件一般指應用程序被動接收的事件,由外部觸發,例如接收了新的I/O事件,Tail節點會做一些收尾工作,如資源釋放等;
- Outbount一般由應用程序主動觸發,例如應用程序從socket讀取或寫入數據,head節點會執行操作系統底層的api完成具體的動作。
ChannelPipeline中異常傳播的順序是什么?
- 異常傳播順序與ChannelHandler的注冊順序一致,與Inbound和Outbound無關。
Netty的編解碼
什么叫做拆包與粘包?
- TCP 傳輸協議是面向流的,沒有數據包界限。客戶端向服務端發送數據時,可能將一個完整的報文拆分成多個小報文進行發送,也可能將多個報文合并成一個大的報文進行發送。因此就有了拆包和粘包。
Netty如何解決半包與粘包問題的?
- FixedLengthFrameDecoder 用來解決固定大小數據包的粘包問題;
- LineBasedFrameDecoder 適合對文本進行按行分包;
- DelimiterBasedFrameDecoder 適合按特殊字符作為分包標記的場景;
- LengthFieldBasedFrameDecoder 可以支持復雜的自定義協議分包等等。
內存管理與ByteBuf
JVM堆內內存與堆外內存的區別是什么?
- 堆內內存由 JVM GC 自動回收內存,降低了 Java 用戶的使用難度;
- 堆外內存不受 JVM 管理,使用后需要手動釋放,如果使用不當容易造成內存泄漏,且排查問題會比較困難;
- 當進行網絡 I/O 操作、文件讀寫時,堆內內存都需要轉換為堆外內存,然后再與底層設備進行交互,所以直接使用堆外內存可以減少一次內存拷貝;
- 堆外內存可以實現進程之間、JVM 多實例之間的數據共享。
Netty的零拷貝指的是什么?
- DirectByteBuffer:
- Netty提供的DirectByteBuffer,直接將數據分配到堆外內存中,避免在 Socket 讀寫時緩沖數據在堆外與堆內進行頻繁復制;
- CompositeByteBuf:
- 對于傳統的ByteBuffer,如果需要將兩個ByteBuffer中的數據組合到一起,需要首先創建一個size=size1+size2大小的新的數組,然后將兩個數組中的數據拷貝到新的數組中;
- Netty利用CompositeByteBuf可以避免這種內存拷貝,因為CompositeByteBuf并沒有真正將多個Buffer組合起來,而是保存了它們的引用,從而實現了零拷貝;
- FileRegion:
- Netty 使用 FileRegion 實現文件傳輸,FileRegion 底層封裝了 FileChannel#transferTo() 方法,可以將文件緩沖區的數據直接傳輸到目標 Channel,避免內核緩沖區和用戶態緩沖區之間的數據拷貝,這屬于操作系統級別的零拷貝。
Netty如何回收堆外內存?
- 首先Netty是通過DirectByteBuffer對象分配堆外內存的,在堆內存放的 DirectByteBuffer 對象并不大,僅僅包含堆外內存的地址、大小等屬性,同時還會創建對應的 Cleaner 對象,這個Cleaner是專門用來回收堆外內存的;
- Cleaner是JAVA四種引用類型中PhantomReference(虛引用)的子類,PhantomReference不能單獨使用,必須與ReferenceQueue聯合使用,ReferenceQueue 用于保存需要回收的 Cleaner 對象;
- 當JVM發生 GC 時,DirectByteBuffer 對象被回收,此時 Cleaner 對象不再有任何引用關系,然后被添加到ReferenceQueue中,并執行clean方法,clean() 方法主要做兩件事情:
- 將 Cleaner 對象從 Cleaner 鏈表中移除;
- 調用 unsafe.freeMemory 方法清理堆外內存;
- 總體來說,Netty是通過虛引用的特性將堆外內存對象與堆內內存對象聯系起來,然后在JVM GC時進行同步回收;
JDK NIO 的 ByteBuffer 有什么缺陷?
- ByteBuffer 分配的長度是固定的,無法動態擴縮容,所以很難控制需要分配多大的容量;
- ByteBuffer 只能通過 position 獲取當前可操作的位置,因為讀寫共用的 position 指針,所以需要頻繁調用 flip、rewind 方法切換讀寫狀態,對使用者不友好,容易出錯。
Netty 的 ByteBuf 有什么優勢?
- 容量可以按需動態擴展,類似于 StringBuffer;
- 讀寫采用了不同的指針,讀寫模式可以隨意切換,不需要調用 flip 方法;
- 通過內置的復合緩沖類型可以實現零拷貝;
- 支持引用計數;
- 支持緩存池。
Netty 的 ByteBuf有哪些分類?
- Pooled與Unpooled(池化與非池化)
- Pooled(池化)方式每次分配內存時都會從系統預先分配好的一段內存中來取;
- Unpooled(非池化)方式每次分配內存時都會調用系統API向操作系統申請內存創建ByteBuf;
- Unsafe與非Unsafe
- Unsafe會先計算數據的內存地址+偏移量,通過unsafe對象的native API來操作數據;
- 非Unsafe不依賴JDK的unsafe對象,它是通過數組+下標方式來獲取數據,或者是通過JDK 底層的ByteBuffer API進行讀寫,一般而言unsafe方式效率更高一些;
- Heap與Direct
- Heap代表堆上內存分配,會被JVM GC管理;
- Direct代表堆外內存分配,調用JDK底層API進行分配系統內存,效率更高,但不受GC直接控制,需要手動釋放內存。
Netty 的內存規格是怎樣的?
- Chunk:Netty中所有內存都是以Chunk為單位分配的,一個Chunk有16M,例如當前需要1M內存,那么就需要向系統申請一個Chunk單位的內存,然后再從這個Chunk中進一步劃分;
- Page:Chunk的劃分單位為Page,一個Page有8K,那么一個Chunk就可以劃分出2048個Page;
- SubPage:有時候我們需要的內存遠達不到一個Page的大小,那么Netty根據實際需要對Page進一步劃分成SubPage。
Netty 的內存池是如何設計的?
- Netty的內存池分四種內存規格管理內存,分別為 Tiny、Small、Normal、Huge,PoolChunk 負責管理 8K 以上的內存分配,PoolSubpage 用于管理 8K 以下的內存分配。當申請內存大于 16M 時,不會經過內存池,直接分配。
- 設計了本地線程緩存機制 PoolThreadCache,用于提升內存分配時的并發性能。用于申請 Tiny、Small、Normal 三種類型的內存時,會優先嘗試從 PoolThreadCache 中分配;
- PoolChunk 使用伙伴算法管理 Page,以二叉樹的數據結構實現,是整個內存池分配的核心所在;
- 每調用 PoolThreadCache 的 allocate() 方法到一定次數,會觸發檢查 PoolThreadCache 中緩存的使用頻率,使用頻率較低的內存塊會被釋放;
- 線程退出時,Netty 會回收該線程對應的所有內存。
Netty 的對象池是如何設計的?
- Netty 為了避免多線程競爭問題,每個線程都會持有各自的 Recycler 對象池,內部通過 FastThreadLocal 來實現每個線程的私有化;
- Recycler 有兩個重要的組成部分:Stack 和 WeakOrderQueue;
- 從 Recycler 獲取對象時,優先從 Stack 中查找,如果 Stack 沒有可用對象,會嘗試從 WeakOrderQueue 遷移部分對象到 Stack 中;
- Recycler 回收對象時,分為同線程對象回收和異線程對象回收兩種情況,同線程回收直接向 Stack 中添加對象,異線程回收向 WeakOrderQueue 中的 Link 添加對象;
- 對象回收都會控制回收速率,每 8 個對象會回收一個,其他的全部丟棄。
總結
以上是生活随笔為你收集整理的Netty常见面试题 与 答案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: opencv精要(3)-win下code
- 下一篇: opencv精要(4)-fedora下的