漫談Pipeline

前言

閱讀和理解本文需要以下技術儲備:深入理解現代計算機CPU計算架構,精通至少一門常用編程語言,至少5年web項目經驗。好吧,這是我編的,如果這三條你都滿足,對不起打擾了,關閉按鈕應該在左上角。

爲什麼會突然想到跟你們聊聊Pipeline呢?有一次跟同事一起去門口的拉麪館拉麪喫,漫長的等待中,我發現這兒的後廚師傅只有一個人,一個人負責和麪拉麪、煮熟、加湯、加牛肉、加蘿蔔、加香菜。。。因爲等的時間太長了,所以記得這麼清楚[手動捂臉]。我們等在外面就像這樣:

作爲一枚程序猿腦子裏就蹦出來Pipeline,不要把邏輯都耦合在一起啊,代碼又多又難看。我們進去不到10分鐘,排隊的人已經排到門外面去了,假如把和麪拉麪交給一個人;煮麪交給一個人;加牛肉、加蘿蔔、加香菜交給另外一個人,老闆啊你一天少說多賣100碗。

什麼是Pipeline

Pipeline的工作其實就是將一件需要重複做的事情切割成各個不同的階段,每一個階段由獨立的單元負責,所有待執行的對象依次進入作業隊列。業界用到Pipeline的地方很多,例如CPU的計算單元裏,linux管道命令裏,netty框架裏等等,今天我就以netty框架爲依託,從爲什麼要使用Pipeline,netty裏的讀寫事件是如何依託Pipeline傳播的兩點跟大家聊聊。

爲什麼要使用Pipeline

netty的使用場景裏一個最常見的就是IM系統的開發,而一個請求從客戶端發起之後,大概會經歷一下幾個步驟:

在“一系列邏輯”部分,最先讓人想到的處理方式就是根據某個標識,分辨消息類型,用if/else來進行處理,爲什麼第一反應是if/else呢?這跟我們的思維方式有很大關係,人第一反應想到的都是先幹什麼,然後再幹什麼,這樣的處理方式在邏輯很簡單的情況下既清晰又快捷,可是如果這個過程包括很多複雜的邏輯(加牛肉、加蘿蔔、加香菜),勢必會導致編寫邏輯的類越來越臃腫,直到有一天你自己看自己寫的代碼都頭皮發麻。Pipeline的作用就是組織不同處理器,在netty裏就相當於它的大動脈,一個請求在生命週期內都是在Pipeline上的不同位置扭轉的,這樣既保證了邏輯的清晰,又使代碼變得優雅。

讀寫事件在Pipeline中是如何傳播的

在聊事件傳播在之前先說下Pipeline的結構,Pipeline的結構是雙向鏈表,每個節點上是包裝了具體邏輯處理器channelHandler的channelHandlerContext對象,繞死我了,參考下圖:

Pipeline會在創建對應的channel時創建,同時會初始化Pipeline的head和tail兩個節點,而我們編寫的業務邏輯處理器,調用Pipeline的addLast方法會被添加到head節點和tail節點之間。netty的事件從類型上可以分爲inbound和outbound事件,我們在編寫具體邏輯處理器時也會繼承ChannelInboundHandlerAdapter或者ChannelOutboundHandlerAdapter,也就標識了事件處理的方向。在Pipeline的源碼裏,有個很貼心的圖,貼出來給大家回憶一下:

先說結論:inbound事件的傳播順序是和添加順序正相關,比如添加順序是A—>B—>C,則netty在處理時的順序也是A—>B—>C;而outbound事件的傳播順序是和添加順序逆相關,比如添加順序是A—>B—>C,則處理順序是C—>B—>A;下面重點以inbound事件爲例講解。

從ChannelInboundHandlerAdapter開始:

@Override

publicvoidchannelRead(ChannelHandlerContext ctx, Object msg)throwsException { ctx.fireChannelRead(msg);}

讀事件的傳播會調用ctx.fireChannelRead(msg)方法,該方法回調到AbstractChannelHandlerContext

@Override

publicChannelHandlerContext fireChannelRead(finalObject msg){ invokeChannelRead(findContextInbound(), msg);

returnthis;}

我們再看看findContextInbound()方法

privateAbstractChannelHandlerContext findContextInbound(){ AbstractChannelHandlerContext ctx = this; do { ctx = ctx.next; } while(!ctx.inbound); returnctx;}

恍然大悟有沒有,我們說過Pipeline是雙向鏈表結構,在處理完當前handler後,針對讀事件,會去從當前節點開始往後循環,找到下一個inbound事件,執行具體讀事件後繼續調用fireChannelRead,就是一個遞歸的過程;

staticvoidinvokeChannelRead(finalAbstractChannelHandlerContext next, Object msg){

finalObject m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next); EventExecutor executor = next.executor();

if(executor.inEventLoop()) {//事件在netty線程組裏next.invokeChannelRead(m); } else{ ...}

privatevoidinvokeChannelRead(Object msg){

if(invokeHandler()) {

try{ ((ChannelInboundHandler) handler()).channelRead(this, msg);//此處會調用到添加的inboundhandler } catch(Throwable t) { notifyHandlerException(t); } } else{ fireChannelRead(msg); }}

那netty又是怎麼判斷事件是inbound還是outbound的呢?

我們上面提過,Pipeline的每個節點上是一個channelHandlerContext對象,具體業務邏輯處理器在放入Pipeline之前,會被封裝成channelHandlerContext對象,到DefaultChannelPipeline裏:

@Override

publicfinalChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler){ … newCtx = newContext(group, filterName(name, handler), handler); addLast0(newCtx); …}

關鍵代碼就是這個newContext操作,可以看到這裏傳入的handler被封裝成一個channelHandlerContext對象,然後再執行addLast0操作,繼續往newContext方法裏看,來到DefaultChannelHandlerContext裏:

DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {

super(pipeline, executor, name, isInbound(handler), isOutbound(handler));

if(handler == null) {

thrownewNullPointerException("handler"); } this.handler = handler;}

我們在DefaultChannelHandlerContext的構造器裏,看到一個isInbound方法,對,沒錯,就是這裏判斷的添加的handler是入還是出,邏輯很簡單,一個instanceof關鍵字搞定。

privatestaticbooleanisInbound(ChannelHandler handler){

returnhandler instanceofChannelInboundHandler;}

還有個點需要和小夥伴們強調一下,在具體的邏輯處理器中,我們可以有兩種主動發起事件傳播的方式:

ctx.pipeline().fireChannelRead(msg);ctx.fireChannelRead(msg);

第一種是從頭節點開始往後開始查找,而第二種則是從調用方法的當前節點開始查找緊鄰的inbound事件,兩種方式的區別不言而喻,那如何選擇呢?netty這麼優秀,怎麼會讓你自己負責事件傳播這種和業務無關的事情呢?實際開發中只需要將自定義handler繼承SimpleChannelInboundHandler類,實現channelRead0方法即可,上面說的buf釋放,事件傳播,在父類裏都給幫你幹了。

結語

沒看過癮?寫長了您也沒時間看啊。留個家庭作業,各位看官自己研究下netty裏寫事件是怎麼傳播的,異常事件又是怎麼傳播的吧?提個醒:我們是聊Pipeline的,不要身陷源碼細節

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