dubbo剖析:五 網絡通信之 -- 請求發送與接收

注:文章中使用的dubbo源碼版本為2.5.4

零、文章目錄

  • Consumer發送請求
  • Provider接收請求并發送響應
  • Consumer接收響應

一、Consumer發送請求

1.1 代碼入口

  • dubbo剖析:二 服務引用 中講到,服務引用方根據引用接口DemoService,使用dubbo的代理工廠類JavassistProxyFactory.getProxy()創建出該接口的動態代理對象。
  • 當用戶想調用DemoService的相關方法時,實際是調用了代理對象的相關方法,從InvokerInvocationHandler.invoke()進入Consumer請求發送流程。

1.2 整體流程

Consumer發送請求流程圖
  • 上圖從上往下展示了服務引用方發送一個RPC請求的關鍵步驟,經歷了“代理層”、“集群層”、“過濾監聽擴展點”、“調用協議層”、“信息交換層”、“網絡傳輸層”。
  • 紫色實線條表示各層關鍵類的方法調用,藍色虛線表示關鍵類的初始化過程。

1)代理執行(InvokerInvocationHandler.invoke):

  • 服務引用的過程中,由ReferenceConfig使用JavassistProxyFactory為引用接口創建了代理對象;
  • 服務引用方調用dubbo代理類DemoService.sayHello時,實際執行InvokerInvocationHandler.invoke()方法,即這是Consumer發送請求的起點;
  • InvokerInvocationHandler內包含一個Invoker,在JavassistProxyFactory.getProxy()過程中通過其構造器注入,該Invoker為一個集群路由功能的AbstractClusterInvoker;

2)集群容錯+負載均衡(AbstractClusterInvoker.invoke):

  • 服務引用的過程中,由RegistryProtocol使用Cluster.join()創建集群InvokerClusterExtensionLoader.getExtensionLoader(Cluster.class).getExtension("mergeable")動態生成;
  • 集群Invoker根據負載均衡算法有多種不同實現類(failover、failfast、failsafe、failback),具體使用哪一種由對應的Cluster實現決定;
  • AbstractClusterInvoker通過Directory.list()方法獲取請求路徑對應的Invoker列表;
  • AbstractClusterInvoker再通過LoadBalance.select()方法從多個Invoker中選取一個做本次調用,即負載均衡算法(Random、RoundRobin、LeastActive);

3)Filter鏈擴展點(ProtocolFilterWrapper + ProtocolListenerWrapper):

  • ReferenceConfig進行服務引用的過程中,通過refProtocol.refer()創建Invoker對象;
  • refprotocol.refer()先后經過修飾類ProtocolFilterWrapperProtocolListenerWrapper,最后執行RegistryProtocol;ProtocolFilterWrapperProtocolListenerWrapper就是Dubbo引入的擴展點;
  • 擴展點對請求發送和接收的核心功能流程無影響,目的是以插件的方式進行一些輔助功能處理,這里不再進一步展開;

4)調用協議層執行(AbstractInvoker.invoke):

  • 經過集群路由和擴展點,現在將直接執行AbstractInvoker.invoke方法,開始真正的遠程調用了;
  • 服務引用的過程中,由RegistryDirectory使用Protocol.refer()創建遠程執行AbstractInvoker,Protocol默認采用default實現,即DubboProtocol;
  • AbstractInvoker有多種協議的具體實現(dubbo、rmi、hessian、http),具體使用哪一種協議由對應的Protocol實現決定,默認采用dubbo協議為DubboInvoker;
  • DubboInvoker中包含了ExchangeClient的引用,通過DubboInvoker的構造器注入;

5)交換層執行(ExchangeClient.request):

  • 遠程執行Invoker通過其引用的ExchangeClient.request完成遠程調用請求的發送并得到ResponseFuture,然后調用ResponseFuture.get()得到 遠程調用結果Result ;
  • 服務引用的過程中,由DubboProtocol使用Exchanger.connect()創建ExchangeClient
  • Exchanger的實現類為HeaderExchanger,由ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type)動態生成;
  • ExchangeClientClient的基礎上封裝了請求響應模式(其以Request、Response、ResponseFuture為核心,后續單獨文章講解),這也是交換層的核心功能;

6)網絡層執行(Client.send):

  • 交換層ExchangeClient.request封裝請求響應模式后,最終依賴網絡層Client.send將請求消息通過網絡發送給服務提供方;
  • 服務引用的過程中,由HeaderExchanger使用Transporter.connect()創建Client并完成初始連接操作,Client有多種網絡層實現(netty、mina...),具體使用哪一種由對應的Transporter實現決定;
  • Transporter有多種網絡層實現(netty、mina...),由ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension()動態生成,默認為NettyTransporter;
  • 最后,NettyClient使用其包含的底層NettyChannel完成網絡消息發送的功能;

二、Provider接收請求并發送響應

2.1 代碼入口

  • dubbo剖析:一 服務發布 中講到,服務提供方通過NettyServer完成服務端創建及監聽工作。
  • NettyServerdoOpen()階段創建了網絡事件處理器NettyHandler,當服務端收到客戶端消息時,將觸發NettyHandlermessageReceived()方法。

2.2 整體流程

接收請求流程圖
  • 上圖從上往下表示了服務提供方接收到一個網絡請求時的處理步驟,經歷了一個Handler處理器鏈,鏈中的每個Handler負責實現自己的處理功能。

1)Netty網絡事件處理器(NettyHandler):

  • 繼承自Netty的原生網絡時間處理器實現類SimpleChannelHandler,定義了網絡建連(channelConnected)、斷連(channelDisconnected)、消息接收(messageReceived)、異常(exceptionCaught)等事件處理方法;
  • 維護了<ip:port, channel>的對應關系Map<String, Channel>channels,在網絡建連/斷連時進行相應put/remove操作,并暴露給NettyServer使用;
  • 接收到網絡消息時,執行messageReceived()方法,將Netty的原生Channel轉換為Dubbo封裝的NettyChannel,并將事件傳遞給其包含的ChannelHandler處理;

2)復合消息處理器(MultiMessageHandler):

    public void received(Channel channel, Object message) throws RemotingException {
        if (message instanceof MultiMessage) {
            MultiMessage list = (MultiMessage) message;
            for (Object obj : list) {
                handler.received(channel, obj);
            }
        } else {
            handler.received(channel, message);
        }
    }
  • 處理MultiMessage,將其拆分成多個Message處理;

3)心跳消息處理器(HeartbeatHandler):

  • 消息收發時重置當前通道的最新消息收發時間,用于配合HeaderExchangeServerHeaderExchangeClient中的心跳檢測任務HeartBeatTask
  • 攔截并處理心跳請求/響應消息。對心跳請求消息,構建對應的心跳響應消息并通過Channel發送回去;對心跳響應消息,僅記錄日志后返回,不做功能上的處理;

4)業務線程轉換處理器(AllChannelHandler):

  • Dubbo通過該處理器完成了 IO線程業務線程 的解耦!
  • 內部封裝了業務線程池,默認使用FixedThreadPool;
public class FixedThreadPool implements ThreadPool {

    public Executor getExecutor(URL url) {
        String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
        int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
        int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
        return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
                queues == 0 ? new SynchronousQueue<Runnable>() :
                        (queues < 0 ? new LinkedBlockingQueue<Runnable>()
                                : new LinkedBlockingQueue<Runnable>(queues)),
                new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }

}

注意點:
a)線程池默認業務線程數為200
b)隊列默認采用SynchronousQueue

  • 將接收到的網絡消息事件封裝成可執行任務ChannelEventRunnable,交由業務線程池處理;

5)業務解碼處理器(DecodeHandler):

  • 進行業務請求響應的解碼工作;
  • RequestResponse中攜帶的消息體或結果體,如果其實現了Decodeable接口,則進行一次解碼處理;

6)交換層請求響應處理器(HeaderExchangeHandler):

  • 交換層真正完成請求響應收發功能的處理器!
  • 將網絡層Channel轉換為交換層ExchangeChannel,為其增加了請求響應方法request()
  • 判斷收到的網絡消息類型,根據類型分別執行不同的處理邏輯;
            if (message instanceof Request) {
                Request request = (Request) message;
                if (request.isEvent()) {
                    handlerEvent(channel, request);
                } else {
                    //case a: 請求響應模型的請求處理
                    if (request.isTwoWay()) {
                        Response response = handleRequest(exchangeChannel, request);
                        channel.send(response);
                    } 
                    //case b: 單向消息接收的處理
                    else {
                        handler.received(exchangeChannel, request.getData());
                    }
                }
            } else if (message instanceof Response) {
                //case c: 請求響應模型的響應處理
                handleResponse(channel, (Response) message);
            }

a)請求響應模型的Request消息:調用ExchangeHandlerAdapter.reply()獲取執行結果Result -->
將本地執行結果Result封裝成RPC響應Response --> 通過channel.send()發送RPC響應;

    Response handleRequest(ExchangeChannel channel, Request req) throws RemotingException {
        Response res = new Response(req.getId(), req.getVersion());
        Object msg = req.getData();
        try {
            // 調用```ExchangeHandlerAdapter.reply()```獲取執行結果```Result```
            Object result = handler.reply(channel, msg);
            res.setStatus(Response.OK);
            res.setResult(result);
        } catch (Throwable e) {
            res.setStatus(Response.SERVICE_ERROR);
            res.setErrorMessage(StringUtils.toString(e));
        }
        //將本地執行結果```Result```封裝成RPC響應```Response```
        return res;
    }

b)單向請求消息的處理:調用ExchangeHandlerAdapter.received()處理請求消息,如果該消息是Invocation則執行reply()邏輯但不主動發送RPC響應Response;

        public void received(Channel channel, Object message) throws RemotingException {
            if (message instanceof Invocation) {
                reply((ExchangeChannel) channel, message);
            } else {
                super.received(channel, message);
            }
        }

c)請求響應模型的Response消息:調用DefaultFuture.received()處理響應消息。
...注:請求響應模型(Request,Response,DufaultFuture)相關后續專門分析,此處不展開...

7)真正本地實現類方法的執行(ExchangeHandlerAdapter):

  • ExchangeHandlerAdapterDubboProtocol創建,并實現了reply()方法;
  • reply()方法,實際通過RPC調用參數InvocationDubboProtocol.exporterMap中獲取到對應的本地實現DubboExporter --> 進而獲取到對應的本地執行AbstractProxyInvoker --> 最終通過AbstractProxyInvoker.invoke()方法,以反射的方式執行真正實現類的對應方法,完成RPC請求。

三、Consumer接收響應

整體流程與 “Provider接收請求” 一樣,唯一的區別是在 交換層請求響應處理器(HeaderExchangeHandler)步驟中會執行 “分支c:請求響應模型的Response消息”,將Response交由DefaultFuture處理。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,996評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,316評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,128評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,524評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,759評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,310評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,065評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,249評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,479評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,984評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,228評論 2 375

推薦閱讀更多精彩內容

  • dubbo暴露服務有兩種情況,一種是設置了延遲暴露(比如delay="5000"),另外一種是沒有設置延遲暴露或者...
    加大裝益達閱讀 21,299評論 5 36
  • 去年10月Carol帶著三個小伙伴來上初級班時,本來我也應該一起來,可是那時候我和精油還沒有很好地鏈接上,我就不想...
    曉曉Akatsuki閱讀 689評論 0 0
  • 1. 時序圖簡介 ??時序圖(Sequence Diagram)是顯示對象之間交互的圖,這些對象是按時間順序排列的...
    GuoYuebo閱讀 1,567評論 0 1
  • 我想,最美的愛情是你在想著我的時候恰好我也在想你,放假這么多天了,每次看著視頻中的你,都會莫名的想笑??粗闵?..
    丶玩世不恭閱讀 555評論 0 1
  • 是否有一種目光 只有你我才有 在最淡然的相視里 感受最深沉的關切 是否有一條道路 只有我和你相依而行 在最平凡的腳...
    花倦琳瑯閱讀 180評論 0 2