Dubbo概述--調用過程

Dubbo概述–調用過程

寫在前面

本文參考了Dubbo官方手冊結合Dubbo2.6.1版本源碼分析。推薦先閱讀官方手冊

鑑於個人水平有限,如有不正確的地方請指出,歡迎一起討論,謝謝!

調用過程

本過程的分析是基於最簡單的Demo進行分析的,主要的目的是介紹架構和流程。

Consumer請求

Dubbo-Consumer-Request

  1. Consumer調用ProxyFactory擴展類生成的代理類:ProxyN中的方法。

    • ProxyFactory擴展點的默認類爲JavassistProxyFactory
    • ProxyN中的N代表本次啓動創建的第N個代理,從0開始的AtomicLong靜態變量
  2. ProxyN類內部將調用方法轉化爲調用InvokerInvocationHandler提供的通用方法:invoke(obj, methodName, args)

  3. InvokerInvocationHandler將傳入的methodName、args構造爲RPCInvocation實例,並調用MockClusterInvoker的invoke(Invocation)方法。

    • RPCInvocation實例可以理解爲整個傳輸過程中的數據載體類,存儲調用方法、參數類型、參數等
  4. 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的
  5. FailoverClusterInvoker根據自身邏輯以及Directory、Router、LoadBalance的實現類確定具體調用Invoker。本例中爲InvokerDelegate。關於Cluster、Direcotry、Router、LoadBalance請參見集羣相關。

    • FailoverClusterInvoker是Cluster擴展點的默認類
  6. InvokerDelegate存儲provider的URL,然後調用ProtocolFilterWrapper
  7. ProtocolFilterWrapper將Filter擴展點的實現類中@Activate註解的group值爲consumer的Filter排序後組成過濾鏈,並逐個調用Filter,然後調用ListenerInvokerWrapper。
    • Filter鏈中的Filter根據@Activate註解的before、after、order屬性排序的
  8. ListenerInvokerWrapper中通知InvokerWrapper的監聽器。然後調用DubboInvoker。
  9. DubboInvoker根據URL中的參數決定是同步、異步還是通知調用,進而調用ReferenceCountExchangeClient發送請求。
    • DubboInvoker在實例化時會根據URL創建與Provider的連接對象並連接Provider。
  10. ReferenceCountExchangeClient調用實際的Client:HeaderExchangeClient進行調用。
    • ReferenceCountExchangeClient實例是Consumer使用共享connection訪問多個Provider時的默認Client,用於一會用計數。
  11. HeaderExchangeClient是默認的客戶端,會向Provider發送心跳。並調用HeaderExchangeChannel發送調用信息(Invocation)。
  12. HeaderExchangeChannel調用內部的Client接口的實現類發送調用,同時將發送的信息保存到DefaultFuture中,供後續接收響應時根據對應關係返回給Consumer上次調用
  13. 默認使用NettyClient(非最新的netty4下邊的包)作爲Client發送調用,Client使用自身持有的Channel來發送數據。
    • 這裏的Client接口是根據Transporter擴展接口指定傳輸器
    • Client接口相關的編碼器爲Codec2擴展接口指定
    • Code2中的序列化方式使用Serialization擴展接口指定
  14. 爲了對多種傳輸方式擴展進行統一的抽象,這裏的Channel是對Dubbo自身的Channel,對各種具體(例如Netty的Channel)進行封裝。最終調用Netty中的channel.write發送。
  15. 雖然請求是也會觸發一系列的handler,但是這些handler不會控制發送數據,只是調用。這點不確定是否正確

Provider響應

Dubbo-Provier-Req&Res

  1. Netty低層接收到字節,並通過InternalEncoder類將字節解碼爲Java對象(Request),然後通過NettyHandler調用NettyServer的received方法。

    • InternalEncoder以及InternalDecoder會根據編碼器接口Codec2進行編碼
    • Code2中的序列化方式使用Serialization擴展接口指定
  2. NettyServer的received方法存在於父類AbstractPeer中,AbstractPeer是Server、Client的公共父類,用於調用內部的handler(MultiMessageHandler)進行後續處理。

    • NettyServer(Provider)和NettyClient(Consumer)都會使用MultiMessageHandler->HeartbeatHandler->從Dispatcher擴展點獲取的ChannelHandler。
  3. MultiMessageHandler判斷解碼後的調用信息是否是MultiMessage的實例,根據判斷結果調用內部的handler(HeartbeatHandler)。
    • 如果是MultiMessage,則迭代調用其內部的消息。否則直接調用消息。
  4. HeartbeatHandler用於專門用於處理心跳請求,非心跳請求才會繼續調用後續的handler(dispatcherHandler)。
    • 對於心跳請求,設置讀時間戳,並根據請求中的twoway屬性確定是否需要返回響應
    • 對於心跳響應,記錄debug日誌
  5. dispatcherHandler指由Dispatcher擴展點指定的handler,默認爲AllChannelHandler。用於控制請求的線程分發邏輯。請參見請求分發。
  6. dispatcherHandler會根據自身邏輯將請求分發到特定的線程
  7. 線程池或者IO線程會調用ChannelEventRunnable的run方法,根據狀態調用後續handler(DecodeHandler)的相應方法。
  8. DecodeHandler將接收到的Invocation或Response內的數據進行進一步解碼,解碼完成後調用後續的handler(HeaderExchangeHandler)處理。此處指代上圖中的7、8兩步。
  9. HeaderExchangeHandler會繼續調用進行後續的handler調用,但是如果此次調用需要返回值,那麼由此處的received方法中將後續handler返回值寫入到channel中返回
    • 後續的handler(DubboProtocol.requestHandler)調用的是reply方法,並不是received方法。
  10. DubboProtocol.requestHandler匿名內部類中調用後續Invoker,實現Provider的調用
  11. ProtocolFilterWrapper將Filter擴展點的實現類中@Activate註解的group值爲consumer的Filter排序後組成過濾鏈,並逐個調用Filter,然後調用ListenerInvokerWrapper。
    • Filter鏈中的Filter根據@Activate註解的before、after、order屬性排序的
  12. 後續通過多層調用,調用到通過ProxyFactory擴展類動態生成的包裝類:WrapperN。
    • WrapperN中的N代表本次啓動創建的第N個包裝類,從0開始的AtomicLong靜態變量
  13. 通過WrapperN中的invokeMethod方法以及方法傳入參數調用實際的服務類相應方法。

Consumer接收響應

Consumer接收響應的過程與Provider響應過程前9步類似,即到HeaderExchangeHandler的received方法之前都一樣。下面是整體概述:

  1. HeaderExchangeHandler中received方法中,由於解碼出的對象是Response,所以將其直接傳入DefaultFuture的received方法中。
  2. DefaultFuture.received()根據response的ID,獲得之前Consumer請求時第12步的DefaultFuture對象。
  3. 根據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後需要釋放鎖,此邏輯也可以實現簡單的線程池。

總結

  1. Dubbo內部大量使用了裝飾器模式(invoker、handler)、外觀模式等設計模式
  2. Dubbo最經典的還是服務擴展方面的設計(個人看法)
  3. Consumer的異步調用和同步調用本質上都是異步的
  4. Consumer和Provider在接收RPC時前置處理完全一致,只是由於消息的類型進行不同的處理