dubbo源碼分析(一)

閱讀源碼的做用

  • 提取設計思路,加強設計能力
  • 理解運行機制,便於快速解決問題以及功能擴展

常見有關dubbo的問題

  • dubbo的負載均衡是在哪一個組件中處理的?
  • dubbo默認的負載均衡算法是什麼?
  • 若是註冊中心掛掉了客戶端是否可以繼續調用dubbo?
  • 一個請求從調用端到服務端的處理流程是什麼?

若是你有仔細的研讀dubbo的開發文檔,大部分問題都是有答案的,爲了進一步瞭解細節就有必要對源碼進行了解。html

源碼分析計劃

使用dubbo時間也有快兩年時間了,雖然官方的dubbo已經中止維護,但也有其它組織在繼續維護:java

  • dubbox,是噹噹維護的
  • 還有一個是基於jdk8版本的

一直想看看dubbo的源碼,但沒有集中時間來學習。最近利用了一部分時間因此將個人學習經歷記錄下來,主要從這幾步來展開,只想搞清楚大致流程,對於其它一些細節我目前並不太關注,好比序列化,線程池,集羣,上下文,異步回調等。git

  • RPC框架的簡易結構
  • dubbo客戶端的初始化
  • dubbo服務端的初始化
  • dubbo客戶端處理請求流程
  • dubbo服務端處理請求流程

RPC簡易結構

爲了更加清楚的看清楚RPC的結構,主要看如下幾個核心組件便可,其他的組件都是圍繞它們來完成。github

  • 序列化組件,由於須要遠程調用,因此須要將參數以二進制流的形式進行編碼操做
  • 發送組件,將二進制流發送到服務端
  • 接收組件,接收調用端發送的二進制流以及接收服務端返回的二進制流

dubbo客戶端的初始化

從這開始來實際看下dubbo的工做流程。RPC的亮點在於將遠程調用的細節隱藏起來,使得調用遠程服務像調用本地服務同樣簡單,而實現上面的功能就是代理。web

代理的做用

RPC框架隱藏了具體的實現細節,客戶端經過調用特定的代理類來訪問遠程服務,下面是一個dubbo遠程接口的本地引用。算法

<dubbo:reference check="false"
	                  timeout="200000"
	                  interface="com.product.core.service.ProductFacadeService"
	                  id="productFacadeService">

ReferenceConfig這個類的createProxy是用來生成遠程服務的本地代理,最終交給RegistryProtocol來處理。兩處核心代碼:canvas

  • refprotocol.refer,與註冊中心相關
  • proxyFactory.getProxy,獲取代理
private T createProxy(Map<String, String> map) {
		//...
		
		if (isJvmRefer) {
			//...
		} else {
            //...

            if (urls.size() == 1) {
                invoker = refprotocol.refer(interfaceClass, urls.get(0));
            } else {
               //...
            }
        }

        //...
        // 建立服務代理
        return (T) proxyFactory.getProxy(invoker);
    }

本地直連不走上面的邏輯緩存

註冊中心

因爲服務註冊到ZK,因此調用端要想調用服務端須要取得服務的註冊信息而後創建網絡鏈接。ruby

 private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        RegistryDirectory directory = new RegistryDirectory(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(this.protocol);
        URL subscribeUrl = new URL("consumer", NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
        if(!"*".equals(url.getServiceInterface()) && url.getParameter("register", true)) {
            registry.register(subscribeUrl.addParameters(new String[]{"category", "consumers", "check", String.valueOf(false)}));
        }

        directory.subscribe(subscribeUrl.addParameter("category", "providers,configurators,routers"));
        return cluster.join(directory);
    }

上面代碼兩個做用:markdown

  • 將調用端以消費者的身份進行信息註冊
  • 監聽註冊中心的信息變化以刷新本地對服務註冊信息的緩存

因爲有服務端信息的本地緩存,因此當註冊中心掛掉後調用端依然可以工做,也就是說調用端不是強依賴服務端。

鏈接服務端

上面的subscribe方法會監聽註冊中心的變化,當獲取到服務註冊信息後會觸發ProtocolListenerWrapper的refer方法:

 public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return (Invoker)("registry".equals(url.getProtocol())?this.protocol.refer(type, url):

new ListenerInvokerWrapper(this.protocol.refer(type, url),

Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(InvokerListener.class).getActivateExtension(url, "invoker.listener")))); }

上面的this.protocol就是DubboProtocol這個類,此類的其它方法暫時先不關注,只看refer。

public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
    this.optimizeSerialization(url);
    DubboInvoker invoker = new DubboInvoker(serviceType, url, this.getClients(url), this.invokers);
    this.invokers.add(invoker);
    return invoker;
}

getClients負責建立連接以供調用端調用服務端方法時使用。

 private ExchangeClient[] getClients(URL url) {
        boolean service_share_connect = false;
        int connections = url.getParameter("connections", 0);
        if(connections == 0) {
            service_share_connect = true;
            connections = 1;
        }

        ExchangeClient[] clients = new ExchangeClient[connections];

        for(int i = 0; i < clients.length; ++i) {
            if(service_share_connect) {
                clients[i] = this.getSharedClient(url);
            } else {
                clients[i] = this.initClient(url);
            }
        }

        return clients;
    }

建立代理

回到ReferenceConfig的createProxy方法,最後會調用proxyFactory.getProxy,此方法最終會調用JavassistProxyFactory的getProxy,一看javassist就知道是利用字節碼來實現代理功能。

 public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }

Proxy,這個類的實現比較複雜就不詳細分析了,獲得了proxy將它放在容器中,當調用端調用服務端代碼時就有了實例跟本地的實例沒什麼區別。

 public static Proxy getProxy(Class... ics) {
        return getProxy(ClassHelper.getCallerClassLoader(Proxy.class), ics);
    }

初始化時序圖

下圖中的callback是指監聽註冊中心後回調產生的調用關係。

總結

本文提到了分析框架源碼的做用,RPC簡單結構,並制定了一個源碼分析的計劃。最後以調用端在啓動時執行的dubbo流程爲起點開始了dubbo流程的源碼分析。

相關文章
相關標籤/搜索