一起來分解一個Netty應用

​前言

前面幾篇博客主要介紹到了NIO針對網絡IO場景相比較傳統的Socket通信的優勢,以及NIO在應用過程中線程模型的演化。從這篇博客開始我們一起來學習一個基於NIO實現的框架Netty,這是一個目前應用非常廣泛的通信框架。Netty所使用的線程模型就是我們上一篇博客提到的主從Reactor模型。那麼爲什麼在JDK中已經集成了NIO之後,還需要一個二次封裝的Netty。這是因爲NIO的使用門檻比較高,易用性比較低。具體可以參考我GIT上面NIO各種模式的Demo,可以看出實現一個通信模型的工作量還是比較大的,原生的NIO對用戶來講實現負擔較重。Netty的出現就是爲了幫助用戶更多的專注在自己的業務邏輯上,降低實現成本。

介紹

我們結合一個簡單的Demo來熟悉下Netty的主要API以及這些API背後的一些簡單的架構設計理念。在這個過程中,我們會將Netty中的主要接口與之前的博客結合起來,看看Netty中的API是怎麼與NIO的主從Reactor模式對應起來的。首先我們來一起看下一個Netty的Server端主要代碼。

//主reactorEventLoopGroup acceptor = new NioEventLoopGroup();//從reactorEventLoopGroup worker =new NioEventLoopGroup();try {     ServerBootstrap bootstrap = new ServerBootstrap();     bootstrap.group(acceptor, worker);       bootstrap.option(ChannelOption.SO_BACKLOG, 1024);     bootstrap.channel(NioServerSocketChannel.class);     //主ractor處理器註冊     bootstrap.handler(new LoggingHandler(LogLevel.INFO))               //從reactor處理器註冊               .childHandler(newChannelInitializer<SocketChannel>() {                    @Override                    public void initChannel(SocketChannel ch) throws Exception {                        ch.pipeline().addLast(new DecodeHandler())                        .addLast(new ProcessHandler())                        .addLast(new DumpHandler());                    }                  });     ChannelFuture f = bootstrap.bind(port).sync();     f.channel().closeFuture().sync();} catch (InterruptedException e) {     e.printStackTrace();} finally {     acceptor.shutdownGracefully();     worker.shutdownGracefully();}

上面這段程序就是Netty Server的啓動邏輯。基本的過程就是設置了一些Server的參數,設置事件監聽對象,用戶請求的處理邏輯等等。可以看到,整個實現過程很簡潔,不需要關心任何關於NIO的細節。下面讓我們一起來看下,每個接口都爲我們做了哪些工作。

ServerBootstrap

ServerBootstrap從字面翻譯過來是服務器引導程序的意思。這個類是Server端的主要類,可以看到關於服務端的所有配置和邏輯都是通過該類進行設置或者註冊的。可以認爲所有服務端的實現和配置都被包裹在ServerBootstrap之內。我們開發服務端代碼的主要目的就是構建該對象,利用接口去配置相關的參數,向其中去註冊我們的處理邏輯,最後啓動ServerBootstrap即可。

EventLoopGroup

EventLoopGroup與我們前面博客中提到的Reactor是相對應的,一個EventLoopGroup就是對應着一個Reactor對象。從Demo中可以看到定義了兩個group對象,acceptor和worker。這兩個group分別對應着我們的主從reactor。回顧下我們上一篇博客中的內容,不難想到,這兩個group都包含了可以獨立運行的線程,每個group中都封裝了NIO的Selector對象並運行獨立的事件監聽線程。其中acceptor負責客戶端的連接請求事件,worker負責數據讀取和發送事件。事實上也確實如此,EventLoopGroup的類繼承關係如下:

(該圖像摘自:https://www.cnblogs.com/duanxz/p/3724395.html)

從繼承關係上可以看出每個group都是一個獨立的線程池實現(該類的實現細節,後續在Netty源碼分析的系列博客中還會進行詳細介紹)。此處我們希望聯繫前面博客中的主從Reactor模型的驗證Netty的實現與前期的理解是一致的。

ChannelHandler

有了事件分發器,接下來就是具體的事件處理邏輯。事件的處理邏輯跟我們的業務密切相關,每個業務都有其獨特性,顯然這部分邏輯不可能由框架來實現。框架需要做的就是提供給用戶自定義的口子。這個口子就是ChannelHandler。ChannelHandler是框架提供的一個處理器基類,它定義了Event處理的接口標準,用戶通過override相應的接口來實現自己的處理邏輯。這也是大部分框架進行業務邏輯解耦的一種慣用方式。用戶自定義實現了Handler之後只需要將自己的Handler對象註冊到Bootstrap中即可。

值得注意的是,我們的框架分爲了主從Reactor來對不同的事件進行分發處理,自然Handler的註冊也分爲了兩類。因此在Demo代碼中可以看出,Handler的註冊也是分兩個不同的接口進行註冊的。Handler接口註冊的主Reactor的處理器,即負責處理accept事件,childHandler接口註冊的是從Reactor的處理器,即負責處理read,write事件。

Pipeline的設計理念

通過上面幾個核心API的串聯,我們基本上完成了整個服務端的處理過程梳理。從服務端參數的配置,事件的監聽分發和用戶自定義處理器的添加,這些基本上是一個完整的通信程序的所有要素。那麼爲什麼還要設計一個Pipeline那。

回答是功能複用和邏輯解耦。Pipeline的字面意思是管道,我更喜歡翻譯成流水線。流水線是一種生產作業方式,我們所有的生產處理過程都可以通過組合不同的基礎處理能力,以流水作業的方式來實施。而我們這裏的Pipeline起到的作用就是這個。我們每個業務的處理邏輯都是以流水線的方式,串聯一組handler組合形成的。框架爲我們定義和實現了不同handler之間進行數據傳輸和流轉的的標準。這種方式方便業務邏輯進行靈活的邏輯擴展,不需要把所有的邏輯堆砌在某一個handler中。具體來說好處有以下兩點。

第一個好處是功能複用,雖然說每個業務的處理邏輯都是不一樣的,有其獨特的地方。但是差異中也是能發掘到共同點的,這些共同點就可以作爲公用的Handler沉澱下來,其他業務在開發時就可以拿來即用(串接到Pipeline中)。甚至有些能夠形成業界統一標準的通用處理邏輯可以集成到Netty框架中供所有人使用,這就所謂的功能複用。

第二個好處是邏輯解耦。在架構整潔之道一書中提到,架構設計本身就是一門劃分邊界的藝術。是的,我們常常稱一個低耦合的軟件架構爲優秀的設計,而解耦的前提就是進行邊界劃分。將軟件架構中不容易變化的部分和容易變化的部分的邊界分析出來,然後再通過一個通用的輕量的標準紐帶聯繫起來就能完成解耦。通過Pipeline用戶就能夠輕鬆的進行這種邊界切割和邏輯解耦。當然,如果沒有Pipeline,用戶也能夠自行實現一個解耦業務邏輯。但是在Pipeline的基礎上,這種實現能夠做到事半功倍。更重要的是這種實現模式向更多的人推廣了邊界劃分的設計理念,增強了他們主動進行功能單元劃分的意識。甚至還會長遠的影響到一部分人的軟件設計理念。

         Netty客戶端的實現這裏就不介紹了,基本上跟服務端的套路差不多。

後話

搭建一個簡單的Netty應用並不難,網絡上也大量的Demo程序可以參考。但是我寫這一篇博客的初衷更多的還是希望能夠將Netty中的API與我們前面博客中提到的NIO的概念和線程模型對應起來,從而形成上下連貫。

另外Pipeline這種插件式架構的設計理念也是值得我們在自行設計的軟件中去落地實踐的。最後向大家推薦一本上面提到的書:架構整潔之道。

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