Dubbo——服務引用

引言

上一篇我們分析了服務發佈的原理,可以看到默認是創建了一個Netty server,並通過Invoker調用服務,同樣,在客戶端也會創建一個Inovker對象,下面就一起來看看這個引用創建過程。

正文

服務訂閱

服務端的dubbo:service配置對應的類爲ServiceBean,同樣的,dubbo:reference對應有一個ReferenceBean,該類中的getObject方法,就是獲取客戶端代理類以及訂閱服務的開端(默認情況下,Dubbo使用懶加載方式,在ReferenceBean對應的服務被引用或注入到其它類的時候調用getObject方法;否則,在bean初始化完成後就會調用afterPropertiesSet方法,而該方法也會調用getObject方法),所以我們就從這個方法開始。

public Object getObject() throws Exception {
    return get();
}

public synchronized T get() {
    if (destroyed){
        throw new IllegalStateException("Already destroyed!");
    }
	if (ref == null) {
		init();
	}
	return ref;
}

這兩個方法不用多說,判斷接口代理類是否已經存在,不存在調用init方法初始化,而該方法中大部分是檢查配置,關鍵點是createProxy方法:

private T createProxy(Map<String, String> map) {
	.......
	// 本地JVM調用
	if (isJvmRefer) {
		URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
		invoker = refprotocol.refer(interfaceClass, url);
           if (logger.isInfoEnabled()) {
               logger.info("Using injvm service " + interfaceClass.getName());
           }
	} else {
		// 配置了url屬性,可能是點對點調用,也可能是寫的註冊中心的url
        if (url != null && url.length() > 0) { 
            String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
            if (us != null && us.length > 0) {
                for (String u : us) {
                    URL url = URL.valueOf(u);
                    if (url.getPath() == null || url.getPath().length() == 0) {
                        url = url.setPath(interfaceName);
                    }
                    // 如果是registry協議,說明是想連接註冊中心,就設置refer參數到url
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    } else {
                        urls.add(ClusterUtils.mergeUrl(url, map));
                    }
                }
            }
        } else { 
        	// 加載註冊中心url,並將zookeeper協議轉爲registry協議
        	List<URL> us = loadRegistries(false);
        	if (us != null && us.size() > 0) {
            	for (URL u : us) {
            	    URL monitorUrl = loadMonitor(u);
                    if (monitorUrl != null) {
                        map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                    }
            	    urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                }
        	}
        }

        if (urls.size() == 1) {
        	// 只有一個註冊中心或者服務直連
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
        } else {
            // 多個註冊中心或者多個服務提供者直連,或者兩者混合
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null;
            for (URL url : urls) {
                invokers.add(refprotocol.refer(interfaceClass, url));
                if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                    registryURL = url; // 用了最後一個registry url
                }
            }
            if (registryURL != null) { // 有 註冊中心協議的URL
                // 對有註冊中心的Cluster 只用 AvailableCluster
                URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME); 
                invoker = cluster.join(new StaticDirectory(u, invokers));
            }  else { // 不是 註冊中心的URL
                invoker = cluster.join(new StaticDirectory(invokers));
            }
        }
    }
    
    // 創建服務代理
    return (T) proxyFactory.getProxy(invoker);
}

代碼很長,我截取了關鍵的部分,首先判斷是否爲本地injvm調用,若不是則加載服務直連的url或註冊中心的url,接着根據url數量判斷生成相應的invoker(url數量爲1說明是隻有一個註冊中心或者服務直連,則直接調用protocol.refer獲得相應的invoker;大於1代表是多個註冊中心或者多個服務提供者直連,或者兩者混合,則會生成多個invoker,通過cluster.join合併),最後調用proxyFactory.getProxy(invoker)生成相應代理類。所以,這裏主要邏輯就在invoker和代理類的生成。

Invoker的創建

在上一篇講過,Invoker在服務端是服務提供類的代理類,通過proxyFactory.getInvoker創建;而在客戶端,則用於執行遠程調用,通過protocol.refer調用。但是Protocol的實現類有很多,這裏主要分析單註冊中心和dubbo協議直連的情況。

單註冊中心的Invoker創建

如果是Zookeeper的單註冊中心方式,invoker就是通過invoker = refprotocol.refer(interfaceClass, urls.get(0))這句代碼創建的,這裏我們能夠很快的定位到RegistryProtocol.refer方法中:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
	// 將registry協議轉爲zookeeper協議
   url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
   // 這個代碼上一篇文章也分析過了,首先這裏是通過依賴注入注入的RegistryFactory$Adpative
   // 對象,最終會創建一個Zookeeper連接並返回ZookeeperRegistry對象
   Registry registry = registryFactory.getRegistry(url);
   if (RegistryService.class.equals(type)) {
   	return proxyFactory.getInvoker((T) registry, type, url);
   }

   // 分組服務走這裏
   Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
   String group = qs.get(Constants.GROUP_KEY);
   if (group != null && group.length() > 0 ) {
       if ( ( Constants.COMMA_SPLIT_PATTERN.split( group ) ).length > 1
               || "*".equals( group ) ) {
           return doRefer( getMergeableCluster(), registry, type, url );
       }
   }
   // 非分組服務走這裏,注意這裏的cluster也是依賴注入進來的
   return doRefer(cluster, registry, type, url);
}

需要注意cluster對象,它是通過依賴注入進來的,猜猜它是個什麼對象?是FailoverCluster嗎?其實並不是的,實際應該爲MockClusterWrapper(FailoverCluster)對象(在服務發佈一節中已有相應的分析),有什麼用,我們後面再分析,這裏暫時不討論。
在refer中主要是創建zookeeper連接,並獲取Registry對象,然後通過doRefer訂閱和調用服務。

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
	// 服務目錄,暫時不詳細分析它,知道它包含了所有的服務url就行了
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    // 註冊consumer服務
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
    if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
    // 監聽providers、configurators、routers、category節點的變化
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
            Constants.PROVIDERS_CATEGORY 
            + "," + Constants.CONFIGURATORS_CATEGORY 
            + "," + Constants.ROUTERS_CATEGORY));
	// 一個註冊中心可能存在多個相同的服務提供者,需要將其合併爲一個Invoker
    return cluster.join(directory);
}

這個方法首先創建了一個服務目錄,該目錄會監聽除consumer節點以外(本身就是consumer,當然沒必要再監聽consumer的變化了)的其它節點狀態,最後通過cluster合併服務目錄並返回invoker(Directory和Cluster等待後文來分析):

// MockClusterWrapper.join
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
	// 這裏的cluster是FailoverCluster
	return new MockClusterInvoker<T>(directory,
			this.cluster.join(directory));
}

// FailoverCluster.join
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    return new FailoverClusterInvoker<T>(directory);
}

從上面可以看到,這裏返回的是MockClusterInvoker對象,並持有FailoverClusterInvoker的引用,這個在分析服務調用過程時會用到。

Dubbo直連的Invoker創建

如果是通過Dubbo協議直連服務的話,也是通過invoker = refprotocol.refer(interfaceClass, urls.get(0))創建Invoker,只不過會進入到DubboProtocol.refer方法中:

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

這個方法主要通過getClients獲取客戶端ExchangeClient的實例,並實例化DubboInvoker返回。但是實際通信的肯定不是ExchangeClient,因爲服務端默認使用的是Netty,那麼客戶端也應該是,我們可以點進去看看如何創建的。

private ExchangeClient[] getClients(URL url){
    // 是否共享連接
    boolean service_share_connect = false;
    int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
    //如果connections不配置,則共享連接,否則每服務每次新建連接
    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] = getSharedClient(url);
        } else {
        	// 創建配置的連接數
            clients[i] = initClient(url);
        }
    }
    return clients;
}

dubbo:reference可以通過connections配置連接數,調用initClient方法創建連接,不配置默認使用共享連接,調用getSharedClient獲取,首先來看看getSharedClient方法:

private ExchangeClient getSharedClient(URL url){
	// 從緩存中獲取客戶端,該客戶端具有計數功能
    String key = url.getAddress();
    ReferenceCountExchangeClient client = referenceClientMap.get(key);
    if ( client != null ){
        if ( !client.isClosed()){
        	// 每被引用一次,計數就+1
            client.incrementAndGetCount();
            return client;
        } else {
            referenceClientMap.remove(key);
        }
    }
    // 創建客戶端實例
    ExchangeClient exchagneclient = initClient(url);
    // 使用ReferenceCountExchangeClient裝飾,使其具有引用計數功能
    client = new ReferenceCountExchangeClient(exchagneclient, ghostClientMap);
    referenceClientMap.put(key, client);
    ghostClientMap.remove(key);
    return client; 
}

該方法中也調用了initClient方法創建客戶端,並使用ReferenceCountExchangeClient裝飾,使其具有引用計數的功能,每被使用一次計數就+1。再看看客戶端是如何被創建的:

private ExchangeClient initClient(URL url) {
    
    // 獲取客戶端類型,默認使用netty
    String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));

    String version = url.getParameter(Constants.DUBBO_VERSION_KEY);
    boolean compatible = (version != null && version.startsWith("1.0."));
    url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() && compatible ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
    //默認開啓heartbeat
    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
    
    // BIO存在嚴重性能問題,暫時不允許使用
    if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
        throw new RpcException("Unsupported client type: " + str + "," +
                " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
    }
    
    ExchangeClient client ;
    try {
        // 創建客戶端時是否立即創建連接,lazy表示不立即創建
        if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)){
            client = new LazyConnectExchangeClient(url ,requestHandler);
        } else {
            client = Exchangers.connect(url ,requestHandler);
        }
    } catch (RemotingException e) {
        throw new RpcException("Fail to create remoting client for service(" + url
                + "): " + e.getMessage(), e);
    }
    return client;
}

默認創建一個netty客戶端,並根據配置判斷是否立即創建連接,若使用懶加載則會在請求服務時才創建連接。但不管是否是懶加載,都是通過Exchangers.connect方法創建的連接:

public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handler == null) {
        throw new IllegalArgumentException("handler == null");
    }
    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    return getExchanger(url).connect(url, handler);
}

這裏的getExchanger獲取到的和服務端一樣,也是HeaderExchanger

public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

是不是感覺似曾相識,還記得服務端是怎麼創建連接的吧,只不過當時是調用的Transporters.bind,而這裏是Transporters.connect:

public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    ChannelHandler handler;
    if (handlers == null || handlers.length == 0) {
        handler = new ChannelHandlerAdapter();
    } else if (handlers.length == 1) {
        handler = handlers[0];
    } else {
        handler = new ChannelHandlerDispatcher(handlers);
    }
    return getTransporter().connect(url, handler);
}

getTransporter方法也不用分析了,最終會進入到NettyTransporter.connect方法中:

public Client connect(URL url, ChannelHandler listener) throws RemotingException {
    return new NettyClient(url, listener);
}

就是去創建一個NettyClient客戶端連接Server進行通信(Netty的源碼分析不是本篇的重點,就先不分析了),這樣DubboInvoker中就持有了NettyClient的引用了。至此,Invoker創建流程分析完成,下面一起來看看代理類的創建。

創建代理類

上面用大量的篇幅分析了Invoker的創建,拿到Invoker對象之後通過proxyFactory.getProxy創建代理類對象,這個proxyFactory很熟悉了,也是通過自適應擴展機制獲取到的對象,所以應該會調用JavassistProxyFactory的getProxy方法,但是該類中沒有與之匹配的方法(方法名相同,但是參數列表不同),所以首先應該進入其父類AbstractProxyFactory的getProxy方法中:

public <T> T getProxy(Invoker<T> invoker) throws RpcException {
    Class<?>[] interfaces = null;
    // 從url中獲取接口
    String config = invoker.getUrl().getParameter("interfaces");
    if (config != null && config.length() > 0) {
        String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
        if (types != null && types.length > 0) {
            interfaces = new Class<?>[types.length + 2];
            interfaces[0] = invoker.getInterface();
            interfaces[1] = EchoService.class;
            for (int i = 0; i < types.length; i ++) {
                interfaces[i + 1] = ReflectUtils.forName(types[i]);
            }
        }
    }
    // 若url中未配置,則直接從invoker中獲取
    if (interfaces == null) {
        interfaces = new Class<?>[] {invoker.getInterface(), EchoService.class};
    }
    // 調用Javaassist的getProxy方法
    return getProxy(invoker, interfaces);
}

// JavaassistProxyFactory
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    // 生成 Proxy 子類Proxy0
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

這裏會通過模板方法調用JavassistProxyFactory.getProxy,然後通過Proxy類生成一個接口代理類proxy0和一個Proxy的子類Proxy0。Proxy0主要是實現父類的抽象方法newInstance(InvocationHandler handler)

public Object newInstance(java.lang.reflect.InvocationHandler h) { 
	return new com.alibaba.dubbo.common.bytecode.proxy0($1); 
}

可以看到該方法就是傳入一個InvocationHandler並初始化proxy0對象,剛剛說了proxy0是服務接口的代理類,因此它是實現了我們定義的服務接口及方法:

private java.lang.reflect.InvocationHandler handler;

public java.lang.String sayHello(java.lang.String arg0) {
	Object[] args = new Object[1]; 
	args[0] = ($w)$1; 
	Object ret = handler.invoke(this, methods[0], args); 
	return (java.lang.String)ret;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    Class<?>[] parameterTypes = method.getParameterTypes();
    if (method.getDeclaringClass() == Object.class) {
        return method.invoke(invoker, args);
    }
    if ("toString".equals(methodName) && parameterTypes.length == 0) {
        return invoker.toString();
    }
    if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
        return invoker.hashCode();
    }
    if ("equals".equals(methodName) && parameterTypes.length == 1) {
        return invoker.equals(args[0]);
    }
    return invoker.invoke(new RpcInvocation(method, args)).recreate();
}

最終就是去調用Invoker的invoker方法,所以這裏會進入到相應的MockClusterInvoker.invoke方法(非服務直連創建的Invoker)以及AbstractInvoker.invoke方法(Dubbo直連時,由於DubboInvoker中沒有invoker方法,所以調用其父類AbstractInvoker的)中,詳細的調用過程下一篇再分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章