Dubbo概述–調用過程
- Dubbo概述–調用過程
- 寫在前面
- 調用過程
- Consumer請求
- Provider響應
- Consumer接收響應
- 總結
寫在前面
本文參考了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時前置處理完全一緻,隻是由于消息的類型進行不同的處理