天天看點

Netty4詳解三:Netty架構設計

     讀完這一章,我們基本上可以了解到netty所有重要的元件,對netty有一個全面的認識,這對下一步深入學習netty是十分重要的,而學完這一章,我們其實已經可以用netty解決一些正常的問題了。

一、先縱覽一下netty,看看netty都有哪些元件?

     為了更好的了解和進一步深入netty,我們先總體認識一下netty用到的元件及它們在整個netty架構中是怎麼協調工作的。netty應用中必不可少的元件:

bootstrap or serverbootstrap

eventloop

eventloopgroup

channelpipeline

channel

future or channelfuture

channelinitializer

channelhandler

     bootstrap,一個netty應用通常由一個bootstrap開始,它主要作用是配置整個netty程式,串聯起各個元件。

     handler,為了支援各種協定和處理資料的方式,便誕生了handler元件。handler主要用來處理各種事件,這裡的事件很廣泛,比如可以是連接配接、資料接收、異常、資料轉換等。

     channelinboundhandler,一個最常用的handler。這個handler的作用就是處理接收到資料時的事件,也就是說,我們的業務邏輯一般就是寫在這個handler裡面的,channelinboundhandler就是用來處理我們的核心業務邏輯。

     channelinitializer,當一個連結建立時,我們需要知道怎麼來接收或者發送資料,當然,我們有各種各樣的handler實作來處理它,那麼channelinitializer便是用來配置這些handler,它會提供一個channelpipeline,并把handler加入到channelpipeline。

     channelpipeline,一個netty應用基于channelpipeline機制,這種機制需要依賴于eventloop和eventloopgroup,因為它們三個都和事件或者事件處理相關。

     eventloops的目的是為channel處理io操作,一個eventloop可以為多個channel服務。

     eventloopgroup會包含多個eventloop。

     channel代表了一個socket連結,或者其它和io操作相關的元件,它和eventloop一起用來參與io處理。

     future,在netty中所有的io操作都是異步的,是以,你不能立刻得知消息是否被正确處理,但是我們可以過一會等它執行完成或者直接注冊一個監聽,具體的實作就是通過future和channelfutures,他們可以注冊一個監聽,當操作執行成功或失敗時監聽會自動觸發。總之,所有的操作都會傳回一個channelfuture。

二、netty是如何處理連接配接請求和業務邏輯的呢?-- channels、events 和 io

     netty是一個非阻塞的、事件驅動的、網絡程式設計架構。當然,我們很容易了解netty會用線程來處理io事件,對于熟悉多線程程式設計的人來說,你或許會想到如何同步你的代碼,但是netty不需要我們考慮這些,具體是這樣:

      一個channel會對應一個eventloop,而一個eventloop會對應着一個線程,也就是說,僅有一個線程在負責一個channel的io操作。

     關于這些名詞之間的關系,可以見下圖:

Netty4詳解三:Netty架構設計

     如圖所示:當一個連接配接到達,netty會注冊一個channel,然後eventloopgroup會配置設定一個eventloop綁定到這個channel,在這個channel的整個生命周期過程中,都會由綁定的這個eventloop來為它服務,而這個eventloop就是一個線程。

     說到這裡,那麼eventloops和eventloopgroups關系是如何的呢?我們前面說過一個eventloopgroup包含多個eventloop,但是我們看一下下面這幅圖,這幅圖是一個繼承樹,從這幅圖中我們可以看出,eventloop其實繼承自eventloopgroup,也就是說,在某些情況下,我們可以把一個eventloopgroup當做一個eventloop來用。

Netty4詳解三:Netty架構設計

三、我們來看看如何配置一個netty應用?-- bootsstrapping

     我們利用bootsstrapping來配置netty 應用,它有兩種類型,一種用于client端:bootsstrap,另一種用于server端:serverbootstrap,要想差別如何使用它們,你僅需要記住一個用在client端,一個用在server端。下面我們來詳細介紹一下這兩種類型的差別:

     1.第一個最明顯的差別是,serverbootstrap用于server端,通過調用bind()方法來綁定到一個端口監聽連接配接;bootstrap用于client端,需要調用connect()方法來連接配接伺服器端,但我們也可以通過調用bind()方法傳回的channelfuture中擷取channel去connect伺服器端。

     2.用戶端的bootstrap一般用一個eventloopgroup,而伺服器端的serverbootstrap會用到兩個(這兩個也可以是同一個執行個體)。為何伺服器端要用到兩個eventloopgroup呢?這麼設計有明顯的好處,如果一個serverbootstrap有兩個eventloopgroup,那麼就可以把第一個eventloopgroup用來專門負責綁定到端口監聽連接配接事件,而把第二個eventloopgroup用來處理每個接收到的連接配接,下面我們用一幅圖來展現一下這種模式:

Netty4詳解三:Netty架構設計

     ps: 如果僅由一個eventloopgroup處理所有請求和連接配接的話,在并發量很大的情況下,這個eventloopgroup有可能會忙于處理已經接收到的連接配接而不能及時處理新的連接配接請求,用兩個的話,會有專門的線程來處理連接配接請求,不會導緻請求逾時的情況,大大提高了并發處理能力。

      我們知道一個channel需要由一個eventloop來綁定,而且兩者一旦綁定就不會再改變。一般情況下一個eventloopgroup中的eventloop數量會少于channel數量,那麼就很有可能出現一個多個channel公用一個eventloop的情況,這就意味着如果一個channel中的eventloop很忙的話,會影響到這個eventloop對其它channel的處理,這也就是為什麼我們不能阻塞eventloop的原因。

     當然,我們的server也可以隻用一個eventloopgroup,由一個執行個體來處理連接配接請求和io事件,請看下面這幅圖:

Netty4詳解三:Netty架構設計

四、我們看看netty是如何處理資料的?-- netty核心channelhandler

     下面我們來看一下netty中是怎樣處理資料的,回想一下我們前面講到的handler,對了,就是它。說到handler我們就不得不提channelpipeline,channelpipeline負責安排handler的順序及其執行,下面我們就來詳細介紹一下他們:

 channelpipeline and handlers

     我們的應用程式中用到的最多的應該就是channelhandler,我們可以這麼想象,資料在一個channelpipeline中流動,而channelhandler便是其中的一個個的小閥門,這些資料都會經過每一個channelhandler并且被它處理。這裡有一個公共接口channelhandler:

Netty4詳解三:Netty架構設計

     從上圖中我們可以看到,channelhandler有兩個子類channelinboundhandler和channeloutboundhandler,這兩個類對應了兩個資料流向,如果資料是從外部流入我們的應用程式,我們就看做是inbound,相反便是outbound。其實channelhandler和servlet有些類似,一個channelhandler處理完接收到的資料會傳給下一個handler,或者什麼不處理,直接傳遞給下一個。下面我們看一下channelpipeline是如何安排channelhandler的:

Netty4詳解三:Netty架構設計

     從上圖中我們可以看到,一個channelpipeline可以把兩種handler(channelinboundhandler和channeloutboundhandler)混合在一起,當一個資料流進入channelpipeline時,它會從channelpipeline頭部開始傳給第一個channelinboundhandler,當第一個處理完後再傳給下一個,一直傳遞到管道的尾部。與之相對應的是,當資料被寫出時,它會從管道的尾部開始,先經過管道尾部的“最後”一個channeloutboundhandler,當它處理完成後會傳遞給前一個channeloutboundhandler。

資料在各個handler之間傳遞,這需要調用方法中傳遞的channehandlercontext來操作, 在netty的api中提供了兩個基類分channeloutboundhandleradapter和channeloutboundhandleradapter,他們僅僅實作了調用channehandlercontext來把消息傳遞給下一個handler,因為我們隻關心處理資料,是以我們的程式中可以繼承這兩個基類來幫助我們做這些,而我們僅需實作處理資料的部分即可。

     我們知道inboundhandler和outboundhandler在channelpipeline中是混合在一起的,那麼它們如何區分彼此呢?其實很容易,因為它們各自實作的是不同的接口,對于inbound event,netty會自動跳過outboundhandler,相反若是outbound event,channelinboundhandler會被忽略掉。

     當一個channelhandler被加入到channelpipeline中時,它便會獲得一個channelhandlercontext的引用,而channelhandlercontext可以用來讀寫netty中的資料流。是以,現在可以有兩種方式來發送資料,一種是把資料直接寫入channel,一種是把資料寫入channelhandlercontext,它們的差別是寫入channel的話,資料流會從channel的頭開始傳遞,而如果寫入channelhandlercontext的話,資料流會流入管道中的下一個handler。  

五、我們最關心的部分,如何處理我們的業務邏輯? -- encoders, decoders and domain logic

     netty中會有很多handler,具體是哪種handler還要看它們繼承的是inboundadapter還是outboundadapter。當然,netty中還提供了一些列的adapter來幫助我們簡化開發,我們知道在channelpipeline中每一個handler都負責把event傳遞給下一個handler,如果有了這些輔助adapter,這些額外的工作都可自動完成,我們隻需覆寫實作我們真正關心的部分即可。此外,還有一些adapter會提供一些額外的功能,比如編碼和解碼。那麼下面我們就來看一下其中的三種常用的channelhandler:

encoders和decoders

     因為我們在網絡傳輸時隻能傳輸位元組流,是以,才發送資料之前,我們必須把我們的message型轉換為bytes,與之對應,我們在接收資料後,必須把接收到的bytes再轉換成message。我們把bytes to message這個過程稱作decode(解碼成我們可以了解的),把message to bytes這個過程成為encode。

     netty中提供了很多現成的編碼/解碼器,我們一般從他們的名字中便可知道他們的用途,如bytetomessagedecoder、messagetobyteencoder,如專門用來處理google protobuf協定的protobufencoder、 protobufdecoder。

     我們前面說過,具體是哪種handler就要看它們繼承的是inboundadapter還是outboundadapter,對于decoders,很容易便可以知道它是繼承自channelinboundhandleradapter或 channelinboundhandler,因為解碼的意思是把channelpipeline傳入的bytes解碼成我們可以了解的message(即java

object),而channelinboundhandler正是處理inbound event,而inbound event中傳入的正是位元組流。decoder會覆寫其中的“channelread()”方法,在這個方法中來調用具體的decode方法解碼傳遞過來的位元組流,然後通過調用channelhandlercontext.firechannelread(decodedmessage)方法把編碼好的message傳遞給下一個handler。與之類似,encoder就不必多少了。

domain logic

     其實我們最最關心的事情就是如何處理接收到的解碼後的資料,我們真正的業務邏輯便是處理接收到的資料。netty提供了一個最常用的基類simplechannelinboundhandler<t>,其中t就是這個handler處理的資料的類型(上一個handler已經替我們解碼好了),消息到達這個handler時,netty會自動調用這個handler中的channelread0(channelhandlercontext,t)方法,t是傳遞過來的資料對象,在這個方法中我們便可以任意寫我們的業務邏輯了。

netty從某方面來說就是一套nio架構,在java nio基礎上做了封裝,是以要想學好netty我建議先了解好java nio,建議大家閱讀一下我的另兩篇文章:

<a target="_blank" href="http://blog.csdn.net/suifeng3051/article/details/48441629">java nio詳解(二)</a>

上一篇: Hadoop簡介