自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Netty基礎(chǔ)招式——ChannelHandler的優(yōu)秀實(shí)踐

開發(fā) 架構(gòu)
今天,我們繼續(xù)學(xué)習(xí)Netty邏輯架構(gòu)中的另一個(gè)核心組件ChannelHandler和ChannelPipeline。對(duì)ChannelHandler的事件傳播機(jī)制、異常處理機(jī)制做了詳細(xì)介紹。

[[416157]]

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

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

[[416158]]

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

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

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

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

  • 什么是ChannelHandler和ChannelPipeline?
  • ChannelHandler的事件傳播機(jī)制
  • ChannelHandler的異常處理機(jī)制
  • ChannelHandler的最佳實(shí)踐

1、什么是ChannelHandler和ChannelPipeline

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

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

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

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

圖片

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

圖片

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

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

圖片

如上圖所示,Netty接收到數(shù)據(jù)后,經(jīng)過若干 InboundHandler 處理后接收成功。如果要輸出數(shù)據(jù),就需要經(jīng)過若干個(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)行開發(fā)的時(shí)候,必須了解Inbound事件和Outbound事件在ChannelPipeline中如何進(jìn)行“事件傳播”,注冊(cè)InboundHandler和OutboundHandler的順序有什么影響。

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

自定義一個(gè)ChannelInboundHandler

圖片

自定義一個(gè)ChannelOutboundHandler

圖片

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

圖片

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

  1. curl localhost:8081 

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

圖片

這樣就清楚了事件傳播順序:

  • - 對(duì)于Inbound事件,InboundHandler的處理順序是和注冊(cè)順序一致
  • - 對(duì)于Outbound事件,OutboundHandler的處理順序和注冊(cè)順序相反

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

圖片

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

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

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

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)沒有進(jìn)行異常處理。

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

3.2 OutboundHandler的異常處理

OutboundHandler也是這么操作嗎?

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

  • 在write操作中拋出異常
  • 重寫下exceptionCaught方法(這個(gè)方法在OutboundHandler中被標(biāo)記為廢棄)

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

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

咦?異常被吃掉了!!

不僅沒有走進(jìn)exceptionCaught方法,也沒有其他異常拋出。

只是對(duì)后續(xù)handler的write方法不再執(zhí)行,而flush方法還是都執(zhí)行了一遍。

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

在AbstractChannelHandlerContext中,對(duì)OutboundHandler的write方法做了異常捕獲,然后對(duì)ChannelPromise進(jìn)行了通知。

后續(xù)源碼就不展開了,有興趣的同學(xué)自己打斷點(diǎn)跟一下,比較清楚。

那么問題來了,怎么在OutboundHandler中捕獲異常呢?很明顯就是直接添加ChannelPromise的回調(diào)。

上代碼:

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

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

千萬注意!!這里ExceptionHandler同樣是添加到ChannelPipeline的tail方向的最后,而不是添加在head方向。

無論是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線程模型開始》已經(jīng)提到過,這里作為自定義ChannelHandler的最佳實(shí)踐再?gòu)?qiáng)調(diào)一下,不在ChannelHandler中做耗時(shí)處理。

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

  • 不在I/O線程中直接處理耗時(shí)操作。
  • 也不把耗時(shí)操作放進(jìn)EventLoop的任務(wù)隊(duì)列中。

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

所以,我們對(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來說,如果你有跟handler特定相關(guān)的異常,可以直接在handler里進(jìn)行exceptionCaught。如果是一些通用的異常,可以自定義ExceptionHandler注冊(cè)到ChannelPipeline的末尾進(jìn)行統(tǒng)一攔截。

對(duì)于OutboudHandler來說,就是通過自定義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)一處理異常處捕獲,快速定位問題。

5、小結(jié)

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

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

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

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

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

 

責(zé)任編輯:姜華 來源: 阿丸筆記
相關(guān)推薦

2021-10-08 09:38:57

NettyChannelHand架構(gòu)

2024-01-11 11:25:22

2019-07-15 10:39:04

云計(jì)算基礎(chǔ)設(shè)施監(jiān)控軟件

2021-04-15 08:08:48

微前端Web開發(fā)

2019-09-17 09:44:45

DockerHTMLPython

2019-11-27 10:55:36

云遷移云計(jì)算云平臺(tái)

2021-08-17 15:00:10

BEC攻擊網(wǎng)絡(luò)攻擊郵件安全

2022-12-21 08:20:01

2020-03-09 14:10:48

代碼開發(fā)工具

2021-07-06 14:17:16

MLOps機(jī)器學(xué)習(xí)AI

2019-05-07 09:00:40

無服務(wù)器Lambda管理

2021-01-20 10:53:41

云計(jì)算云存儲(chǔ)云遷移

2022-03-11 18:30:39

DevOps軟件開發(fā)

2020-11-25 10:26:24

云計(jì)算云安全數(shù)據(jù)

2021-12-17 14:06:55

云計(jì)算安全工具

2023-06-29 00:19:51

2023-07-04 15:56:08

DevOps開發(fā)測(cè)試

2023-01-13 16:34:08

2023-02-07 15:33:16

云遷移數(shù)據(jù)中心云計(jì)算

2020-05-25 11:14:59

代碼程序開發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)