Dubbo服務(wù)調(diào)用過程

本文已同步至我的公眾號(hào) Code4j,歡迎各位看官老爺來玩昆庇。

1. 什么是遠(yuǎn)程過程調(diào)用

在講述 Dubbo 的服務(wù)調(diào)用過程之前扎阶,讓我們先來了解一下什么是遠(yuǎn)程過程調(diào)用。

遠(yuǎn)程過程調(diào)用即 Remote Producedure Call蜓洪,簡單來說就是跨進(jìn)程調(diào)用,通過網(wǎng)絡(luò)傳輸坯苹,使得 A 機(jī)器上的應(yīng)用可以像調(diào)用本地的服務(wù)一樣去調(diào)用 B 機(jī)器上的服務(wù)隆檀。

舉個(gè)最簡單的栗子,假設(shè)現(xiàn)在有一個(gè)電商系統(tǒng)北滥,其中有著用戶服務(wù)刚操,優(yōu)惠券服務(wù)闸翅,訂單服務(wù)等服務(wù)模塊再芋,這些不同的服務(wù)并不是運(yùn)行在同一個(gè) JVM 中,而是分開運(yùn)行在不同的 JVM 中坚冀。因此济赎,當(dāng)訂單服務(wù)想要調(diào)用優(yōu)惠券服務(wù)時(shí),就不能像以前的單體應(yīng)用一樣记某,直接向?qū)?yīng)服務(wù)發(fā)起本地調(diào)用司训,只能夠通過網(wǎng)絡(luò)來發(fā)起調(diào)用。

那么液南,一個(gè)最簡單的遠(yuǎn)程過程調(diào)用是怎么樣的呢壳猜?來看下面這張圖。

最簡單的調(diào)用過程

也就是說滑凉,一次最簡單的 RPC 調(diào)用统扳,無非就是調(diào)用方通過網(wǎng)絡(luò),將調(diào)用的參數(shù)傳送到服務(wù)方畅姊,服務(wù)方收到調(diào)用請(qǐng)求后咒钟,根據(jù)參數(shù)完成本地調(diào)用,并且將結(jié)果通過網(wǎng)絡(luò)傳送回調(diào)用方若未。

在這個(gè)過程中朱嘴,像參數(shù)的封裝,網(wǎng)絡(luò)傳輸?shù)燃?xì)節(jié)會(huì)由 RPC 框架來完成粗合,把上面的圖片完善一下萍嬉,一個(gè)完整的 RPC 調(diào)用的流程是這樣的:

  • 客戶端(Client)以本地調(diào)用的方式調(diào)用遠(yuǎn)程服務(wù)乌昔。
  • 客戶端代理對(duì)象(Client Stub)將本次請(qǐng)求的相關(guān)信息(要調(diào)用的類名、方法名壤追、方法參數(shù)等)封裝成 Request玫荣,并且對(duì)其序列化,為網(wǎng)絡(luò)通信做準(zhǔn)備大诸。
  • 客戶端代理對(duì)象(Client Stub)找到服務(wù)端(Server)的地址捅厂,通過網(wǎng)絡(luò)(Socket 通信)將 Request 發(fā)送到服務(wù)端。
  • 服務(wù)端代理對(duì)象(Server Stub)接收到客戶端(Client)的請(qǐng)求后资柔,將二進(jìn)制數(shù)據(jù)反序列化為 Request焙贷。
  • 服務(wù)端代理對(duì)象(Server Stub)根據(jù)調(diào)用信息向本地的方法發(fā)起調(diào)用。
  • 服務(wù)端代理對(duì)象(Server Stub)將調(diào)用后的結(jié)果封裝到 Response 中贿堰,并且對(duì)其序列化辙芍,通過網(wǎng)絡(luò)發(fā)送給客戶端。
  • 客戶端代理對(duì)象(Client Stub)收到響應(yīng)后羹与,將其反序列化為 Response故硅,遠(yuǎn)程調(diào)用結(jié)束。
調(diào)用過程核心流程

2. Dubbo 的遠(yuǎn)程調(diào)用過程

本節(jié)內(nèi)容基于 Dubbo 2.6.x 版本纵搁,并且使用官網(wǎng)提供的 Demo 對(duì)同步調(diào)用進(jìn)行分析吃衅。

在上一節(jié)內(nèi)容中,我們已經(jīng)對(duì)服務(wù)調(diào)用的過程有了一定的了解腾誉。實(shí)際上徘层,Dubbo 在實(shí)現(xiàn)遠(yuǎn)程調(diào)用的時(shí)候,核心流程和上面的圖片是完全一樣的利职,只不過 Dubbo 在此基礎(chǔ)上增加了一些額外的流程趣效,例如集群容錯(cuò)、負(fù)載均衡猪贪、過濾器鏈等跷敬。

本篇文章只分析核心的調(diào)用流程,其它的額外流程可以自行了解热押。

在講解 Dubbo 的調(diào)用過程之前西傀,我們先來了解一下 Dubbo 的一些概念。

  • Invoker:在 Dubbo 中作為實(shí)體域楞黄,也就是代表了要操作的對(duì)象模型池凄,這有點(diǎn)像 Spring 中的 Bean,所有的操作都是圍繞著這個(gè)實(shí)體域來進(jìn)行鬼廓。

    • 代表了一個(gè)可執(zhí)行體肿仑,可以向它發(fā)起 invoke 調(diào)用。它有可能是一個(gè)本地實(shí)現(xiàn),也有可能是一個(gè)遠(yuǎn)程實(shí)現(xiàn)尤慰,也有可能是一個(gè)集群實(shí)現(xiàn)馏锡。
  • Invocation:在 Dubbo 中作為會(huì)話域,表示每次操作的瞬時(shí)狀態(tài)伟端,操作前創(chuàng)建杯道,操作后銷毀。

    • 其實(shí)就是調(diào)用信息责蝠,存放了調(diào)用的類名党巾、方法名、參數(shù)等信息霜医。
  • Protocol:在 Dubbo 作為服務(wù)域齿拂,負(fù)責(zé)實(shí)體域和會(huì)話域的生命周期管理。

    • 可以理解為 Spring 中的 BeanFactory肴敛,是產(chǎn)品的入口署海。

2.1 遠(yuǎn)程調(diào)用的開端 —— 動(dòng)態(tài)代理

在了解以上基本概念后,我們開始來跟蹤 Dubbo 的遠(yuǎn)程調(diào)用流程医男。在 RPC 框架中砸狞,想要實(shí)現(xiàn)遠(yuǎn)程調(diào)用,代理對(duì)象是不可或缺的镀梭,因?yàn)樗梢詭臀覀兤帘魏芏嗟讓蛹?xì)節(jié)刀森,使得我們對(duì)遠(yuǎn)程調(diào)用無感知。

如果用過 JDK 的動(dòng)態(tài)代理或者是 CGLIB 的動(dòng)態(tài)代理丰辣,那么應(yīng)該都知道每個(gè)代理對(duì)象都會(huì)有對(duì)應(yīng)的一個(gè)處理器撒强,用于處理動(dòng)態(tài)代理時(shí)的增強(qiáng),例如 JDK 使用的 InvacationHandler 或者 CGLIB 的 MethodInterceptor笙什。在 Dubbo 中,默認(rèn)是使用 javasisst 來實(shí)現(xiàn)動(dòng)態(tài)代理的胚想,它與 JDK 動(dòng)態(tài)一樣使用 InvocationHandler 來進(jìn)行代理增強(qiáng)琐凭。

InvokerInvocationHandler

下面分別是使用 javasisst 和使用 JDK 動(dòng)態(tài)代理時(shí)對(duì)代理類進(jìn)行反編譯后的結(jié)果。

代理類反編譯結(jié)果

從上面可以看出浊服,InvacationHandler 要做的事無非就是根據(jù)本次調(diào)用的方法名和方法參數(shù)统屈,將其封裝成調(diào)用信息 Invacation,然后將其傳遞給持有的 Invoker 對(duì)象牙躺。從這里開始愁憔,才算是真正進(jìn)入到了 Dubbo 的核心模型中。

2.2 客戶端的調(diào)用鏈路

在了解客戶端的調(diào)用鏈路之前孽拷,我們需要先看一下 Dubbo 的整體設(shè)計(jì)吨掌,下圖是來自于 Dubbo 官網(wǎng)的一張框架設(shè)計(jì)圖,很好地展示了整個(gè)框架的結(jié)構(gòu)。

Dubbo 整體框架設(shè)計(jì)

為了容易理解膜宋,我把上圖中的 Proxy 代理層窿侈、Cluster 集群層以及 Protocol 協(xié)議層進(jìn)行了一個(gè)抽象。

如下圖所示秋茫, Dubbo 的 Proxy 代理層先與下層的 Cluster 集群層進(jìn)行交互史简。Cluster 這一層的作用就是將多個(gè) Invoker 偽裝成一個(gè) ClusterInvoker 后暴露給上層使用,由該 ClusterInvoker 來負(fù)責(zé)容錯(cuò)的相關(guān)邏輯肛著,例如快速失敗圆兵,失敗重試等等。對(duì)于上層的 Proxy 來說枢贿,這一層的容錯(cuò)邏輯是透明的衙傀。

Proxy 到 Invoker 的大概流程

因此,當(dāng) Proxy 層的 InvocationHandler 將調(diào)用請(qǐng)求委托給持有的 Invoker 時(shí)萨咕,其實(shí)就是向下傳遞給對(duì)應(yīng)的 ClusterInvoker统抬,并且經(jīng)過獲取可用 Invoker,根據(jù)路由規(guī)則過濾 Invoker危队,以及負(fù)載均衡選中要調(diào)用的 Invoker 等一系列操作后聪建,就會(huì)得到一個(gè)具體協(xié)議的 Invoker

這個(gè)具體的 Invoker 可能是一個(gè)遠(yuǎn)程實(shí)現(xiàn)茫陆,例如默認(rèn)的 Dubbo 協(xié)議對(duì)應(yīng)的 DubboInvoker金麸,也有可能是一個(gè)本地實(shí)現(xiàn),例如 Injvm 協(xié)議對(duì)應(yīng)的 InjvmInvoker 等簿盅。

關(guān)于集群相關(guān)的 Invoker挥下,如果有興趣的話可以看一下用于服務(wù)降級(jí)的 MockClusterInvoker,集群策略抽象父類 AbstractClusterInvoker 以及默認(rèn)的也是最常用的失敗重試集群策略 FailoverClusterInvoker桨醋,實(shí)際上默認(rèn)情況下的集群調(diào)用鏈路就是逐個(gè)經(jīng)過這三個(gè)類的棚瘟。

順帶提一句,在獲取到具體的協(xié)議 Invoker 之前會(huì)經(jīng)過一個(gè)過濾器鏈喜最,對(duì)于每一個(gè)過濾器對(duì)于本次請(qǐng)求都會(huì)做一些處理偎蘸,比如用于統(tǒng)計(jì)的 MonitorFilter,用于處理當(dāng)前上下文信息的 ConsumerContextFilter 等等瞬内。過濾器這一部分給用戶提供了很大的擴(kuò)展空間迷雪,有興趣的話可以自行了解。

拿到具體的 Invoker 之后虫蝶,此時(shí)所處的位置為上圖中的 Protocol 層章咧,這時(shí)候就可以通過下層的網(wǎng)絡(luò)層來完成遠(yuǎn)程過程調(diào)用了,先來看一下 DubboInvoker 的源碼能真。

DubboInvoker

可以看到赁严,Dubbo 對(duì)于調(diào)用方式做了一些區(qū)分扰柠,分別為同步調(diào)用,異步調(diào)用以及單次調(diào)用误澳。

首先有一點(diǎn)要明確的是耻矮,同步調(diào)用也好,異步調(diào)用也好忆谓,這都是站在用戶的角度來看的裆装,但是在網(wǎng)絡(luò)這一層面的話,所有的交互都是異步的倡缠,網(wǎng)絡(luò)框架只負(fù)責(zé)將數(shù)據(jù)發(fā)送出去哨免,或者將收到的數(shù)據(jù)向上傳遞,網(wǎng)絡(luò)框架并不知道本次發(fā)送出去的二進(jìn)制數(shù)據(jù)和收到的二進(jìn)制的數(shù)據(jù)是否是一一對(duì)應(yīng)的昙沦。

因此琢唾,當(dāng)用戶選擇同步調(diào)用的時(shí)候,為了將底層的異步通信轉(zhuǎn)化為同步操作盾饮,這里 Dubbo 需要調(diào)用某個(gè)阻塞操作采桃,使用戶線程阻塞在這里,直到本次調(diào)用的結(jié)果返回丘损。

2.3 遠(yuǎn)程調(diào)用的基石 —— 網(wǎng)絡(luò)層

在上一小節(jié)的 DubboInvoker 當(dāng)中普办,我們可以看到遠(yuǎn)程調(diào)用的請(qǐng)求是通過一個(gè) ExchangeClient 的類發(fā)送出去的,這個(gè) ExchangeClient 類處于 Dubbo 框架的遠(yuǎn)程通信模塊中的 Exchange 信息交換層徘钥。

從前面出現(xiàn)過的架構(gòu)圖中可以看到衔蹲,遠(yuǎn)程通信模塊共分為三層,從上到下分別是 Exchange 信息交換層呈础,Transport 網(wǎng)絡(luò)傳輸層以及 Serialize 序列化層舆驶,每一層都有其特定的作用。

從最底層的 Serialize 層說起而钞,這一層的作用就是負(fù)責(zé)序列化/反序列化沙廉,它對(duì)多種序列化方式進(jìn)行了抽象,如 JDK 序列化笨忌,Hessian 序列化蓝仲,JSON 序列化等。

往上則是 Transport 層官疲,這一層負(fù)責(zé)的單向的消息傳輸,強(qiáng)調(diào)的是一種 Message 的語義亮隙,不體現(xiàn)交互的概念途凫。同時(shí)這一層也對(duì)各種 NIO 框架進(jìn)行了抽象,例如 Netty溢吻,Mina 等等维费。

再往上就是 Exhange 層果元,和 Transport 層不同,這一層負(fù)責(zé)的是請(qǐng)求/響應(yīng)的交互犀盟,強(qiáng)調(diào)的一種 RequestReponse 的語義而晒,也正是由于請(qǐng)求響應(yīng)的存在,才會(huì)有 ClientServer 的區(qū)分阅畴。

遠(yuǎn)程通信模塊分層結(jié)構(gòu)

了解完遠(yuǎn)程通信模塊的分層結(jié)構(gòu)后倡怎,我們?cè)賮砜匆幌略撃K中的核心概念。

Dubbo 在這個(gè)模塊中抽取出了一個(gè)端點(diǎn) Endpoint 的概念贱枣,通過一個(gè) IP 和 一個(gè) Port监署,就可以唯一確定一個(gè)端點(diǎn)。在這兩個(gè)端點(diǎn)之間纽哥,我們可以建立 TCP 連接钠乏,而這個(gè)連接被 Dubbo 抽象成了通道 Channel,通道處理器 ChannelHandler 則負(fù)責(zé)對(duì)通道進(jìn)行處理春塌,例如處理通道的連接建立事件晓避、連接斷開事件,處理讀取到的數(shù)據(jù)只壳、發(fā)送的數(shù)據(jù)以及捕獲到的異常等俏拱。

同時(shí),為了在語義上對(duì)端點(diǎn)進(jìn)行區(qū)分吕世,Dubbo 將發(fā)起請(qǐng)求的端點(diǎn)抽象為客戶端 Client彰触,而發(fā)送響應(yīng)的端點(diǎn)則抽象成服務(wù)端 Server。由于不同的 NIO 框架對(duì)外接口和使用方式不一樣命辖,所以為了避免上層接口直接依賴具體的 NIO 庫况毅,Dubbo 在 ClientServer 之上又抽象出了一個(gè) Transporter 接口,該接口用于獲取 ClientServer尔艇,后續(xù)如果需要更換使用的 NIO 庫尔许,那么只需要替換相關(guān)實(shí)現(xiàn)類即可。

Dubbo 將負(fù)責(zé)數(shù)據(jù)編解碼功能的處理器抽象成了 Codec 接口终娃,有興趣的話可以自行了解味廊。

網(wǎng)絡(luò)通信抽象圖

Endpoint 主要的作用就是發(fā)送數(shù)據(jù),因此 Dubbo 為其定義了 send() 方法棠耕;同時(shí)余佛,讓 Channel 繼承 Endpoint,使其在發(fā)送數(shù)據(jù)的基礎(chǔ)上擁有添加 K/V 屬性的功能窍荧。

對(duì)于客戶端來說辉巡,一個(gè) Cleint 只會(huì)關(guān)聯(lián)著一個(gè) Channel,因此直接繼承 Channel 使其也具備發(fā)送數(shù)據(jù)的功能即可蕊退,而 Server 可以接受多個(gè) Cleint 建立的 Channel 連接郊楣,所以 Dubbo 沒有讓其繼承 Channel憔恳,而是選擇讓其直接繼承 Endpoint,并且提供了 getChannels() 方法用于獲取關(guān)聯(lián)的連接净蚤。

為了體現(xiàn)了請(qǐng)求/響應(yīng)的交互模式钥组,在 ChannelServer 以及 Client 的基礎(chǔ)上進(jìn)一步抽象出 ExchangeChannel今瀑、ExchangeServer 以及 ExchangeClient 接口程梦,并為 ExchangeChannel 接口添加 request() 方法,具體類圖如下放椰。

遠(yuǎn)程通信模塊類圖

了解完網(wǎng)絡(luò)層的相關(guān)概念后作烟,讓我們看回 DubboInvoker,當(dāng)同步調(diào)用時(shí)砾医,DubboInvoker 會(huì)通過持有的 ExchangeClient 來發(fā)起請(qǐng)求拿撩。實(shí)際上,這個(gè)調(diào)用最后會(huì)被 HeaderExchangeChannel 類所接收如蚜,這是一個(gè)實(shí)現(xiàn)了 ExchangeChannel 的類压恒,因此也具備請(qǐng)求的功能。

HeaderExchangeChannel

可以看到错邦,其實(shí) request() 方法只不過是將數(shù)據(jù)封裝成 Request 對(duì)象探赫,構(gòu)造一個(gè)請(qǐng)求的語義,最終還是通過 send() 方法將數(shù)據(jù)單向發(fā)送出去撬呢。下面是一張關(guān)于客戶端發(fā)送請(qǐng)求的調(diào)用鏈路圖伦吠。

客戶端發(fā)送請(qǐng)求

這里值得注意的是 DefaultFuture 對(duì)象的創(chuàng)建。DefaultFuture 類是 Dubbo 參照 Java 中的 Future 類所設(shè)計(jì)的魂拦,這意味著它可以用于異步操作毛仪。每個(gè) Request 對(duì)象都有一個(gè) ID,當(dāng)創(chuàng)建 DefaultFuture 時(shí)芯勘,會(huì)將請(qǐng)求 ID 和創(chuàng)建的 DefaultFutrue 映射給保存起來箱靴,同時(shí)設(shè)置超時(shí)時(shí)間。

保存映射的目的是因?yàn)樵诋惒角闆r下荷愕,請(qǐng)求和響應(yīng)并不是一一對(duì)應(yīng)的衡怀。為了使得后面接收到的響應(yīng)可以正確被處理,Dubbo 會(huì)在響應(yīng)中帶上對(duì)應(yīng)的請(qǐng)求 ID安疗,當(dāng)接收到響應(yīng)后抛杨,根據(jù)其中的請(qǐng)求 ID 就可以找到對(duì)應(yīng)的 DefaultFuture,并將響應(yīng)結(jié)果設(shè)置到 DefaultFuture荐类,使得阻塞在 get() 操作的用戶線程可以及時(shí)返回蝶桶。

DefaultFuture 構(gòu)造函數(shù)

整個(gè)過程可以抽象為下面的時(shí)序圖。

DefaultFuture 的作用

當(dāng) ExchangeChannel 調(diào)用 send() 后掉冶,數(shù)據(jù)就會(huì)通過底層的 NIO 框架發(fā)送出去真竖,不過在將數(shù)據(jù)通過網(wǎng)絡(luò)傳輸之前,還有最后一步需要做的厌小,那就是序列化和編碼恢共。

注意,在調(diào)用 send() 方法之前璧亚,所有的邏輯都是用戶線程在處理的讨韭,而編碼工作則是由 Netty 的 I/O 線程處理,有興趣的話可以了解一下 Netty 的線程模型癣蟋。

2.4 協(xié)議和編碼

上文提到過很多次協(xié)議(Protocol)和編碼透硝,那么到底什么是協(xié)議,什么又是編碼呢?

其實(shí)疯搅,通俗一點(diǎn)講濒生,協(xié)議就是一套約定好的通信規(guī)則。打個(gè)比方幔欧,張三和李四要進(jìn)行交流罪治,那么他們之間在交流之前就需要先約定好如何交流,比如雙方約定礁蔗,當(dāng)聽到“Hello World”的時(shí)候觉义,就代表對(duì)方要開始講話了。此時(shí)浴井,張三和李四之間的這種約定就是他們的通信協(xié)議晒骇。

而對(duì)于編碼的話,其實(shí)就是根據(jù)約定好的協(xié)議磺浙,將數(shù)據(jù)組裝成協(xié)議規(guī)定的格式洪囤。當(dāng)張三想和李四說“早上好”的時(shí)候,那么張三只需要在“早上好”之前加上約定好的“Hello World”屠缭,也就是最終的消息為“Hello World 早上好”箍鼓。李四一聽到“Hello World”,就知道隨后的內(nèi)容是張三想說的呵曹,通過這種形式款咖,張三和李四之間就可以完成正常的交流了。

具體到實(shí)際的 RPC 通信中奄喂,所謂的 Dubbo 協(xié)議铐殃,RMI 協(xié)議,HTTP 協(xié)議等等跨新,它們只不過是對(duì)應(yīng)的通信規(guī)則不一樣富腊,但最終的作用都是一樣的,就是提供給組裝通信數(shù)據(jù)的一套規(guī)則域帐,僅此而已赘被。

這里借用一張官網(wǎng)的圖是整,展示了默認(rèn)的 Dubbo 協(xié)議數(shù)據(jù)包格式。

Dubbo 數(shù)據(jù)包分為消息頭和消息體。消息頭為定長格式,共 16 字節(jié)党瓮,用于存儲(chǔ)一些元信息录肯,例如消息的起始標(biāo)識(shí) Magic Number,數(shù)據(jù)包的類型,使用的序列化方式 ID,消息體長度等。消息體則為變長格式易迹,具體長度存儲(chǔ)在消息頭中,這部分是用于存儲(chǔ)了具體的調(diào)用信息或調(diào)用結(jié)果平道,也就是 Invocation 序列化后的字節(jié)序列或遠(yuǎn)程調(diào)用返回的對(duì)象的字節(jié)序列睹欲,消息體這部分的數(shù)據(jù)是由序列化/反序列化來處理的。

Dubbo 協(xié)議數(shù)據(jù)包格式

之前提到過巢掺,Dubbo 將用于編解碼數(shù)據(jù)的通道處理器抽象為了 Codec 接口句伶,所以在消息發(fā)送出去之前,Dubbo 會(huì)調(diào)用該接口的 encode() 方法進(jìn)行編碼陆淀。其中考余,對(duì)于消息體,也就是本次調(diào)用的調(diào)用信息 Invacation轧苫,會(huì)通過 Serialization 接口來進(jìn)行序列化楚堤。

Dubbo 在啟動(dòng)客戶端和服務(wù)端的時(shí)候,會(huì)通過適配器模式含懊,將 Codec 相關(guān)的編解碼器與 Netty 進(jìn)行適配身冬,將其添加到 Netty 的 pipeline 中,參見 NettyCodecAdapter岔乔、NettyClientNettyServer酥筝。

Request 編碼過程

下面是相關(guān)的編碼邏輯,對(duì)照上圖食用更佳雏门。

Codec 編碼邏輯

編碼完成之后嘿歌,數(shù)據(jù)就會(huì)被 NIO 框架所發(fā)出,通過網(wǎng)絡(luò)到達(dá)服務(wù)端茁影。

2.5 服務(wù)端的調(diào)用鏈路

當(dāng)服務(wù)端接收到數(shù)據(jù)的時(shí)候宙帝,因?yàn)榻邮盏降亩际亲止?jié)序列,所以第一步應(yīng)該是對(duì)其解碼募闲,這一步最終會(huì)交給 Codec 接口的 decode 方法處理步脓。

解碼的時(shí)候會(huì)先解析得到消息頭,然后再根據(jù)消息頭中的元信息,例如消息頭長度靴患,消息類型仍侥,將消息體反序列化為 DecodeableRpcInvocation 對(duì)象(也就是調(diào)用信息)。

Request 解碼過程

此時(shí)的線程為 Netty 的 I/O 線程蚁廓,不一定會(huì)在當(dāng)前線程解碼访圃,所以有可能會(huì)得到部分解碼的 Request 對(duì)象,具體分析見下文相嵌。

Codec 解碼邏輯

值得注意的是,在 2.6.x 版本中况脆,默認(rèn)情況下對(duì)于請(qǐng)求的解碼會(huì)在 I/O 線程中執(zhí)行饭宾,而 2.7.x 之后的版本則是交給業(yè)務(wù)線程執(zhí)行。

這里的 I/O 線程指的是底層通信框架中接收請(qǐng)求的線程(其實(shí)就是 Netty 中的 Worker 線程)格了,業(yè)務(wù)線程則是 Dubbo 內(nèi)部用于處理請(qǐng)求/響應(yīng)的線程池中的線程看铆。如果某個(gè)事件可能比較耗時(shí),不能在 I/O 線程上執(zhí)行盛末,那么就需要通過線程派發(fā)器將線程派發(fā)到線程池中去執(zhí)行弹惦。

再次借用官網(wǎng)的一張圖,當(dāng)服務(wù)端接收到請(qǐng)求時(shí)悄但,會(huì)根據(jù)不同的線程派發(fā)策略棠隐,將請(qǐng)求派發(fā)到線程池中執(zhí)行。線程派發(fā)器 Dispatcher 本身并不具備線程派發(fā)的能力檐嚣,它只是用于創(chuàng)建具有線程派發(fā)能力的 ChannelHandler助泽。

線程派發(fā)

Dubbo 擁有 5 種線程派發(fā)策略,默認(rèn)使用的策略為 all嚎京,具體策略差別見下表嗡贺。

策略 用途
all 所有消息都派發(fā)到線程池,包括請(qǐng)求鞍帝,響應(yīng)诫睬,連接事件,斷開事件等
direct 所有消息都不派發(fā)到線程池帕涌,全部在 IO 線程上直接執(zhí)行
message 只有請(qǐng)求和響應(yīng)消息派發(fā)到線程池摄凡,其它消息均在 IO 線程上執(zhí)行
execution 只有請(qǐng)求消息派發(fā)到線程池,不含響應(yīng)宵膨。其它消息均在 IO 線程上執(zhí)行
Connection 在 IO 線程上架谎,將連接斷開事件放入隊(duì)列,有序逐個(gè)執(zhí)行辟躏,其它消息派發(fā)到線程池

經(jīng)過 DubboCodec 解碼器處理過的數(shù)據(jù)會(huì)被 Netty 傳遞給下一個(gè)入站處理器谷扣,最終根據(jù)配置的線程派發(fā)策略來到對(duì)應(yīng)的 ChannelHandler,例如默認(rèn)的 AllChannelHandler

AllChannelHandler

可以看到会涎,對(duì)于每種事件裹匙,AllChannelHandler 只是創(chuàng)建了一個(gè) ChannelEventRunnable 對(duì)象并提交到業(yè)務(wù)線程池中去執(zhí)行,這個(gè) Runnable 對(duì)象其實(shí)只是一個(gè)中轉(zhuǎn)站末秃,它是為了避免在 I/O 線程中執(zhí)行具體的操作概页,最終真正的操作它會(huì)委托給持有的 ChannelHandler 去處理。

ChannelEventRunnable

服務(wù)端對(duì)請(qǐng)求進(jìn)行派發(fā)的過程如下圖所示练慕。

服務(wù)端派發(fā)請(qǐng)求過程

上面說過惰匙,解碼操作也有可能在業(yè)務(wù)線程中執(zhí)行,因?yàn)?ChannelEventRunnable 中直接持有的 ChannelHandler 就是一個(gè)用于解碼的 DecodeHandler铃将。

如果需要解碼项鬼,那么這個(gè)通道處理器會(huì)調(diào)用在 I/O 線程中創(chuàng)建的 DecodeableRpcInvocation 對(duì)象的 decode 方法,從字節(jié)序列中反序列化得到本次調(diào)用的類名劲阎,方法名绘盟,參數(shù)信息等。

解碼完成后悯仙,DecodeHandler 會(huì)將完全解碼的 Request 對(duì)象繼續(xù)傳遞到下一個(gè)通道處理器即 HeaderExchangeHandler龄毡。

DecodeHandler

到這里其實(shí)已經(jīng)可以體會(huì)到 Dubbo 抽取出 ChannelHandler 的好處了,可以避免和特定 NIO 庫耦合锡垄,同時(shí)使用裝飾者模式一層層地處理請(qǐng)求沦零,最終對(duì) NIO 庫只暴露出一個(gè)特定的 Handler,更加靈活偎捎。

這里附上一張服務(wù)端 ChannelHandler 的結(jié)構(gòu)圖蠢终。

服務(wù)端通道處理器結(jié)構(gòu)

HeaderExchangeHandler 會(huì)根據(jù)本次請(qǐng)求的類型決定如何處理。如果是單向調(diào)用茴她,那么只需向后調(diào)用即可寻拂,不需要返回響應(yīng)。如果是雙向調(diào)用丈牢,那么就需要在得到具體的調(diào)用結(jié)果后祭钉,封裝成 Response 對(duì)象,并通過持有的 Channel 對(duì)象將本次調(diào)用的響應(yīng)發(fā)送回客戶端己沛。

HeaderExchangeHandler

HeaderExchangeHandler 將調(diào)用委托給持有的 ExchangeHandler 處理器慌核,這個(gè)處理器是和服務(wù)暴露時(shí)使用的協(xié)議有關(guān)的,一般來說都是某個(gè)協(xié)議的內(nèi)部類申尼。

由于默認(rèn)情況下都是使用的 Dubbo 協(xié)議垮卓,所以接下來對(duì) Dubbo 協(xié)議中的處理器進(jìn)行分析。

DubboProtocol

Dubbo 協(xié)議內(nèi)部的 ExchangeHandler 會(huì)從已經(jīng)暴露的服務(wù)列表中找到本次調(diào)用的 Invoker师幕,并且向其發(fā)起本地調(diào)用粟按。不過要注意的是诬滩,這里的 Invoker 是一個(gè)動(dòng)態(tài)生成的代理對(duì)象,類型為 AbstractProxyInvoker灭将,它持有了處理業(yè)務(wù)的真實(shí)對(duì)象疼鸟。

當(dāng)發(fā)起 invoke 調(diào)用時(shí),它會(huì)通過持有的真實(shí)對(duì)象完成調(diào)用庙曙,并將其封裝到 RpcResult 對(duì)象中并且返回給下層空镜。

AbstractProxyInvoker

關(guān)于 RpcResult 有興趣的話可以了解一下 2.7.x 異步化改造后的變化。簡單來說就是 RpcResultAppResonse 所替代捌朴,用來保存調(diào)用結(jié)果或調(diào)用異常吴攒,同時(shí)引入了一個(gè)新的中間狀態(tài)類 AsyncRpcResult 用于代表未完成的 RPC 調(diào)用。

這個(gè)代理對(duì)象是在服務(wù)端進(jìn)行服務(wù)暴露的時(shí)候生成的男旗,javassist 會(huì)動(dòng)態(tài)生成一個(gè) Wrapper 類舶斧,并且創(chuàng)建一個(gè)匿名內(nèi)部對(duì)象,將調(diào)用操作委托給 Wrapper察皇。

JavassistProxyFactory

下面是反編譯得到的 Wrapper 類,可以看到具體的處理邏輯和客戶端的 InvocationHandler 類似泽台,都是根據(jù)本次調(diào)用的方法名來向真實(shí)對(duì)象發(fā)起調(diào)用什荣。

反編譯的 Wrapper 類

至此,服務(wù)端已完成了調(diào)用過程怀酷。下層 ChannelHandler 收到調(diào)用結(jié)果后稻爬,就會(huì)通過 Channel 將響應(yīng)發(fā)送回客戶端,期間又會(huì)經(jīng)過編碼序列化等操作蜕依,由于和請(qǐng)求的編碼序列化過程類似桅锄,這里不再贅述,感興趣的話可以自行查看 ExchangeCodec#encodeResponse() 以及 DubboCodec#encodeResponseData()样眠。

這里再附上一張服務(wù)端處理請(qǐng)求的時(shí)序圖友瘤。

服務(wù)端處理請(qǐng)求過程

2.6 客戶端處理響應(yīng)

當(dāng)客戶端收到調(diào)用的響應(yīng)后,毫無疑問依舊需要對(duì)收到的字節(jié)序列進(jìn)行解碼及反序列化檐束,這里和服務(wù)端解碼請(qǐng)求的過程是類似的辫秧,查看 ExchangeCodec#decode() 以及 DubboCodec#decodeBody() 自行了解,也可參考上面的服務(wù)端解碼請(qǐng)求的時(shí)序圖被丧,這里只附上一張客戶端處理已(部分)解碼的響應(yīng)的時(shí)序圖盟戏。

響應(yīng)處理過程

這里主要講的是客戶端對(duì)解碼后的 Reponse 對(duì)象的處理邏輯∩穑客戶端的 ChannelHandler 結(jié)構(gòu)和上面的服務(wù)端 ChnnelHandler 結(jié)構(gòu)圖沒有太大區(qū)別柿究,經(jīng)過解碼后的響應(yīng)最終也會(huì)傳遞到 HeaderExchangeHandler 處理器中進(jìn)行處理。

DefaultFuture 接收響應(yīng)邏輯

在客戶端發(fā)起請(qǐng)求時(shí)我們提到過黄选,每個(gè)構(gòu)造的請(qǐng)求都有一個(gè) ID 標(biāo)識(shí)蝇摸,當(dāng)對(duì)應(yīng)的響應(yīng)返回時(shí),就會(huì)把這個(gè) ID 帶上。當(dāng)接收到響應(yīng)時(shí)探入, Dubbo 會(huì)從請(qǐng)求的 Future 映射集合中狡孔,根據(jù)返回的請(qǐng)求 ID,找到對(duì)應(yīng)的 DefaultFuture蜂嗽,并將結(jié)果設(shè)置到 DefaultFuture 中苗膝,同時(shí)喚醒阻塞的用戶線程,這樣就完成了 Dubbo 的業(yè)務(wù)線程到用戶線程的轉(zhuǎn)換植旧。

業(yè)務(wù)線程與用戶線程的轉(zhuǎn)換邏輯

有興趣的話可以再了解一下 DefauFuture 的超時(shí)處理 以及 Dubbo 2.7 異步化改造后的線程模型變化辱揭。

最后附上一張來源官網(wǎng)的圖。

DefaultFuture 作用圖

至此病附,一個(gè)完整的 RPC 調(diào)用就結(jié)束了问窃。

由于本人水平有限,可能部分細(xì)節(jié)并沒有講清楚 完沪,如果有疑問的話歡迎大家指出域庇,一起交流學(xué)習(xí)。

3. 參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末覆积,一起剝皮案震驚了整個(gè)濱河市听皿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宽档,老刑警劉巖尉姨,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吗冤,居然都是意外死亡又厉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門椎瘟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來覆致,“玉大人,你說我怎么就攤上這事降传∨穸洌” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵婆排,是天一觀的道長声旺。 經(jīng)常有香客問我,道長段只,這世上最難降的妖魔是什么腮猖? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮赞枕,結(jié)果婚禮上澈缺,老公的妹妹穿的比我還像新娘坪创。我一直安慰自己,他們只是感情好姐赡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布莱预。 她就那樣靜靜地躺著,像睡著了一般项滑。 火紅的嫁衣襯著肌膚如雪依沮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天枪狂,我揣著相機(jī)與錄音危喉,去河邊找鬼。 笑死州疾,一個(gè)胖子當(dāng)著我的面吹牛辜限,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播严蓖,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼薄嫡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了颗胡?” 一聲冷哼從身側(cè)響起岂座,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎杭措,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钾恢,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡手素,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瘩蚪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泉懦。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖疹瘦,靈堂內(nèi)的尸體忽然破棺而出崩哩,到底是詐尸還是另有隱情,我是刑警寧澤言沐,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布邓嘹,位于F島的核電站,受9級(jí)特大地震影響险胰,放射性物質(zhì)發(fā)生泄漏汹押。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一起便、第九天 我趴在偏房一處隱蔽的房頂上張望棚贾。 院中可真熱鬧窖维,春花似錦、人聲如沸妙痹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怯伊。三九已至琳轿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間震贵,已是汗流浹背利赋。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猩系,地道東北人媚送。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像寇甸,于是被迫代替她去往敵國和親塘偎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容