Dubbo概述--調用過程
時間 2021-01-10
標籤
Dubbo
調用
響應
過程
解析
Dubbo概述–調用過程
寫在前面
本文參考了Dubbo官方手冊結合Dubbo2.6.1版本源碼分析。推薦先閱讀官方手冊。
鑑於個人水平有限,如有不正確的地方請指出,歡迎一起討論,謝謝!
調用過程
本過程的分析是基於最簡單的Demo進行分析的,主要的目的是介紹架構和流程。
Consumer請求
Consumer調用ProxyFactory擴展類生成的代理類:ProxyN中的方法。
- ProxyFactory擴展點的默認類爲JavassistProxyFactory
- ProxyN中的N代表本次啓動創建的第N個代理,從0開始的AtomicLong靜態變量
ProxyN類內部將調用方法轉化爲調用InvokerInvocationHandler提供的通用方法:invoke(obj, methodName, args)。
InvokerInvocationHandler將傳入的methodName、args構造爲RPCInvocation實例,並調用MockClusterInvoker的invoke(Invocation)方法。
- RPCInvocation實例可以理解爲整個傳輸過程中的數據載體類,存儲調用方法、參數類型、參數等
MockClusterInvoker根據Directory的URL確定調用方法時是否需要、怎樣調用Mock,如果不是強制使用Mock則調用FailoverClusterInvoker的invoke方法。
- MockClusterInvoker是通過服務擴展功能中自動添加包裝類(Cluster的包裝類MockClusterWrapper)的機制添加進來的
- 沒有methodName.mock參數則直接向下調用
- methodName.mock參數值以force開始時,直接使用mock而不會調用Provider
- methodName.mock參數值爲其他時,只有調用Provider拋異常時才使用mock
- 相關的MockInvokersSelector是通過硬編碼AbstractDirectory.setRouters添加到Directory的
FailoverClusterInvoker根據自身邏輯以及Directory、Router、LoadBalance的實現類確定具體調用Invoker。本例中爲InvokerDelegate。關於Cluster、Direcotry、Router、LoadBalance請參見集羣相關。
- FailoverClusterInvoker是Cluster擴展點的默認類
- InvokerDelegate存儲provider的URL,然後調用ProtocolFilterWrapper
- ProtocolFilterWrapper將Filter擴展點的實現類中@Activate註解的group值爲consumer的Filter排序後組成過濾鏈,並逐個調用Filter,然後調用ListenerInvokerWrapper。
- Filter鏈中的Filter根據@Activate註解的before、after、order屬性排序的
- ListenerInvokerWrapper中通知InvokerWrapper的監聽器。然後調用DubboInvoker。
- DubboInvoker根據URL中的參數決定是同步、異步還是通知調用,進而調用ReferenceCountExchangeClient發送請求。
- DubboInvoker在實例化時會根據URL創建與Provider的連接對象並連接Provider。
- ReferenceCountExchangeClient調用實際的Client:HeaderExchangeClient進行調用。
- ReferenceCountExchangeClient實例是Consumer使用共享connection訪問多個Provider時的默認Client,用於一會用計數。
- HeaderExchangeClient是默認的客戶端,會向Provider發送心跳。並調用HeaderExchangeChannel發送調用信息(Invocation)。
- HeaderExchangeChannel調用內部的Client接口的實現類發送調用,同時將發送的信息保存到DefaultFuture中,供後續接收響應時根據對應關係返回給Consumer上次調用。
- 默認使用NettyClient(非最新的netty4下邊的包)作爲Client發送調用,Client使用自身持有的Channel來發送數據。
- 這裏的Client接口是根據Transporter擴展接口指定傳輸器
- Client接口相關的編碼器爲Codec2擴展接口指定
- Code2中的序列化方式使用Serialization擴展接口指定
- 爲了對多種傳輸方式擴展進行統一的抽象,這裏的Channel是對Dubbo自身的Channel,對各種具體(例如Netty的Channel)進行封裝。最終調用Netty中的channel.write發送。
- 雖然請求是也會觸發一系列的handler,但是這些handler不會控制發送數據,只是調用。這點不確定是否正確。
Provider響應
Netty低層接收到字節,並通過InternalEncoder類將字節解碼爲Java對象(Request),然後通過NettyHandler調用NettyServer的received方法。
- InternalEncoder以及InternalDecoder會根據編碼器接口Codec2進行編碼
- Code2中的序列化方式使用Serialization擴展接口指定
NettyServer的received方法存在於父類AbstractPeer中,AbstractPeer是Server、Client的公共父類,用於調用內部的handler(MultiMessageHandler)進行後續處理。
- NettyServer(Provider)和NettyClient(Consumer)都會使用MultiMessageHandler->HeartbeatHandler->從Dispatcher擴展點獲取的ChannelHandler。
- MultiMessageHandler判斷解碼後的調用信息是否是MultiMessage的實例,根據判斷結果調用內部的handler(HeartbeatHandler)。
- 如果是MultiMessage,則迭代調用其內部的消息。否則直接調用消息。
- HeartbeatHandler用於專門用於處理心跳請求,非心跳請求才會繼續調用後續的handler(dispatcherHandler)。
- 對於心跳請求,設置讀時間戳,並根據請求中的twoway屬性確定是否需要返回響應
- 對於心跳響應,記錄debug日誌
- dispatcherHandler指由Dispatcher擴展點指定的handler,默認爲AllChannelHandler。用於控制請求的線程分發邏輯。請參見請求分發。
- dispatcherHandler會根據自身邏輯將請求分發到特定的線程
- 線程池或者IO線程會調用ChannelEventRunnable的run方法,根據狀態調用後續handler(DecodeHandler)的相應方法。
- DecodeHandler將接收到的Invocation或Response內的數據進行進一步解碼,解碼完成後調用後續的handler(HeaderExchangeHandler)處理。此處指代上圖中的7、8兩步。
- HeaderExchangeHandler會繼續調用進行後續的handler調用,但是如果此次調用需要返回值,那麼由此處的received方法中將後續handler返回值寫入到channel中返回。
- 後續的handler(DubboProtocol.requestHandler)調用的是reply方法,並不是received方法。
- DubboProtocol.requestHandler匿名內部類中調用後續Invoker,實現Provider的調用
- ProtocolFilterWrapper將Filter擴展點的實現類中@Activate註解的group值爲consumer的Filter排序後組成過濾鏈,並逐個調用Filter,然後調用ListenerInvokerWrapper。
- Filter鏈中的Filter根據@Activate註解的before、after、order屬性排序的
- 後續通過多層調用,調用到通過ProxyFactory擴展類動態生成的包裝類:WrapperN。
- WrapperN中的N代表本次啓動創建的第N個包裝類,從0開始的AtomicLong靜態變量
- 通過WrapperN中的invokeMethod方法以及方法傳入參數調用實際的服務類相應方法。
Consumer接收響應
Consumer接收響應的過程與Provider響應過程前9步類似,即到HeaderExchangeHandler的received方法之前都一樣。下面是整體概述:
- HeaderExchangeHandler中received方法中,由於解碼出的對象是Response,所以將其直接傳入DefaultFuture的received方法中。
- DefaultFuture.received()根據response的ID,獲得之前Consumer請求時第12步的DefaultFuture對象。
- 根據DefaultFuture內部的併發編程邏輯,返回響應的response。
- Consumer請求時的request線程會阻塞在DefaultFuture的get方法內的done.await上
- response線程接收返回後,獲取和request相同的鎖。這個是必須的,且由於get是await的,所以可以獲取到鎖。
- response在獲取到鎖後,觸發done.signal喚醒阻塞在done上的線程並在finally中釋放鎖。
- request線程被喚醒,返回response給Consumer頂層調用。
- 以上是一個經典的等待-通知結構,也可以使用JDK自身的await、notify實現。兩個線程都必須在獲得鎖後在可以wait或notify,且notify後需要釋放鎖,此邏輯也可以實現簡單的線程池。
總結
- Dubbo內部大量使用了裝飾器模式(invoker、handler)、外觀模式等設計模式
- Dubbo最經典的還是服務擴展方面的設計(個人看法)
- Consumer的異步調用和同步調用本質上都是異步的
- Consumer和Provider在接收RPC時前置處理完全一致,只是由於消息的類型進行不同的處理