<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Netty基礎(chǔ)招式——ChannelHandler的最佳實(shí)踐

          共 6657字,需瀏覽 14分鐘

           ·

          2021-08-09 13:47



          本文是Netty系列第7篇

          上一篇文章我們深入學(xué)習(xí)了Netty邏輯架構(gòu)中的核心組件EventLoop和EventLoopGroup,掌握了Netty的線程模型,并且介紹了Netty4線程模型中的無(wú)鎖串行化設(shè)計(jì)。

          今天,我們繼續(xù)學(xué)習(xí)Netty邏輯架構(gòu)中的另一個(gè)核心組件ChannelHandler和ChannelPipeline。

          如果說(shuō)線程模型是Netty的 “核心內(nèi)功”,那么ChannelHandler就是Netty最著名的 “武功招式”,是我們?nèi)粘J褂肗etty時(shí)接觸最多的組件。


          引用《Netty in action》中的一句話

          From the appliaction developer's standpoint, the primary component of Netty is the ChannelHandler.


          所以,阿丸盡可能通過(guò) 圖 和 代碼demo,來(lái)讓大家獲得最直觀的使用體驗(yàn)。

          本文預(yù)計(jì)閱讀時(shí)間約 10分鐘,將重點(diǎn)圍繞以下幾個(gè)問(wèn)題展開(kāi):

          • 什么是ChannelHandler和ChannelPipeline?

          • ChannelHandler的事件傳播機(jī)制

          • ChannelHandler的異常處理機(jī)制

          • ChannelHandler的最佳實(shí)踐

          1、什么是ChannelHandler和ChannelPipeline

          ChannelHandler是一個(gè)包含所有應(yīng)用處理邏輯的容器載體,用來(lái)對(duì)Netty的輸入輸出數(shù)據(jù)進(jìn)行加工處理。

          比如數(shù)據(jù)格式轉(zhuǎn)換、異常處理等

          ChannelPipeline 則是 ChannelHandler 的容器載體,負(fù)責(zé)以鏈?zhǔn)降男问秸{(diào)度各個(gè)注冊(cè)的ChannelHandler。

          我們回顧下之前介紹過(guò)的Netty邏輯架構(gòu),觀察下ChannelPipeline和ChannelHandler的位置。


          再?gòu)木植糠糯?,可以更加明確地看到ChannelPipeline和ChannelHandler的作用。


          如上圖所示,當(dāng)EventLoop中監(jiān)聽(tīng)到事件后,會(huì)對(duì)I/O事件進(jìn)行處理。而這個(gè)處理,就是交給ChannelPipeline進(jìn)行,更嚴(yán)格地說(shuō),是交給ChannelPipeline中的各個(gè)ChannelHandler按照一定的順序進(jìn)行處理。

          根據(jù)數(shù)據(jù)的流向,Netty把ChannelHandler分為2類,InboundHandler和OutboundHandler。


          如上圖所示,Netty接收到數(shù)據(jù)后,經(jīng)過(guò)若干 InboundHandler 處理后接收成功。如果要輸出數(shù)據(jù),就需要經(jīng)過(guò)若干個(gè) OutboundHandler 處理完成后發(fā)送。

          比如,我們經(jīng)常需要對(duì)接收到的數(shù)據(jù)進(jìn)行解碼,就是在某一個(gè)專門decode的InboundHandler中處理的。如果要發(fā)送數(shù)據(jù),往往需要編碼,就是在某一個(gè)專門encode的OutBoundHandler中處理的。

          值得一提的是,雖然我們?cè)谑褂肗etty時(shí),直接打交道的是ChannelPipeline和ChannelHandler,但是,它們之間有一座“隱形”的橋梁,名字叫做ChannelHandlerContext。

          顧名思義,ChannelHanderContext就是ChannelHandler的上下文,每個(gè) ChannelHandler 都對(duì)應(yīng)一個(gè) ChannelHandlerContext。

          每一個(gè) ChannelPipeline 都包含多個(gè) ChannelHandlerContext,所有 ChannelHandlerContext 之間組成了雙向鏈表。如下圖所示。


          其中,有兩個(gè)特殊的ChannelHandlerContext,分別是HeadContext和TailContext,表示雙向鏈表的頭尾節(jié)點(diǎn)。


          從類圖上可以看到,HeadContext同時(shí)實(shí)現(xiàn)了ChannelInboundHandler和ChannelOutboundHandler。因此,HeadContext在讀取數(shù)據(jù)時(shí)作為頭節(jié)點(diǎn),向后傳遞InBound事件,同時(shí),在寫數(shù)據(jù)時(shí)作為尾節(jié)點(diǎn),處理最后的OutBound事件。

          TailContext只實(shí)現(xiàn)了ChannelInboundHandler。它在InBound事件傳遞的末尾,負(fù)責(zé)處理一些資源釋放的工作。在OutBound事件傳遞的第一個(gè)節(jié)點(diǎn),不做任何處理,僅僅傳遞OutBound事件給prev節(jié)點(diǎn)。

          而我們平時(shí)自定義的ChannelHandler,就是插在這兩個(gè)頭尾節(jié)點(diǎn)之間的。

          至此,我們對(duì)ChannelHandler和ChannelPipeline有了基本的認(rèn)識(shí)。具體到實(shí)踐上,我們?cè)撊绾握_地使用ChannelHandler呢?

          對(duì)ChannelHandler的使用,必須先了解ChannelHandler的事件傳播機(jī)制和異常處理機(jī)制。

          2、ChannelHandler的事件傳播機(jī)制

          前面我們提到了Netty中的兩種事件類型,Inbound事件和Outbound事件,分別對(duì)應(yīng)InboundHandler和OutbountHandler進(jìn)行處理。

          當(dāng)我們使用Netty進(jìn)行開(kāi)發(fā)的時(shí)候,必須了解Inbound事件和Outbound事件在ChannelPipeline中如何進(jìn)行“事件傳播”,注冊(cè)InboundHandler和OutboundHandler的順序有什么影響。

          話不多說(shuō),我們先來(lái)一個(gè)demo直觀地感受一下。

          自定義一個(gè)ChannelInboundHandler


          自定義一個(gè)ChannelOutboundHandler


          簡(jiǎn)單組裝一下EchoPipelineServer,特別注意一下 6個(gè)handler 的注冊(cè)順序。

          然后我們通過(guò)命令行簡(jiǎn)單訪問(wèn)一下這個(gè)Netty Server

          curl localhost:8081

          可以看到控制臺(tái)的如下輸出


          這樣就清楚了事件傳播順序:
          - 對(duì)于Inbound事件,InboundHandler的處理順序是和注冊(cè)順序一致
          - 對(duì)于Outbound事件,OutboundHandler的處理順序和注冊(cè)順序相反

          結(jié)合上一節(jié)說(shuō)的HeadContext和TailContext,我們畫個(gè)圖來(lái)更直觀地看一下這個(gè)ChannelPipeline中的handler構(gòu)建順序是怎樣的。


          在上面的ChannelInitializer中,我們按需添加了3個(gè)InboundHandler和3個(gè)OutboundHandler。所以,在頭節(jié)點(diǎn)HeadContext和TailContext之間,有序構(gòu)成了雙向鏈表。

          而InboundHandler3中,通過(guò)調(diào)用 ctx.channel.writeAndFlush( msg ) 方法,將消息從TailContext開(kāi)始,依據(jù)OutboundHandler的路徑向HeadContext方向傳播出去。具體可以看下DefaultChannelPipeline類中的實(shí)現(xiàn)


          雖然這里是雙向鏈表,但是無(wú)論是Inbound事件還是Outbound事件,在按序訪問(wèn)鏈表節(jié)點(diǎn)時(shí),會(huì)根據(jù)事件類型進(jìn)行過(guò)濾。

          3、ChannelHandler的異常傳播機(jī)制

          我們已經(jīng)了解了ChannelPipeline的鏈?zhǔn)絺鬟f規(guī)則,如果雙向鏈表中任意一個(gè)handler拋出了異常,那么應(yīng)該怎么處理呢?

          3.1 InboundHandler的異常處理

          我們修改下示例中的TestInboudHandler進(jìn)行模擬。

          • channelRead方法中拋出異常

          • 重寫exceptionCaught方法,打印當(dāng)前節(jié)點(diǎn)捕獲異常情況



          得到輸出如下

          可以看到,雖然在InboundHander1中拋出了異常,但是仍然會(huì)被3個(gè)InboundHandler都捕獲一次,并按序向tail節(jié)點(diǎn)方向傳遞,然后拋出異常。

          我們也看到了,Netty給出了會(huì)警告,在最后的節(jié)點(diǎn)沒(méi)有進(jìn)行異常處理。

          An exceptionCaught() event was fired, and it reached at the tail of the pipeline. 
          It usually means the last handler in the pipeline did not handle the exception.

          3.2 OutboundHandler的異常處理

          OutboundHandler也是這么操作嗎?

          我們來(lái)做個(gè)實(shí)驗(yàn)。

          • 在write操作中拋出異常

          • 重寫下exceptionCaught方法(這個(gè)方法在OutboundHandler中被標(biāo)記為廢棄)

          重寫組裝下channelPipeline,第二個(gè)OutboundHandler中拋出異常

          結(jié)果得到的輸出如下:

          咦?異常被吃掉了!!

          不僅沒(méi)有走進(jìn)exceptionCaught方法,也沒(méi)有其他異常拋出。
          只是對(duì)后續(xù)handler的write方法不再執(zhí)行,而flush方法還是都執(zhí)行了一遍。

          我們從源碼找找原因吧。跟一下斷點(diǎn),馬上就找到了原因:


          AbstractChannelHandlerContext中,對(duì)OutboundHandler的write方法做了異常捕獲,然后對(duì)ChannelPromise進(jìn)行了通知。
          后續(xù)源碼就不展開(kāi)了,有興趣的同學(xué)自己打斷點(diǎn)跟一下,比較清楚。

          那么問(wèn)題來(lái)了,怎么在OutboundHandler中捕獲異常呢?很明顯就是直接添加ChannelPromise的回調(diào)
          上代碼:

          在前面提到的ExceptionHandler中,復(fù)寫write方法,然后注冊(cè)一個(gè)ChannelPromise的Listener就行了。

          當(dāng)然,這個(gè)ExceptionHandler同樣要注冊(cè)到ChannelPipeline。

          千萬(wàn)注意!!這里ExceptionHandler同樣是添加到ChannelPipeline的tail方向的最后,而不是添加在head方向。
          無(wú)論是inboundHandler或者是outboundHandler的異常,都是按序向tail方向傳遞的。

          異常就這樣抓到了。

          4、ChannelHandler的最佳實(shí)踐

          其實(shí)前面已經(jīng)對(duì)ChannelHandler的常用機(jī)制做了介紹,這里簡(jiǎn)單再介紹下兩個(gè)最佳實(shí)踐。

          4.1 不在ChannelHandler中耗時(shí)處理

          這一點(diǎn)其實(shí)在前一篇《 深入Netty邏輯架構(gòu),從Reactor線程模型開(kāi)始》已經(jīng)提到過(guò),這里作為自定義ChannelHandler的最佳實(shí)踐再?gòu)?qiáng)調(diào)一下,不在ChannelHandler中做耗時(shí)處理。

          這里包括兩點(diǎn)。

          • 不在I/O線程中直接處理耗時(shí)操作。

          • 也不把耗時(shí)操作放進(jìn)EventLoop的任務(wù)隊(duì)列中。

          由于Netty4的無(wú)鎖串行化設(shè)計(jì),一旦任何耗時(shí)操作阻塞了某個(gè)EventLoop,那么這個(gè)EventLoop上的各個(gè)channel都會(huì)被阻塞。更詳細(xì)內(nèi)容可以參考上一篇《 深入Netty邏輯架構(gòu),從Reactor線程模型開(kāi)始》。

          所以,我們對(duì)于耗時(shí)操作,我們要放在自己的業(yè)務(wù)線程池中進(jìn)行處理,如果需要發(fā)送response,需要提交任務(wù)到EventLoop的任務(wù)隊(duì)列中執(zhí)行。

          給個(gè)簡(jiǎn)單的demo。

          4.2 統(tǒng)一的異常處理

          在本文的第三節(jié)中,講解了ChannelHandler的異常傳播機(jī)制。

          對(duì)于InboundHandler來(lái)說(shuō),如果你有跟handler特定相關(guān)的異常,可以直接在handler里進(jìn)行exceptionCaught。如果是一些通用的異常,可以自定義ExceptionHandler注冊(cè)到ChannelPipeline的末尾進(jìn)行統(tǒng)一攔截。

          對(duì)于OutboudHandler來(lái)說(shuō),就是通過(guò)自定義ExceptionHandler,重寫對(duì)應(yīng)方法,并注冊(cè)ChannelPromise的Listener。同樣的,ExceptionHandler注冊(cè)到ChannelPipeline的末尾進(jìn)行統(tǒng)一攔截。

          所以,總結(jié)下如何添加一個(gè)“統(tǒng)一”的異常攔截器呢?

          • 自定義ExceptionHandler繼承ChannelDuplexHandler,并注冊(cè)到 tail節(jié)點(diǎn)前(ChannelPipeline的最后一個(gè)節(jié)點(diǎn))。

          • 對(duì)于Inbound事件,我們需要在exceptionCaught()進(jìn)行處理。

          • 對(duì)于Outbound事件,我們需要對(duì)OutboundHandler的不同方法(如write、flush)注冊(cè)ChannelFutureListener事件。

          異常攔截器的注冊(cè)位置應(yīng)該在tail方向的最后一個(gè)Handler。


          注意,統(tǒng)一異常處理除了更優(yōu)雅處理通用異常外,也是排查故障的好幫手。比如有時(shí)候?qū)τ诰幗獯a異常,可以在統(tǒng)一處理異常處捕獲,快速定位問(wèn)題。


          5、小結(jié)

          來(lái)簡(jiǎn)單回顧下吧。

          本文介紹了什么是ChannelHandler和ChannelPipeline。能厘清InboundChannelHandler、OutboundChannelHandler、ChannelHandlerContext是什么嗎?

          然后對(duì)ChannelHandler的事件傳播機(jī)制、異常處理機(jī)制做了詳細(xì)介紹。

          最后說(shuō)明了日常開(kāi)發(fā)中ChannelHandler的最佳實(shí)踐。

          希望對(duì)大家有所幫助。



          參考書目:
          《Netty 實(shí)戰(zhàn)》

          《Netty4核心原理與手寫RPC框架實(shí)戰(zhàn)》


          如果有任何疑問(wèn)或者建議,歡迎 寫留言 或者 微信 和我聯(lián)系哦~



          往期熱門筆記合集推薦:


          原創(chuàng):阿丸筆記(微信公眾號(hào):aone_note),歡迎 分享,轉(zhuǎn)載請(qǐng)保留出處。

          掃描下方二維碼可以關(guān)注我哦~

                                                                                        覺(jué)得不錯(cuò),就點(diǎn)個(gè) 再看 吧??


          如果有任何疑問(wèn)或者建議,歡迎 寫留言 或者 微信 和我聯(lián)系哦~

          瀏覽 82
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  美女骚逼丝袜野战视频 | 日韩电影无码 | 超碰青青操 | 五月婷婷六月天 | 国产一级a毛一级a爰片 |