成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Dubbo服務調用過程

開發
前面丙已經帶著大家過了服務暴露和服務引入兩個流程了,而這兩個流程就是為了服務的調用,今天丙就帶著大家來過一遍 Dubbo 服務調用流程。

 看完今天的服務調用流程,基本上Dubbo的核心過程就完整的串聯起來了,在腦海中應該就有 Dubbo 整體運行的概念,這體系就建立起來了,對 RPC 也會有進一步的認識。

簡單的想想大致流程
在分析Dubbo 的服務調用過程前我們先來思考一下如果讓我們自己實現的話一次調用過程需要經歷哪些步驟?

首先我們已經知曉了遠程服務的地址,然后我們要做的就是把我們要

然后根據這些信息找到對應的實現類,然后進行調用,調用完了之后再原路返回,然后客戶端解析響應再返回即可。

 

調用具體的信息
那客戶端告知服務端的具體信息應該包含哪些呢?

首先客戶端肯定要告知要調用是服務端的哪個接口,當然還需要方法名、方法的參數類型、方法的參數值,還有可能存在多個版本的情況,所以還得帶上版本號。

由這么幾個參數,那么服務端就可以清晰的得知客戶端要調用的是哪個方法,可以進行精確調用!

然后組裝響應返回即可,我這里貼一個實際調用請求對象列子。

 

data 就是我所說的那些數據,其他是框架的,包括協議版本、調用方式等等這個下面再分析。

到此其實大致的意思大家都清楚了,就是普通的遠程調用,告知請求的參數,然后服務端解析參數找到對應的實現調用,再返回。

落地的調用流程
上面的是想象的調用流程,真實的落地調用流程沒有這么簡單。

首先遠程調用需要定義協議,也就是互相約定我們要講什么樣的語言,要保證雙方都能聽得懂。

比如我會英語和中文,你也會英語、中文,我們之間要做約定,選定一個語言比如都用中文來談話,有人說不對啊,你中文夾著的英文我也能聽得懂啊。

那是因為你的大腦很智能,它能智能地識別到交流的語言,而計算機可不是,你想想你的代碼寫 print 1,它還能打出 2 不成?

也就是計算機是死板的,我們的程序告訴它該怎么做,它就會生硬的怎么做。

需要一個協議
所以首先需要雙方定義一個協議,這樣計算機才能解析出正確的信息。

常見的三種協議形式
應用層一般有三種類型的協議形式,分別是:固定長度形式、特殊字符隔斷形式、header+body 形式。

固定長度形式:指的是協議的長度是固定的,比如100個字節為一個協議單元,那么讀取100個字節之后就開始解析。

優點就是效率較高,無腦讀一定長度就解析。

缺點就是死板,每次長度只能固定,不能超過限制的長度,并且短了還得填充,在 RPC 場景中不太合適,誰曉得參數啥的要多長,定長了浪費,定短了不夠。

特殊字符隔斷形式:其實就是定義一個特殊結束符,根據特殊的結束符來判斷一個協議單元的結束,比如用換行符等等。

這個協議的優點是長度自由,反正根據特殊字符來截斷,缺點就是需要一直讀,直到讀到一個完整的協議單元之后才能開始解析,然后假如傳輸的數據里面混入了這個特殊字符就出錯了。

header+body 形式:也就是頭部是固定長度的,然后頭部里面會填寫 body 的長度, body 是不固定長度的,這樣伸縮性就比較好了,可以先解析頭部,然后根據頭部得到 body 的 len 然后解析 body。

dubbo 協議就是屬于 header+body 形式,而且也有特殊的字符 0xdabb ,這是用來解決 TCP 網絡粘包問題的。

Dubbo 協議
Dubbo 支持的協議很多,我們就簡單的分析下 Dubbo 協議。

 

協議分為協議頭和協議體,可以看到 16 字節的頭部主要攜帶了魔法數,也就是之前說的 0xdabb,然后一些請求的設置,消息體的長度等等。

16 字節之后就是協議體了,包括協議版本、接口名字、接口版本、方法名字等等。

其實協議很重要,因為從中可以得知很多信息,而且只有懂了協議的內容,才能看得懂編碼器和解碼器在干嘛,我再截取一張官網對協議的解釋圖。

 

需要約定序列化器
網絡是以字節流的形式傳輸的,相對于我們的對象來說,我們對象是多維的,而字節流是一維的,我們需要把我們的對象壓縮成一維的字節流傳輸到對端。

然后對端再反序列化這些字節流變成對象。

序列化協議
其實從上圖的協議中可以得知 Dubbo 支持很多種序列化,我不具體分析每一種協議,就大致分析序列化的種類,萬變不離其宗。

序列化大致分為兩大類,一種是字符型,一種是二進制流。

字符型的代表就是 XML、JSON,字符型的優點就是調試方便,它是對人友好的,我們一看就能知道那個字段對應的哪個參數。

缺點就是傳輸的效率低,有很多冗余的東西,比如 JSON 的括號,對于網絡傳輸來說傳輸的時間變長,占用的帶寬變大。

還有一大類就是二進制流型,這種類型是對機器友好的,它的數據更加的緊湊,所以占用的字節數更小,傳輸更快。

缺點就是調試很難,肉眼是無法識別的,必須借用特殊的工具轉換。

更深層次的就不深入了,序列化還是有很多門道的,以后有機會再談。

Dubbo 默認用的是 hessian2 序列化協議。

所以實際落地還需要先約定好協議,然后再選擇好序列化方式構造完請求之后發送。

粗略的調用流程圖
我們來看一下官網的圖。

 

簡述一下就是客戶端發起調用,實際調用的是代理類,代理類最終調用的是 Client (默認Netty),需要構造好協議頭,然后將 Java 的對象序列化生成協議體,然后網絡調用傳輸。

服務端的 NettyServer接到這個請求之后,分發給業務線程池,由業務線程調用具體的實現方法。

但是這還不夠,因為 Dubbo 是一個生產級別的 RPC 框架,它需要更加的安全、穩重。

詳細的調用流程

 

前面已經分析過了客戶端也是要序列化構造請求的,為了讓圖更加突出重點,所以就省略了這一步,當然還有響應回來的步驟,暫時就理解為原路返回,下文會再做分析。

可以看到生產級別就得穩,因此服務端往往會有多個,多個服務端的服務就會有多個 Invoker,最終需要通過路由過濾,然后再通過負載均衡機制來選出一個 Invoker 進行調用。

當然 Cluster 還有容錯機制,包括重試等等。

請求會先到達 Netty 的 I/O 線程池進行讀寫和可選的序列化和反序列化,可以通過 decode.in.io控制,然后通過業務線程池處理反序列化之后的對象,找到對應 Invoker 進行調用。

調用流程-客戶端源碼分析
客戶端調用一下代碼。

 

  1. String hello = demoService.sayHello("world");  

調用具體的接口會調用生成的代理類,而代理類會生成一個 RpcInvocation 對象調用 MockClusterInvoker#invoke方法。

此時生成的 RpcInvocation 如下圖所示,包含方法名、參數類和參數值。

 

然后我們再來看一下 MockClusterInvoker#invoke 代碼。

 

可以看到就是判斷配置里面有沒有配置 mock, mock 的話就不展開分析了,我們來看看 this.invoker.invoke 的實現,實際上會調用 AbstractClusterInvoker#invoker 。

 

模板方法
這其實就是很常見的設計模式之一,模板方法。如果你經??丛创a的話你知道這個設計模式真的是太常見的。

模板方法其實就是在抽象類中定好代碼的執行骨架,然后將具體的實現延遲到子類中,由子類來自定義個性化實現,也就是說可以在不改變整體執行步驟的情況下修改步驟里面的實現,減少了重復的代碼,也利于擴展,符合開閉原則。

在代碼中就是那個 doInvoke由子類來實現,上面的一些步驟都是每個子類都要走的,所以抽到抽象類中。

路由和負載均衡得到 Invoker
我們再來看那個 list(invocation),其實就是通過方法名找 Invoker,然后服務的路由過濾一波,也有再造一個 MockInvoker 的。

 

然后帶著這些 Invoker 再進行一波 loadbalance 的挑選,得到一個 Invoker,我們默認使用的是 FailoverClusterInvoker,也就是失敗自動切換的容錯方式,其實關于路由、集群、負載均衡是獨立的模塊,如果展開講的話還是有很多內容的,所以需要另起一篇講,這篇文章就把它們先作為黑盒使用。

稍微總結一下就是 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表,并且經過路由之后,它會讓 LoadBalance 從 Invoker 列表中選擇一個 Invoker。

最后FailoverClusterInvoker會將參數傳給選擇出的那個 Invoker 實例的 invoke 方法,進行真正的遠程調用,我們來簡單的看下 FailoverClusterInvoker#doInvoke,為了突出重點我刪除了很多方法。

 

發起調用的這個 invoke 又是調用抽象類中的 invoke 然后再調用子類的 doInvoker,抽象類中的方法很簡單我就不展示了,影響不大,直接看子類 DubboInvoker 的 doInvoke 方法。

 

調用的三種方式
從上面的代碼可以看到調用一共分為三種,分別是 oneway、異步、同步。

oneway還是很常見的,就是當你不關心你的請求是否發送成功的情況下,就用 oneway 的方式發送,這種方式消耗最小,啥都不用記,啥都不用管。

異步調用,其實 Dubbo 天然就是異步的,可以看到 client 發送請求之后會得到一個 ResponseFuture,然后把 future 包裝一下塞到上下文中,這樣用戶就可以從上下文中拿到這個 future,然后用戶可以做了一波操作之后再調用 future.get 等待結果。

同步調用,這是我們最常用的,也就是 Dubbo 框架幫助我們異步轉同步了,從代碼可以看到在 Dubbo 源碼中就調用了 future.get,所以給用戶的感覺就是我調用了這個接口的方法之后就阻塞住了,必須要等待結果到了之后才能返回,所以就是同步的。

可以看到 Dubbo 本質上就是異步的,為什么有同步就是因為框架幫我們轉了一下,而同步和異步的區別其實就是future.get 在用戶代碼被調用還是在框架代碼被調用。

再回到源碼中來,currentClient.request 源碼如下就是組裝 request 然后構造一個 future 然后調用 NettyClient 發送請求。

 

我們再來看一下 DefaultFuture 的內部,你有沒有想過一個問題,因為是異步,那么這個 future 保存了之后,等響應回來了如何找到對應的 future 呢?

這里就揭秘了!就是利用一個唯一 ID。

 

可以看到 Request 會生成一個全局唯一 ID,然后 future 內部會將自己和 ID 存儲到一個 ConcurrentHashMap。這個 ID 發送到服務端之后,服務端也會把這個 ID 返回來,這樣通過這個 ID 再去ConcurrentHashMap 里面就可以找到對應的 future ,這樣整個連接就正確且完整了!

我們再來看看最終接受到響應的代碼,應該就很清晰了。

先看下一個響應的 message 的樣子:

看到這個 ID 了吧,最終會調用 DefaultFuture#received的方法。

 

為了能讓大家更加的清晰,我再畫個圖:

 

到這里差不多客戶端調用主流程已經很清晰了,其實還有很多細節,之后的文章再講述,不然一下太亂太雜了。

發起請求的調用鏈如下圖所示:

 

處理請求響應的調用鏈如下圖所示

 

調用流程-服務端端源碼分析
服務端接收到請求之后就會解析請求得到消息,這消息又有五種派發策略:

 

默認走的是 all,也就是所有消息都派發到業務線程池中,我們來看下 AllChannelHandler 的實現。

 

就是將消息封裝成一個 ChannelEventRunnable 扔到業務線程池中執行,ChannelEventRunnable 里面會根據 ChannelState 調用對于的處理方法,這里是 ChannelState.RECEIVED,所以調用 handler.received,最終會調用 HeaderExchangeHandler#handleRequest,我們就來看下這個代碼。

 

這波關鍵點看到了吧,構造的響應先塞入請求的 ID,我們再來看看這個 reply 干了啥。

 

最后的調用我們已經清楚了,實際上會調用一個 Javassist 生成的代理類,里面包含了真正的實現類,之前已經分析過了這里就不再深入了,我們再來看看getInvoker 這個方法,看看怎么根據請求的信息找到對應的 invoker 的。

 

關鍵就是那個 serviceKey, 還記得之前服務暴露將invoker 封裝成 exporter 之后再構建了一個 serviceKey將其和 exporter 存入了 exporterMap 中吧,這 map 這個時候就起作用了!

這個 Key 就長這樣:

 

找到 invoker 最終調用實現類具體的方法再返回響應整個流程就完結了,我再補充一下之前的圖。

 

總結
今天的調用過程我再總結一遍應該差不多了。

首先客戶端調用接口的某個方法,實際調用的是代理類,代理類會通過 cluster 從 directory 中獲取一堆 invokers(如果有一堆的話),然后進行 router 的過濾(其中看配置也會添加 mockInvoker 用于服務降級),然后再通過 SPI 得到 loadBalance 進行一波負載均衡。

這里要強調一下默認的 cluster 是 FailoverCluster ,會進行容錯重試處理,這個日后再詳細分析。

現在我們已經得到要調用的遠程服務對應的 invoker 了,此時根據具體的協議構造請求頭,然后將參數根據具體的序列化協議序列化之后構造塞入請求體中,再通過 NettyClient 發起遠程調用。

服務端 NettyServer 收到請求之后,根據協議得到信息并且反序列化成對象,再按照派發策略派發消息,默認是 All,扔給業務線程池。

業務線程會根據消息類型判斷然后得到 serviceKey 從之前服務暴露生成的 exporterMap 中得到對應的 Invoker ,然后調用真實的實現類。

最終將結果返回,因為請求和響應都有一個統一的 ID, 客戶端根據響應的 ID 找到存儲起來的 Future, 然后塞入響應再喚醒等待 future 的線程,完成一次遠程調用全過程。

而且還小談了下模板方法這個設計模式,當然其實隱藏了很多設計模式在其中,比如責任鏈、裝飾器等等,沒有特意挑開來說,源碼中太常見了,基本上無處不在。

 

 

 

責任編輯:姜華 來源: 三太子敖丙
相關推薦

2021-09-03 08:50:50

Dubbo服務引用

2020-09-08 08:55:52

Dubbo服務全鏈路

2010-01-19 09:48:22

VB.NET調用過程

2021-01-19 09:19:33

RPC調用過程框架

2010-01-19 14:42:43

VB.NET調用過程重

2021-09-06 08:50:49

服務Dubbo參數

2013-10-30 17:34:22

Clouda安裝使用

2023-06-01 08:10:56

2023-10-18 07:16:41

2023-04-14 08:19:27

2022-04-06 08:47:03

Dubbo服務協議

2010-07-02 11:59:00

2018-08-10 10:23:40

服務器硬盤故障

2023-10-17 08:08:37

Dubbo服務注冊中心

2024-09-14 14:14:26

Dubbo框架微服務

2023-04-19 08:13:42

Dubbo服務注冊

2021-07-19 07:31:08

服務調用Dubbo

2025-02-27 00:00:55

Dubbo服務不兼容

2021-03-25 14:25:24

Linux運維Linux系統

2010-06-07 16:51:06

rsync 使用
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩欧美国产一区二区 | 国产精品久久777777 | 国产精品成人一区二区 | 男女黄网站 | 国产一区二区三区久久久久久久久 | 天天操夜夜爽 | 高清视频一区 | 久久精品国产一区二区电影 | 国产福利91精品一区二区三区 | 天天爽夜夜爽精品视频婷婷 | 日本午夜视频 | 中文字幕在线观 | 亚洲一区毛片 | 亚洲欧美成人在线 | 色婷婷亚洲国产女人的天堂 | 国产精品视频免费 | 久久99精品久久 | 一色桃子av一区二区 | www视频在线观看 | 日韩成人免费视频 | 久久中文视频 | 久久草在线视频 | 欧美狠狠操| 精品久久中文字幕 | 91视频大全 | 欧美日韩精品一区 | 成在线人视频免费视频 | 久久免费国产 | 午夜精品一区二区三区在线 | 久久久久久久久久久爱 | 成人国产精品久久 | 伊人伊人 | 天天操夜夜爽 | 男女视频在线观看免费 | 国产免费va | 91欧美精品成人综合在线观看 | 日韩中文一区二区三区 | 四虎在线观看 | 久久精品二区亚洲w码 | 蜜桃特黄a∨片免费观看 | 欧美二区乱c黑人 |