一、把握 Netty 整体架构脉络
一、整體結構
Netty 是一個 NIO 客戶端服務器框架,可快速輕松地開發網絡應用程序,例如協議服務器和客戶端。它極大地簡化和簡化了網絡編程,例如 TCP 和 UDP 套接字服務器。“快速簡便”并不意味著最終的應用程序將遭受可維護性或性能問題的困擾。Netty 經過精心設計,結合了許多協議(例如FTP,SMTP,HTTP 以及各種基于二進制和文本的舊式協議)的實施經驗。結果,Netty 成功地找到了一種無需妥協即可輕松實現開發,性能,穩定性和靈活性的方法。
Netty 是一個設計非常用心的網絡基礎組件,結構一共分為三個模塊:
- Core 核心層
提供了底層網絡通信的通用抽象和實現,包括可擴展的事件模型、通用的通信 API、支持零拷貝的 ByteBuf 等。 - Protocol Support 協議支持層
覆蓋了主流協議的編解碼實現,如HTTP、SSL、Protobuf、壓縮、大文件傳輸、WebSocket、文本、二進制等主流協議,此外 Netty 還支持自定義應用層協議。 - Transport Service 傳輸服務層
提供了網絡傳輸能力的定義和實現方法,支持 Socket、HTTP 隧道、虛擬機管道等傳輸方式。Netty 對 TCP、UDP 等數據傳輸做了抽象和封裝。具備較高的通用性和可擴展性,它不僅是一個優秀的網絡框架,還可以作為網絡編程的工具箱。
核心邏輯架構
- Reactor 通信調度層:由一系列輔助類組成,包括 Reactor 線程 NioEventLoop 及其父類,NioSocketChannel 和 NioServerSocketChannel等等。該層的職責就是監聽網絡的讀寫和連接操作,負責將網絡層的數據讀到內存緩沖區,然后觸發各自網絡事件,例如連接創建、連接激活、讀事件、寫事件等。將這些事件觸發到 pipeline 中,由 pipeline 管理的職責鏈來進行后續的處理。
- 職責鏈 ChannelPipeline:負責事件在職責鏈中的有序傳播,以及負責動態地編排職責鏈。職責鏈可以選擇監聽和處理自己關心的事件,攔截處理和向后傳播事件。
- 業務邏輯編排層:業務邏輯編排層通常有兩類,一類是純粹的業務邏輯編排,一類是應用層協議插件,用于特定協議相關的會話和鏈路管理。由于應用層協議棧往往是開發一次到處運行,并且變動較小,故而將應用協議到 POJO 的轉變和上層業務放到不同的 ChannelHandler 中,就可以實現協議層和業務邏輯層的隔離,實現架構層面的分層隔離。
二、邏輯架構
核心組件包含BootStrap、ServerBootStrap、Channel三個組件
- BootStrap & ServerBootStrap
Bootstrap 是“引導”的意思,它主要負責整個 Netty 程序的啟動、初始化、服務器連接等過程,它相當于一條主線,串聯了 Netty 的其他核心組件。
Netty 中的引導器共分為兩種類型:
- 一個為用于客戶端引導的 Bootstrap
- 另一個為用于服務端引導的ServerBootStrap,它們都繼承自抽象類 AbstractBootstrap。
Bootstrap與ServerBootstrap 的區別?
- Bootstrap 可用于連接遠端服務器,只綁定一個 EventLoopGroup。
- ServerBootStrap則用于服務端啟動綁定本地端口,會綁定兩個 EventLoopGroup,這兩個 EventLoopGroup 通常稱為 Boss 和 Worker。這里的 Boss 和 Worker 可以理解為“老板”和“員工”的關系。每個服務器中都會有一個 Boss,也會有一群做事情的Worker。Boss 會不停地接收新的連接,然后將連接分配給一個個 Worker 處理連接。
- Channel
Channel的字面意思是“通道”,是網絡通信的載體,提供了基本的 API 用于網絡 I/O 操作,如 register、bind、connect、read、write、flush 等。
- Netty實現的 Channel 是以 JDK NIO Channel 為基礎的
- 相比較于 JDK NIO,Netty 的 Channel 提供了更高層次的抽象,同時屏蔽了底層 Socket 的復雜性,賦予了 Channel 更加強大的功能。
常用分類:
- NioServerSocketChannel 異步 TCP 服務端
- NioSocketChannel 異步 TCP 客戶端
- OioServerSocketChannel 同步 TCP 服務端
- OioSocketChannel 同步 TCP 客戶端
- NioDatagramChannel 異步 UDP 連接
- OioDatagramChannel 同步 UDP 連接
基本狀態:
- 連接建立
- 連接注冊
- 數據讀寫
- 連接銷毀
常見事件:
事件調度層的職責是通過 Reactor 線程模型對各類事件進行聚合處理,通過 Selector 主循環線程集成多種事件( I/O 事件、信號事件、定時事件等),實際的業務處理邏輯是交由服務編排層中相關的 Handler 完成。
- EventLoopGroup & EventLoop
EventLoop 則是 EventLoopGroup 的子接口,可以將其看做只有一個EventLoop的Group.
- NioEventLoopGroup 是 Netty 中最被推薦使用的線程模型,基于 NIO 模型開發,每個線程負責處理多個Channel,而同一個 Channel 只會對應一個線程。
EventLoopGroup 是 Netty Reactor 線程模型的具體實現方式。
- Reactor 的三種線程模型
負責組裝各類服務,它是 Netty 的核心處理鏈,用以實現網絡事件的動態編排和有序傳播。核心組件包括 ChannelPipeline、ChannelHandler、ChannelHandlerContext。
- ChannelPipeline
負責組裝各種 ChannelHandler,可以理解為ChannelHandler 的實例列表——內部通過雙向鏈表將不同的 ChannelHandler 鏈接在一起。當 I/O 讀寫事件觸發時,ChannelPipeline 會依次調用 ChannelHandler 列表對 Channel 的數據進行攔截和處理。ChannelPipeline 是線程安全的,因為每一個新的 Channel 都會對應綁定一個新的 ChannelPipeline。一個 ChannelPipeline 關聯一個 EventLoop,一個 EventLoop 僅會綁定一個線程。
ChannelPipeline 中包含入站 ChannelInboundHandler 和出站 ChannelOutboundHandler 兩種處理器。客戶端和服務端都有各自的 ChannelPipeline。以客戶端為例,數據從客戶端發向服務端,該過程稱為出站,反之則稱為入站。數據入站會由一系列 InBoundHandler 處理,然后再以相反方向的 OutBoundHandler 處理后完成出站。我們經常使用的編碼 Encoder 是出站操作,解碼 Decoder 是入站操作。服務端接收到客戶端數據后,需要先經過 Decoder 入站處理后,再通過 Encoder 出站通知客戶端。所以客戶端和服務端一次完整的請求應答過程可以分為三個步驟:客戶端出站(請求數據)、服務端入站(解析數據并執行業務邏輯)、服務端出站(響應結果)。
- ChannelHandler&ChannelHandlerContext
每創建一個 Channel 都會綁定一個新的 ChannelPipeline,ChannelPipeline 中每加入一個ChannelHandler 都會綁定一個 ChannelHandlerContext。ChannelHandlerContext 用于保存
ChannelHandler 上下文。
通過 ChannelHandlerContext 我們可以知道 ChannelPipeline 和 ChannelHandler 的關聯關系。ChannelHandlerContext 可以實現 ChannelHandler 之間的交互,ChannelHandlerContext 包含了 ChannelHandler 生命周期的所有事件,如 connect、bind、read、flush、write、close 等。
三、組件關系梳理
四、Netty 的數據容器(ByteBuf)
前面介紹了 Netty 的幾個核心組件,服務器在數據傳輸的時候,產生事件,并且對事件進行監控和處理。接下來看看數據是如何存放以及是如何讀寫的。Netty 將 ByteBuf 作為數據容器,來存放數據。
從結構上來說,ByteBuf 由一串字節數組構成。數組中每個字節用來存放信息。ByteBuf 提供了兩個索引,一個用于讀取數據,一個用于寫入數據。這兩個索引通過在字節數組中移動,來定位需要讀或者寫信息的位置。當從 ByteBuf 讀取時,它的 readerIndex(讀索引)將會根據讀取的字節數遞增。同樣,當寫 ByteBuf 時,它的 writerIndex 也會根據寫入的字節數進行遞增。ByteBuf 讀寫索引圖例
需要注意的是極限的情況是 readerIndex 剛好讀到了 writerIndex 寫入的地方。如果 readerIndex 超過了 writerIndex 的時候,Netty 會拋出 IndexOutOf-BoundsException 異常。
談了 ByteBuf 的工作原理以后,再來看看它的使用模式。
根據存放緩沖區的不同分為三類:
- 堆緩沖區,ByteBuf 將數據存儲在 JVM 的堆中,通過數組實現,可以做到快速分配。 由于在堆上被 JVM管理,在不被使用時可以快速釋放。可以通過 ByteBuf.array() 來獲取 byte[] 數據。
- 直接緩沖區,在 JVM的堆之外直接分配內存,用來存儲數據。其不占用堆空間,使用時需要考慮內存容量。 它在使用 Socket傳遞時性能較好,因為間接從緩沖區發送數據,在發送之前 JVM 會先將數據復制到直接緩沖區再進行發送。由于,直接緩沖區的數據分配在堆之外,通過 JVM 進行垃圾回收,并且分配時也需要做復制的操作,因此使用成本較高。
- 復合緩沖區,顧名思義就是將上述兩類緩沖區聚合在一起。Netty 提供了一個CompsiteByteBuf,可以將堆緩沖區和直接緩沖區的數據放在一起,讓使用更加方便。
聊完了結構和使用模式,再來看看 ByteBuf 是如何分配緩沖區的數據的。
Netty 提供了兩種 ByteBufAllocator 的實現,他們分別是:
- PooledByteBufAllocator,實現了 ByteBuf 的對象的池化,提高性能減少內存碎片。
- Unpooled-ByteBufAllocator,沒有實現對象的池化,每次會生成新的對象實例。
對象池化的技術和線程池,比較相似,主要目的是提高內存的使用率。池化的簡單實現思路,是在 JVM 堆內存上構建一層內存池,通過 allocate 方法獲取內存池中的空間,通過 release 方法將空間歸還給內存池。對象的生成和銷毀,會大量地調用 allocate 和 release 方法,因此內存池面臨碎片空間回收的問題,在頻繁申請和釋放空間后,內存池需要保證連續的內存空間,用于對象的分配。
五、源碼結構
1。 核心層模塊
- netty-common
是 Netty 的核心基礎包,提供了豐富的工具類
- 通用工具類:比如定時器工具 TimerTask、時間輪 HashedWheelTimer 等。
- 自定義并發包:比如異步模型Future & Promise、相比 JDK 增強的 FastThreadLocal 等。
- netty-buffer
實現了的一個更加完備的ByteBuf 工具類,用于網絡通信中的數據載體 - netty-resover
提供了一些有關基礎設施的解析工具,包括 IP Address、Hostname、DNS 等。
- netty-codec
負責編解碼工作,通過編解碼實現原始字節數據與業務實體對象之間的相互轉化。提供了抽象的編解碼類 ByteToMessageDecoder 和 MessageToByteEncoder,通過繼承這兩個類我們可以輕松實現自定義的編解碼邏輯。 - netty-handler
netty-handler 模塊提供了開箱即用的 ChannelHandler 實現類,例如日志、IP 過濾、流量整形等,如果需要這些功能,僅需在 pipeline 中加入相應的 ChannelHandler 即可。
- netty-transport
Netty 提供數據處理和傳輸的核心模塊,提供了很多非常重要的接口,如 Bootstrap、Channel、ChannelHandler、EventLoop、EventLoopGroup、ChannelPipeline 等。其中 Bootstrap 負責客戶端或服務端的啟動工作,包括創建、初始化 Channel 等;EventLoop 負責向注冊的 Channel 發起 I/O 讀寫操作;ChannelPipeline 負責 ChannelHandler 的有序編排。
六、總結
從整體結構、邏輯架構以及源碼結構對 Netty 的整體架構進行了初步介紹,可見 Netty 的分層架構設計非常合理,實現了各層之間的邏輯解耦,對于開發者來說,只需要擴展業務邏輯即可。
參考文章1
參考文章2
總結
以上是生活随笔為你收集整理的一、把握 Netty 整体架构脉络的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苏杰专访:产品创新好方向=几十年不变的需
- 下一篇: B端产品设计——批量导入