自頂向下深入分析Netty(三)--Bootstrap

Netty的一種線(xiàn)程模型

本文開(kāi)始分析Netty的源碼,由于目標(biāo)是自頂向下分析,在這一節(jié)將分析Netty是如何構(gòu)建起如上圖所示的整體框架。首先將使用一個(gè)示例展示怎么使用Bootstarp構(gòu)建服務(wù)端應(yīng)用,然后將深入源碼了解底層機(jī)制和原理。


1.使用示例

首先使用Netty構(gòu)造如圖所示的框架,源碼如下:

    // 指定mainReactor
    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    // 指定subReactor
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    // 用戶(hù)自定義的ThreadPool
    EventExecutorGroup threadPool = new ThreadPool();
    try {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
         .channel(NioServerSocketChannel.class)
         .option(ChannelOption.SO_BACKLOG, 100) // 設(shè)置TCP參數(shù)
         .childHandler(new ChannelInitializer<SocketChannel>() {
             @Override
             public void initChannel(SocketChannel ch) throws Exception {
                 ChannelPipeline p = ch.pipeline();
                 p.addLast(threadPool,    
                    new DecoderHandler(),   // 解碼處理器
                    new ComputeHandler());  // 計(jì)算處理器
                    new EncoderHandler(),   // 編碼處理器
             }
         });

        // 綁定到本地端口等待客戶(hù)端連接
        ChannelFuture f = b.bind(PORT).sync();
        
        // 等待接受客戶(hù)端連接的Channel被關(guān)閉
        f.channel().closeFuture().sync();
    } finally {
        // 關(guān)閉兩個(gè)線(xiàn)程組
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
        threadPool.shutdown();
    }

逐行分析代碼,EventLoopGroup是Netty實(shí)現(xiàn)的線(xiàn)程池接口,兩個(gè)線(xiàn)程池:bossGroup和workerGroup分別對(duì)應(yīng)mainReactor和subReactor,其中boss專(zhuān)門(mén)用于接受客戶(hù)端連接,worker也就是常說(shuō)的IO線(xiàn)程專(zhuān)門(mén)用于處理IO事件。IO事件包括兩類(lèi),一類(lèi)如服務(wù)端接收到客戶(hù)端數(shù)據(jù)的Read事件,另一類(lèi)如用戶(hù)線(xiàn)程主動(dòng)向客戶(hù)端發(fā)送數(shù)據(jù)的Write事件。在4.0版本中,用戶(hù)自定義的業(yè)務(wù)線(xiàn)程池須實(shí)現(xiàn)EventExecutorGroup接口,4.1版本則可以直接使用JAVA自帶的線(xiàn)程池。

為了幫助用戶(hù)快速構(gòu)建基于Netty的服務(wù),Netty提供了兩個(gè)啟動(dòng)器ServerBootstrapBootstrap,分別用于啟動(dòng)服務(wù)器端和客戶(hù)端程序。group(EventLoopGroup...)方法用于指定一個(gè)或兩個(gè)Reactor,本例中指定為兩個(gè)。channel(Channel)方法本質(zhì)用來(lái)指定一個(gè)Channel工廠(chǎng),本例中該工廠(chǎng)生產(chǎn)服務(wù)端用于accept客戶(hù)端連接的Channel,將默認(rèn)使用Channel的無(wú)參構(gòu)造方法。如果用戶(hù)需要自定義有參數(shù)的Channel,可自定義所需的工廠(chǎng)實(shí)現(xiàn)。option(Key, Value)用于指定TCP相關(guān)的參數(shù)以及一些Netty自定義的參數(shù)。childHandler()用于指定subReactor中的處理器,類(lèi)似的,handler()用于指定mainReactor的處理器,只是默認(rèn)情況下mainReactor中已經(jīng)添加了acceptor處理器,所以無(wú)需再指定。需要注意的是:這兩個(gè)方法并不能累積調(diào)用而達(dá)到增加多個(gè)處理器的目的,所以引入了 ChannelInitializer,它是一個(gè)特殊的Handler,功能是初始化多個(gè)Handler,如本例中的DecoderHandlerComputeHandlerEncoderHandler。完成初始化工作后,ChannelInitializer會(huì)從Handler鏈中刪除。至此,如圖所示的框架已經(jīng)構(gòu)建完畢。

最后臨門(mén)一腳,bind(int)方法將服務(wù)端Channel綁定到本地端口,成功后將accept客戶(hù)端的連接,從而是整個(gè)框架運(yùn)行起來(lái)。使用sync()方法是由于Netty中的事件都是異步的,所以需要同步等待結(jié)果。準(zhǔn)確的說(shuō),這個(gè)方法在這里使用是有問(wèn)題的,sync()完成后只能表明綁定事件運(yùn)行完畢,但并不能說(shuō)明綁定成功,雖然失敗的可能性微乎其微。

f.channel().closeFuture().sync()方法僅僅是為了使當(dāng)前main線(xiàn)程阻塞而不立即執(zhí)行之后的各種shutdown()方法,其語(yǔ)義是等到服務(wù)端接受客戶(hù)端連接的Channel被關(guān)閉時(shí),才執(zhí)行后面代碼的操作。在實(shí)際應(yīng)用中,這樣的代碼并不實(shí)用,我們可能需要接受諸如kill命令后,優(yōu)雅關(guān)閉線(xiàn)程組。

一些情況下,我們并不使用如圖所示的結(jié)構(gòu),比如當(dāng)業(yè)務(wù)邏輯都很簡(jiǎn)單,也就是如圖所示的decode,compute,encode能在短時(shí)間完成(數(shù)十毫秒或更少),那么可以不使用業(yè)務(wù)線(xiàn)程池。代碼也很簡(jiǎn)單,只需要改動(dòng)ChannelInitializer即可:

    b.childHandler(new ChannelInitializer<SocketChannel>() {
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ChannelPipeline p = ch.pipeline();
             p.addLast(new DecoderHandler());   // 解碼處理器
             p.addLast(new ComputeHandler());   // 計(jì)算處理器
             p.addLast(new EncoderHandler());   // 編碼處理器
         }
    });

事實(shí)上這是Netty的默認(rèn)方法,也就是說(shuō)不在addLast(Handler)方法中指定線(xiàn)程池,那么將使用默認(rèn)的subReacor即woker線(xiàn)程池也即IO線(xiàn)程池執(zhí)行處理器中的業(yè)務(wù)邏輯代碼。

又比如,如開(kāi)始的例子只讓IO線(xiàn)程池處理read,write等IO事件會(huì)覺(jué)得有點(diǎn)大材小用,于是將decode和encode交給IO線(xiàn)程處理,如果此時(shí)的compute查詢(xún)需要數(shù)據(jù)庫(kù)中的數(shù)據(jù),那么代碼可改動(dòng)為如下:

    b.childHandler(new ChannelInitializer<SocketChannel>() {
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ChannelPipeline p = ch.pipeline();
             p.addLast(new DecoderHandler());   // 解碼處理器
             p.addLast(new EncoderHandler());   // 編碼處理器
             p.addLast(threadPool, new ComputeWithSqlHandler());   // 附帶SQL查詢(xún)的計(jì)算
         }
    });

最佳實(shí)踐
簡(jiǎn)單快速的業(yè)務(wù)邏輯可由IO線(xiàn)程池執(zhí)行,復(fù)雜耗時(shí)的業(yè)務(wù)(如查詢(xún)數(shù)據(jù)庫(kù),取得網(wǎng)絡(luò)連接等)使用新的業(yè)務(wù)邏輯線(xiàn)程池執(zhí)行。

本文介紹了Bootstrap的使用,如果還想知道背后的原理,可移步后續(xù)文章:自頂向下深入分析Netty(三)--Bootstrap源碼分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 長(zhǎng)文預(yù)警,本文為源碼分析部分,夾雜大量源碼可能會(huì)引起不適,請(qǐng)選擇性閱讀。如果你只想知道Bootstrap的使用,可...
    Hypercube閱讀 6,190評(píng)論 8 14
  • 前奏 https://tech.meituan.com/2016/11/04/nio.html 綜述 netty通...
    jiangmo閱讀 5,900評(píng)論 0 13
  • netty常用API學(xué)習(xí) netty簡(jiǎn)介 Netty是基于Java NIO的網(wǎng)絡(luò)應(yīng)用框架. Netty是一個(gè)NIO...
    花丶小偉閱讀 6,039評(píng)論 0 20
  • 6.2 Channel實(shí)現(xiàn) ![Netty_Channel類(lèi)圖][2] Channel的類(lèi)圖比較清晰。我們主要分析...
    Hypercube閱讀 8,567評(píng)論 6 19
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,868評(píng)論 18 139