Dubbo原始碼分析(三)Dubbo的服務引用Refer

狼圖騰xxx發表於2018-10-04

Dubbo的服務引用

服務引用

先從Dubbo的配置檔案看起

<dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService"/>
複製程式碼

原始碼入口: 根據上一篇說的,我們通過DubboNamespaceHandler類找到ReferenceBean類,在afterPropertiesSet()方法中我們找到關鍵程式碼getObject()
進入ReferenceConfig類中的get()方法,這個get() 方法是一個同步方法,呼叫了init()方法
我們看到init()方法中的最後一行程式碼ref = createProxy(map);我們從這這個方法開始分析:

private T createProxy(Map<String, String> 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);
    }
複製程式碼

先看invoker = refprotocol.refer(interfaceClass, urls.get(0))這行程式碼
此時的refprotocol= Protocol$Adatptive,進入refer方法:

public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
複製程式碼

此時的extName=registry,所以extension=ProtocolFilterWrapper(ProtocolListenerWrapper(RegistryProtocol)),我們直接進入RegistryProtocol.refer()方法中

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
            return proxyFactory.getInvoker((T) registry, type, url);
        }

        // group="a,b" or group="*"
        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);
            }
        }
        return doRefer(cluster, registry, type, url);
}
複製程式碼

看到第二行程式碼Registry registry = registryFactory.getRegistry(url);這裡從字面上理解應該是建立和註冊中心的連線,這裡的程式碼和服務端釋出是一樣的,這裡跳過,繼續往下走group,Dubbo裡面是可以對服務進行分組,這裡不影響主流程走向,我們跳過,看到最後一行程式碼,我們進入

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        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)));
        }
        directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
                Constants.PROVIDERS_CATEGORY
                        + "," + Constants.CONFIGURATORS_CATEGORY
                        + "," + Constants.ROUTERS_CATEGORY));
        return cluster.join(directory);
    }
複製程式碼

先看subscribeUrl是啥,這裡的url是consumer開頭的url,看到registry.register()方法,這裡是向註冊中心去註冊消費端資訊,具體註冊的節點是:/dubbo/com.alibaba.dubbo.demo.DemoService/consumers
directory.subscribe(),這句程式碼一看就明白,應該是向註冊中心訂閱我們剛剛註冊的地址,我們進入到這個方法裡面去看看如果目錄地址有變化,怎麼通知,該做什麼樣的處理,最終的實現類是ZookeeperRegistry.doSubscribe()方法中,這裡用到了模板方法,我們看到doSubscribe()方法中的這段程式碼notify(url, listener, urls)

 protected void notify(URL url, NotifyListener listener, List<URL> urls) {
       ···
        try {
            doNotify(url, listener, urls);
        } catch (Exception t) {
            // 將失敗的通知請求記錄到失敗列表,定時重試
            Map<NotifyListener, List<URL>> listeners = failedNotified.get(url);
            ···
            listeners.put(listener, urls);
           ···
        }
    }
複製程式碼

這裡面執行了doNotify方法,如果執行失敗,對應的通過定時策略去重試,繼續進入doNotify方法

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
        ···
        for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
            String category = entry.getKey();
            List<URL> categoryList = entry.getValue();
            categoryNotified.put(category, categoryList);
            saveProperties(url);
            listener.notify(categoryList);
        }
    }
複製程式碼

這個是AbstractRegistry類中的方法,我們看到saveProperties方法,作用是把消費端註冊的url資訊快取到本地

registryCacheExecutor.execute(new SaveProperties(version));
複製程式碼

然後通過執行緒池來定時快取資料,我們繼續看一下listener.notify(categoryList)這句程式碼,這裡的listener是RegistryDirectory

public synchronized void notify(List<URL> urls) {
        List<URL> invokerUrls = new ArrayList<URL>();
        List<URL> routerUrls = new ArrayList<URL>();
        List<URL> configuratorUrls = new ArrayList<URL>();
        for (URL url : urls) {
            String protocol = url.getProtocol();
            String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            if (Constants.ROUTERS_CATEGORY.equals(category)
                    || Constants.ROUTE_PROTOCOL.equals(protocol)) {
                routerUrls.add(url);
            } else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
                    || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
                configuratorUrls.add(url);
            } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
                invokerUrls.add(url);
            } else {
                logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
            }
        }
       ···
        // providers
        refreshInvoker(invokerUrls);
    }
複製程式碼

看到最後一段程式碼refreshInvoker(invokerUrls)

 /**
     * 根據invokerURL列表轉換為invoker列表。轉換規則如下:
     * 1.如果url已經被轉換為invoker,則不在重新引用,直接從快取中獲取,注意如果url中任何一個引數變更也會重新引用
     * 2.如果傳入的invoker列表不為空,則表示最新的invoker列表
     * 3.如果傳入的invokerUrl列表是空,則表示只是下發的override規則或route規則,需要重新交叉對比,決定是否需要重新引用。
     *
     * @param invokerUrls 傳入的引數不能為null
     */
    // TODO: FIXME 使用執行緒池去重新整理地址,否則可能會導致任務堆積
    private void refreshInvoker(List<URL> invokerUrls) {
        if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
                && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            this.forbidden = true; // 禁止訪問
            this.methodInvokerMap = null; // 置空列表
            destroyAllInvokers(); // 關閉所有Invoker
        } else {
            this.forbidden = false; // 允許訪問
            Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
            if (invokerUrls.size() == 0 && this.cachedInvokerUrls != null) {
                invokerUrls.addAll(this.cachedInvokerUrls);
            } else {
                this.cachedInvokerUrls = new HashSet<URL>();
                this.cachedInvokerUrls.addAll(invokerUrls);//快取invokerUrls列表,便於交叉對比
            }
            if (invokerUrls.size() == 0) {
                return;
            }
            Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// 將URL列表轉成Invoker列表
            Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 換方法名對映Invoker列表
            // state change
            //如果計算錯誤,則不進行處理.
            if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
                logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
                return;
            }
            this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
            this.urlInvokerMap = newUrlInvokerMap;
            try {
                destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // 關閉未使用的Invoker
            } catch (Exception e) {
                logger.warn("destroyUnusedInvokers error. ", e);
            }
        }
    }
複製程式碼

這段程式碼的最終目的是重新整理urlInvokerMap快取,並且關閉關閉未使用的Invoker 接下來我們繼續cluster.join(directory)這個方法 ,此時的cluster=Cluster$Adaptive

public com.alibaba.dubbo.rpc.Invoker join(com.alibaba.dubbo.rpc.cluster.Directory arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("cluster", "failover");
        if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.Cluster) name from url(" + url.toString() + ") use keys([cluster])");
        com.alibaba.dubbo.rpc.cluster.Cluster extension = (com.alibaba.dubbo.rpc.cluster.Cluster)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
        return extension.join(arg0);
    }
複製程式碼

此時extension=MockClusterWrapper(FaileOverCluster), 這裡有一個Mock包裝類,猜想一下,這個Mock應該是Dubbo的容錯機制中用到的Mock,進入MockClusterWrapper.join方法

public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new MockClusterInvoker<T>(directory,
                this.cluster.join(directory));
    }
複製程式碼

這裡new了一個MockClusterInvoker,進入FaileOverCluster.join方法

public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker<T>(directory);
    }
複製程式碼

這裡new 了一個FailoverClusterInvoker,然後回到最初的ReferenceConfig.createProxy方法,看到最後一段程式碼return (T) proxyFactory.getProxy(invoker);這段程式碼的作用是建立服務代理,這裡的invoker就是我們剛剛new的MockClusterInvoker,這裡的proxyFactory=ProxyFactory$Adaptive,直接貼結果,進入StubProxyFactoryWrapper.getProxy

public <T> T getProxy(Invoker<T> invoker) throws RpcException {
        T proxy = proxyFactory.getProxy(invoker);
        if (GenericService.class != invoker.getInterface()) {
            String stub = invoker.getUrl().getParameter(Constants.STUB_KEY, invoker.getUrl().getParameter(Constants.LOCAL_KEY));
            if (ConfigUtils.isNotEmpty(stub)) {
                Class<?> serviceType = invoker.getInterface();
                if (ConfigUtils.isDefault(stub)) {
                    if (invoker.getUrl().hasParameter(Constants.STUB_KEY)) {
                        stub = serviceType.getName() + "Stub";
                    } else {
                        stub = serviceType.getName() + "Local";
                    }
                }
                try {
                    Class<?> stubClass = ReflectUtils.forName(stub);
                    if (!serviceType.isAssignableFrom(stubClass)) {
                        throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + serviceType.getName());
                    }
                    try {
                        Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType);
                        proxy = (T) constructor.newInstance(new Object[]{proxy});
                        //export stub service
                        URL url = invoker.getUrl();
                        if (url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT)) {
                            url = url.addParameter(Constants.STUB_EVENT_METHODS_KEY, StringUtils.join(Wrapper.getWrapper(proxy.getClass()).getDeclaredMethodNames(), ","));
                            url = url.addParameter(Constants.IS_SERVER_KEY, Boolean.FALSE.toString());
                            try {
                                export(proxy, (Class) invoker.getInterface(), url);
                            } catch (Exception e) {
                                LOGGER.error("export a stub service error.", e);
                            }
                        }
                    } catch (NoSuchMethodException e) {
                        throw new IllegalStateException("No such constructor \"public " + stubClass.getSimpleName() + "(" + serviceType.getName() + ")\" in stub implemention class " + stubClass.getName(), e);
                    }
                } catch (Throwable t) {
                    LOGGER.error("Failed to create stub implemention class " + stub + " in consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", cause: " + t.getMessage(), t);
                    // ignore
                }
            }
        }
        return proxy;
    }
複製程式碼

我們先看第一行程式碼 T proxy = proxyFactory.getProxy(invoker);
這裡的proxyFactory=JavassitProxyFactory,我們首先進入的是AbstractProxyFactory.getProxy方法,這裡又是一個模版方法,

public <T> T getProxy(Invoker<T> invoker) throws RpcException {
        Class<?>[] interfaces = null;
        String config = invoker.getUrl().getParameter("interfaces");
        ···
        if (interfaces == null) {
            interfaces = new Class<?>[]{invoker.getInterface(), EchoService.class};
        }
        return getProxy(invoker, interfaces);
    }
複製程式碼

進入JavassitProxyFactory.getProxy方法,

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

這裡傳入的interfaces=[interface com.alibaba.dubbo.demo.DemoService, interface com.alibaba.dubbo.rpc.service.EchoService]
再進入new InvokerInvocationHandler(invoker),這裡初始化一個InvokerInvocationHandler物件,我們看下這個物件

public class InvokerInvocationHandler implements InvocationHandler {

    private final Invoker<?> invoker;

    public InvokerInvocationHandler(Invoker<?> handler) {
        this.invoker = handler;
    }

    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();
    }

}
複製程式碼

這裡用了JDK自帶的動態代理Proxy類和InvocationHandler介面,到這裡proxy代理類建立完成。

總結

從Dubbo官網上找到一張引用服務的時序圖

Dubbo原始碼分析(三)Dubbo的服務引用Refer

相關文章