【手撕源碼】Dubbo的工作機制&源碼分析 頂 原

Dubbo是基於Netty設計的

而Netty基於NIO

先簡單對NIO和Netty作簡單瞭解

1. NIO

NIO(Non-Blocking IO)起源於jdk1.4

NIO與BIO的作用是相同的,但使用方式完全不同

NIO支持面向緩衝區的、基於通道的IO操作,效率更高

NIO與BIO的區別:

  • BIO:面向流,阻塞

  • NIO:面向緩衝區,非阻塞,基於選擇器

  • 使用NIO,在程序讀取文件時,建立的不再是BIO中的“流”,而是通道

  • 數據傳輸的載體是緩衝區,這個緩衝區需要流動在通道里

    • 可以將通道理解爲鐵路,緩衝區理解爲火車:鐵路本身不具備傳輸能力,但它卻是火車通行的必要條件

在實際高併發中,如果使用BIO,要爲每一個請求都創建一個Socket,而每個Socket又需要對應的IO流,每個IO流又需要單獨的線程,這樣就會導致大量線程阻塞,系統運行緩慢。

如果換用NIO,因爲所有的流都被換爲通道Channel,而通道中使用緩衝區Buffer傳輸數據。所有的通道都交給一個選擇器Selector(也被稱爲多路複用器),由它來監聽這些通道的事件,來分別執行不同的策略。

2. Netty

Netty是一個異步事件驅動網絡應用程序框架

它通常用於開發可維護高性能協議服務器和客戶端

使用Netty,可以極大簡化TCP和UDP的SocketServer等網絡編程

Netty基於NIO,主要的底層邏輯如下:

  1. Netty服務器啓動,並監聽某一個端口

  2. 服務器啓動時,會初始化Netty的通道(NioServerSocketChannel),並註冊到Selector(暫且叫AcceptSelector)中,這個AcceptSelector只關心accept事件

  3. AcceptSelector會輪詢Netty通道的accept事件

  4. 當監聽到accept事件後,該AcceptSelector會處理accept事件,並與客戶端建立起一個新的通道(NioSocketChannel)

  5. 之後,將這個新的通道註冊到另一個Selector(暫且叫ReadWriteSelector)中,這個ReadWriteSelector只感興趣read和write事件

  6. ReadWriteSelector輪詢與客戶端連接的通道,當監聽到read就緒時,Netty會從通道中讀數據(基於任務隊列),write事件同理

  7. 當read和write完成預定目標後,整個流程結束

另外,Netty底層有一個Boss線程組,還有一個Worker線程組

  • Boss線程組負責監聽主線程Netty監聽的端口的連接就緒事件

  • Worker線程組負責具體的讀寫工作

-----

Dubbo是基於RPC調用的,也需要了解RPC的通信原理

3. RPC原理

一次完整的RPC調用過程需要經歷以下步驟

  1. 服務消費方以本地調用的方式,調用接口的方法(不是真正調用)

  2. 消費方RPC代理接收到調用請求後,將調用的方法、傳入的參數封裝(序列化)成可以進行網絡通信的消息體

  3. 消費方RPC代理找到服務地址,並將消息發送給服務提供方

  4. 提供方RPC代理收到消息後進行消息解碼

  5. 提供方RPC代理根據解碼後的消息,調用實際的本地服務(真正調用)

  6. 本地服務執行後將結果返回給提供方RPC代理

  7. 提供方RPC代理將返回的結果封裝(序列化)成消息體,傳送給消費方RPC代理

  8. 消費方RPC代理收到服務調用結果後進行消息解碼

  9. 服務消費方真正得到消息結果

對於實際開發中,只需要使用第1步和第9步,RPC屏蔽掉了2-8的步驟

4. Dubbo框架設計

官方文檔:http://dubbo.apache.org/zh-cn/docs/dev/design.html

4.1 Business部分

在Business部分,只有一個層面:Service

對於Service層,只是提供一個ServiceInterface,再在Provider中寫對應的ServiceImpl即可

也就是說,對於應用而言,到這裏就足以了

下面的部分全部都是Dubbo的底層原理

4.2 RPC部分

自上往下,依次有:

  • config 配置層:對外配置接口
    • 它是封裝配置文件中的配置信息
    • 以ServiceConfig, ReferenceConfig爲中心
    • 可以直接初始化配置類,也可以通過Spring解析配置生成配置類
  • proxy 服務代理層:服務接口透明代理,生成服務的客戶端Stub和服務器端Skeleton
    • 它實際上就是輔助生成RPC的代理對象
    • 以ServiceProx爲中心,擴展接口爲ProxyFactory
  • registry 註冊中心層:封裝服務地址的註冊與發現
    • 這一層就是註冊中心的核心功能層(服務發現、服務註冊)
    • 以服務URL爲中心,擴展接口爲RegistryFactory, Registry, RegistryService
  • cluster 路由層:封裝多個提供者的路由及負載均衡,並橋接註冊中心
    • 這一層的調用者Invoker可以保證多臺服務器的服務調用,以及實現負載均衡
    • 以Invoker爲中心,擴展接口爲Cluster, Directory, Router, LoadBalance
  • monitor 監控層:RPC調用次數和調用時間監控
    • 以Statistics爲中心,擴展接口爲MonitorFactory, Monitor, MonitorService
  • protocol 遠程調用層:封裝RPC調用
    • 這一層是RPC的調用核心
    • 以Invocation, Result爲中心,擴展接口爲Protocol, Invoker, Exporter

4.3 Remoting部分

自上往下,依次有:

  • exchange 信息交換層:封裝請求響應模式,同步轉異步
    • 這一層負責給服務提供方與服務消費方之間架起連接的管道
    • 以Request, Response爲中心,擴展接口爲Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
  • transport 網絡傳輸層:抽象mina和netty爲統一接口
    • 這一層負責真正的網絡數據傳輸,Netty也就在這一層被封裝
    • 以Message爲中心,擴展接口爲Channel, Transporter, Client, Server, Codec
  • serialize 數據序列化層:可複用的一些工具
    • 擴展接口爲Serialization, ObjectInput, ObjectOutput, ThreadPool

5. Dubbo啓動流程

因爲Dubbo的配置文件實際是Spring的配置文件

而Spring解析配置文件,最終都是迴歸到一個接口:BeanDefinitionParser

那就意味着,Dubbo肯定也提供了對應的BeanDefinitionParser:DubboBeanDefinitionParser

在BeanDefinitionParser中只定義了一個方法:

BeanDefinition parse(Element element, ParserContext parserContext);

下面剖析IOC容器啓動時標籤的解析機制

5.1 Dubbo的標籤解析機制

5.1.1 進入parse方法

可以看到parse方法僅有一句:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    return parse(element, parserContext, beanClass, required);
}

注意這裏的beanClass是成員變量而不是參數!

那難道說,每次解析標籤,都是一個全新的DubboBeanDefinitionParser?

5.1.2 自行聲明的parse方法

由於該方法太長,只取關鍵部分

if (ProtocolConfig.class.equals(beanClass)) {
    for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
        BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
        PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
        if (property != null) {
            Object value = property.getValue();
            if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
                definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
            }
        }
    }
} else if (ServiceBean.class.equals(beanClass)) {
    String className = element.getAttribute("class");
    if (className != null && className.length() > 0) {
        RootBeanDefinition classDefinition = new RootBeanDefinition();
        classDefinition.setBeanClass(ReflectUtils.forName(className));
        classDefinition.setLazyInit(false);
        parseProperties(element.getChildNodes(), classDefinition);
        beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
    }
} else if (ProviderConfig.class.equals(beanClass)) {
    parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
} else if (ConsumerConfig.class.equals(beanClass)) {
    parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}

這一部分在判斷beanClass的類型!

可Dubbo的配置文件中並沒有寫class=…之類的內容,它怎麼知道的?

那既然不知道beanClass從哪兒來的,就需要追到構造方法中了

5.1.3 DubboBeanDefinitionParser的構造方法

public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
    this.beanClass = beanClass;
    this.required = required;
}

構造方法很簡單,但這個Class<?>的參數從哪兒來的?

5.1.4 追溯到創建DubboBeanDefinitionParser的前一步

通過Debug可以發現,在這一步之前,還有一個調用過程,是調用了一個叫DubboNamespaceHandler的init方法

public void init() {
    registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
    registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
    registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
    registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
    registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
    registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
    registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
    registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
    registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
    registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}

從這個方法中,可以看到,這是把Dubbo配置文件中可能出現的標籤,全部轉化爲DubboBeanDefinitionParser了

也就是說,並不是解析一個標籤就創建一個DubboBeanDefinitionParser,而是把所有可能出現的標籤全部窮舉了,創建出對應的DubboBeanDefinitionParser,這樣遇到哪個標籤,就使用哪個標籤的解析器而已

而且注意到每次傳入的Class對象,要麼是xxxConfig,要麼是xxxBean

  • xxxConfig是標籤中的配置信息,只能出現一次

  • xxxBean是發佈的服務/引用的服務,每組標籤都會創建一個Bean

對於xxxConfig,只是把配置封裝到對應的Config類裏就可以了

但提供方發佈的服務、消費方的服務引用,是另外一種機制

5.2 Dubbo的服務發佈機制

所有的服務都被封裝爲ServiceBean,而ServiceBean類的定義信息比較複雜

public class ServiceBean<T> extends ServiceConfig<T> 
        implements InitializingBean, DisposableBean, ApplicationContextAware, 
                   ApplicationListener<ContextRefreshedEvent>, BeanNameAware

這個ServiceBean實現了幾個接口:

  • InitializingBean:實現該接口後,Spring會在該Bean創建完畢後回調afterPropertiesSet方法做後置處理

  • ApplicationListener<ContextRefreshedEvent>:實現該接口後,當Spring的IOC容器初始化完畢後,會回調onApplicationEvent方法

5.2.1 實現InitializingBean的afterPropertiesSet方法

原源碼太長,取部分源碼:

public void afterPropertiesSet() throws Exception {
        if (getProvider() == null) {
            Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);
            if (providerConfigMap != null && providerConfigMap.size() > 0) {
                Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
                if ((protocolConfigMap == null || protocolConfigMap.size() == 0)
                        && providerConfigMap.size() > 1) { // backward compatibility
                    List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
                    for (ProviderConfig config : providerConfigMap.values()) {
                        if (config.isDefault() != null && config.isDefault().booleanValue()) {
                            providerConfigs.add(config);
                        }
                    }
                    if (!providerConfigs.isEmpty()) {
                        setProviders(providerConfigs);
                    }
                } else {
                    ProviderConfig providerConfig = null;
                    for (ProviderConfig config : providerConfigMap.values()) {
                        if (config.isDefault() == null || config.isDefault().booleanValue()) {
                            if (providerConfig != null) {
                                throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);
                            }
                            providerConfig = config;
                        }
                    }
                    if (providerConfig != null) {
                        setProvider(providerConfig);
                    }
                }
            }
        }
        if (getApplication() == null
                && (getProvider() == null || getProvider().getApplication() == null)) {
            Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
            if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
                ApplicationConfig applicationConfig = null;
                for (ApplicationConfig config : applicationConfigMap.values()) {
                    if (config.isDefault() == null || config.isDefault().booleanValue()) {
                        if (applicationConfig != null) {
                            throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
                        }
                        applicationConfig = config;
                    }
                }
                if (applicationConfig != null) {
                    setApplication(applicationConfig);
                }
            }
        }
        //……………(已忽略)
        if (getPath() == null || getPath().length() == 0) {
            if (beanName != null && beanName.length() > 0
                    && getInterface() != null && getInterface().length() > 0
                    && beanName.startsWith(getInterface())) {
                setPath(beanName);
            }
        }
        if (!isDelay()) {
            export();
        }
    }

在每一個主幹if結構中,都能看到最後的一句話:

if (providerConfig!= null) {…}

如果判定成功,會把此前創建的那些Dubbo的配置標籤內容都存到這些ServiceBean中

5.2.2 實現ApplicationListener的onApplicationEvent方法

    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (isDelay() && !isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }

它調用了一個export方法

5.2.3 【關鍵】回調的export方法:暴露服務

該方法來源於父類ServiceConfig

    public synchronized void export() {
        if (provider != null) {
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        if (export != null && !export) {
            return;
        }

        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {
            doExport();
        }
    }

前面還是一些判斷,關鍵的步驟在下面的起線程調用doExport方法

5.2.4 doExport方法

    protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        if (exported) {
            return;
        }
        exported = true;
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }
        checkDefault();
        if (provider != null) {
            if (application == null) {
                application = provider.getApplication();
            }
            if (module == null) {
                module = provider.getModule();
            }
            if (registries == null) {
                registries = provider.getRegistries();
            }
            if (monitor == null) {
                monitor = provider.getMonitor();
            }
            if (protocols == null) {
                protocols = provider.getProtocols();
            }
        }
        if (module != null) {
            if (registries == null) {
                registries = module.getRegistries();
            }
            if (monitor == null) {
                monitor = module.getMonitor();
            }
        }
        if (application != null) {
            if (registries == null) {
                registries = application.getRegistries();
            }
            if (monitor == null) {
                monitor = application.getMonitor();
            }
        }
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            checkInterfaceAndMethods(interfaceClass, methods);
            checkRef();
            generic = Boolean.FALSE.toString();
        }
        if (local != null) {
            if ("true".equals(local)) {
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            try {
                localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        if (stub != null) {
            if ("true".equals(stub)) {
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            try {
                stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(stubClass)) {
                throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
        checkApplication();
        checkRegistry();
        checkProtocol();
        appendProperties(this);
        checkStubAndMock(interfaceClass);
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        doExportUrls();
        ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
        ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    }

還是好多好多的判斷、檢查,到最後執行了一個doExportUrls方法

5.2.5 doExportUrls方法

    private void doExportUrls() {
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

這個方法就是暴露服務的URL地址的!

這個方法先加載了註冊中心的URL,之後for循環了一組protocols

這組protocols實際上是Dubbo配置文件中配置的protocol標籤

之後它拿到這個protocol配置和註冊中心的URL,執行了一個doExportUrlsFor1Protocol方法

5.2.6 doExportUrlsFor1Protocol方法

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        String name = protocolConfig.getName();
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }

        Map<String, String> map = new HashMap<String, String>();
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
        map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
        if (methods != null && !methods.isEmpty()) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && !arguments.isEmpty()) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (methods != null && methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // target the method, and get its signature
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) {
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                            }
                                        } else {
                                            // multiple callbacks in the method
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                        throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }

                    }
                }
            } // end of methods for
        }

        if (ProtocolUtils.isGeneric(generic)) {
            map.put(Constants.GENERIC_KEY, generic);
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }

            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
            } else {
                map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(Constants.TOKEN_KEY, token);
            }
        }
        if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }
        // export service
        String contextPath = protocolConfig.getContextpath();
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }

        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        String scope = url.getParameter(Constants.SCOPE_KEY);
        // don't export when none is configured
        if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

            // export to local if the config is not remote (export to remote only when config is remote)
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (registryURLs != null && !registryURLs.isEmpty()) {
                    for (URL registryURL : registryURLs) {
                        url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }

                        // For providers, this is used to enable custom proxy to generate invoker
                        String proxy = url.getParameter(Constants.PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                        }

                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            }
        }
        this.urls.add(url);
    }

前面的大量內容還是做配置,關鍵的暴露部分在最後的幾行

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, 
                         registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);

先是拿到代理工廠,去獲取了一個Invoker執行器,從代碼層面也可以看出來,它其實是利用代理技術,將接口對應的實現類對象(ref),與該服務的暴露地址等信息封裝到一個新的代理對象中,這個對象就是Invoker執行器。

之後又調用了protocol的export方法

這個protocol對象,在上面可以看到它是使用瞭如下方法:

  • ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

這個方法其實是用的Java的SPI技術

5.2.6.1 【擴展】Java的SPI技術

關於Java中原生的SPI技術,可以參照博客

https://www.cnblogs.com/zhongkaiuu/articles/5040971.html

5.2.6.2 【擴展】Dubbo的SPI技術

Dubbo沒有使用jdk原生的SPI技術,而是自己另寫了一套

它的ExtensionLoader就可以類比於jdk的ServiceLoader,它也是用來加載指定路徑下的接口實現

更詳細的Dubbo-SPI,可以參照博客

https://blog.csdn.net/qiangcai/article/details/77750541

5.2.7 protocol的export方法

在Dubbo發佈服務時,由於涉及到註冊中心和服務本身,也就意味着需要找Protocol的兩個實現類,去調用他們的export方法

通過查找Protocol的實現類,可以發現有兩個需要使用的實現類:

RegistryProtocol,DubboProtocol

實際在進行Debug的時候,發現先進RegistryProtocol

5.2.8 RegistryProtocol的export方法

    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //export invoker
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);

        URL registryUrl = getRegistryUrl(originInvoker);

        //registry provider
        final Registry registry = getRegistry(originInvoker);
        final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);

        //to judge to delay publish whether or not
        boolean register = registeredProviderUrl.getParameter("register", true);

        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);

        if (register) {
            register(registryUrl, registeredProviderUrl);
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }

        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
    }

這個方法傳入的參數就是上面的那個服務接口的實現類的代理對象

這裏面有兩個關鍵的步驟:doLocalExport和ProviderConsumerRegTable.registerProvider

按照執行順序,先執行doLocalExport方法

5.2.9 doLocalExport方法

    private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
        String key = getCacheKey(originInvoker);
        ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
        if (exporter == null) {
            synchronized (bounds) {
                exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
                if (exporter == null) {
                    final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
                    exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
                    bounds.put(key, exporter);
                }
            }
        }
        return exporter;
    }

在這段中,一開始這個exporter是空的,需要使用雙檢鎖來構造一個exporter(懶漢單例模式的思路)

而創建的時候,是先構造了一個invokerDelegete,這個對象中包含兩份invoker,都是該服務的實現類代理

之後又調用了這個RegistryProtocol中聚合的另一個protocol的export方法

這個protocol就是上面提到的DubboProtocol

5.2.10 DubboProtocol的export方法

    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();

        // export service.
        String key = serviceKey(url);
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        exporterMap.put(key, exporter);

        //export an stub service for dispatching event
        Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }
            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }

        openServer(url);
        optimizeSerialization(url);
        return exporter;
    }

其實這個DubboProtocol不是指這個Dubbo框架,而是指發佈服務的協議使用的是dubbo協議

此外,從上面的Protocol的實現類來看,還有http的,Hessian的,WebService的,等等等

所以這裏的Dubbo並不是指框架,而是dubbo協議

這個方法一開始先從Invoker中拿到了url,之後封裝成一個DubboExporter對象

之後進行一部分if檢查後來到了另外一個關鍵的方法:openServer

5.2.11 【關鍵】openServer方法

   private void openServer(URL url) {
       // find server.
       String key = url.getAddress();
       //client can export a service which's only for server to invoke
       boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
       if (isServer) {
           ExchangeServer server = serverMap.get(key);
           if (server == null) {
               serverMap.put(key, createServer(url));
           } else {
               // server supports reset, use together with override
               server.reset(url);
           }
       }
   }

在這裏,因爲一開始是沒有ExchangeServer的,需要調用createServer方法

5.2.12 createServer方法

    private ExchangeServer createServer(URL url) {
        // send readonly event when server closes, it's enabled by default
        url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
        // enable heartbeat by default
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
        String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);

        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);

        url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
        ExchangeServer server;
        try {
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
        str = url.getParameter(Constants.CLIENT_KEY);
        if (str != null && str.length() > 0) {
            Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }
        return server;
    }

在創建ExchangeServer時,核心是try-catch中的那一句Exchanges.bind方法,要對url和requestHandler進行綁定

5.2.13 【Netty】Exchanges.bind方法

    public static ExchangeServer bind(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).bind(url, handler);
    }

    public static Exchanger getExchanger(URL url) {
        String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
        return getExchanger(type);
    }

    public static Exchanger getExchanger(String type) {
        return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
    }

從這裏開始,就一步一步導向Netty了

因爲這裏前面都是檢查,到最後return的時候,又是調用了另一個Exchanger的bind方法

這部分過程還是使用了SPI技術

使用IDE引導查看這個bind方法,會來到另外一個Exchanger:HeaderExchanger

5.2.13.1 HeaderExchanger的bind方法

    public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }

在這個方法中,又是調用了Transporters的靜態方法bind

5.2.13.2 Transporters的bind方法

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

可以發現這裏面已經出現ChannelHandler了,也就是NIO相關的東西了!

最後一步還是bind,不過這次不再是Exchanger的bind,而是Transporter的bind

而這個Transporter,不再是某一個固定的實現類了,而是幾個實現類中的一個

也就是這個地方,出現了使用Netty

這也就說明了Dubbo的RPC底層使用的是Netty

到這兒,openServer方法就執行完畢了

這段源碼實際做的事情,是Netty底層啓動,並監聽服務暴露的地址

5.2.14 回到DubboProtocol的export方法

    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        // ………

        openServer(url);
        optimizeSerialization(url);
        return exporter;
    }

數據交換服務器啓動後,將url序列化一下,整段export方法就執行完畢了

5.2.15 回到RegistryProtocol的export方法

前面提到了export方法中第二個關鍵方法是ProviderConsumerRegTable.registerProvider,進入到該方法:

5.2.16 ProviderConsumerRegTable.registerProvider方法

    public static ConcurrentHashMap<String, Set<ProviderInvokerWrapper>> providerInvokers = new ConcurrentHashMap<String, Set<ProviderInvokerWrapper>>();
    public static ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>> consumerInvokers = new ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>>();

    public static void registerProvider(Invoker invoker, URL registryUrl, URL providerUrl) {
        ProviderInvokerWrapper wrapperInvoker = new ProviderInvokerWrapper(invoker, registryUrl, providerUrl);
        String serviceUniqueName = providerUrl.getServiceKey();
        Set<ProviderInvokerWrapper> invokers = providerInvokers.get(serviceUniqueName);
        if (invokers == null) {
            providerInvokers.putIfAbsent(serviceUniqueName, new ConcurrentHashSet<ProviderInvokerWrapper>());
            invokers = providerInvokers.get(serviceUniqueName);
        }
        invokers.add(wrapperInvoker);
    }

注意這個類的名:提供方-消費方的註冊表,也就是說,這個類會保存服務提供方和消費方的調用地址和代理對象

可以看到在ProviderConsumerRegTable類中保存了兩個Map,這兩個Map實際上是保存了服務URL以及執行這個服務的Invoker(這些Invoker裏面有被代理的真正的服務實現類對象)

等上述方法完全執行完畢後,Dubbo服務發佈完成。

5.3 Dubbo的服務消費機制

所有的服務引用都被封裝爲ReferenceBean,而ReferenceBean類的定義信息比較複雜

public class ReferenceBean<T> extends ReferenceConfig<T> 
        implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean

這個ReferenceBean實現了幾個接口:

  • FactoryBean:Spring的工廠Bean

  • InitializingBean:實現該接口後,Spring會在該Bean創建完畢後回調afterPropertiesSet方法做後置處理

5.3.1 實現FactoryBean的效果

因爲ReferenceBean實現了該接口,所以在Controller引用這些Service時,由於Spring要完成自動注入,所以要調用這個ReferenceBean的getObject方法

5.3.2 調用getObject方法初始化服務引用

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

    //該方法來源於父類ReferenceConfig
    public synchronized T get() {
        if (destroyed) {
            throw new IllegalStateException("Already destroyed!");
        }
        if (ref == null) {
            init();
        }
        return ref;
    }

它調用了父類的get方法,而在父類ReferenceConfig中定義了init方法

5.3.3 init方法初始化服務引用

    private void init() {
        if (initialized) {
            return;
        }
        initialized = true;
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
        }
        // get consumer's global configuration
        checkDefault();
        appendProperties(this);
        if (getGeneric() == null && getConsumer() != null) {
            setGeneric(getConsumer().getGeneric());
        }
        if (ProtocolUtils.isGeneric(getGeneric())) {
            interfaceClass = GenericService.class;
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            checkInterfaceAndMethods(interfaceClass, methods);
        }
        // ………………

        //attributes are stored by system context.
        StaticContext.getSystemContext().putAll(attributes);
        ref = createProxy(map);
        ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
        ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
    }

前面還是跟服務發佈一樣,大量的檢查判斷

到最後有一句:createProxy,它負責創建服務的引用代理

5.3.4 【關鍵】createProxy方法

    private T createProxy(Map<String, String> map) {
        URL tmpUrl = new URL("temp", "localhost", 0, map);
        final boolean isJvmRefer;
        if (isInjvm() == null) {
            if (url != null && url.length() > 0) { // if a url is specified, don't do local reference
                isJvmRefer = false;
            } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
                // by default, reference local service if there is
                isJvmRefer = true;
            } else {
                isJvmRefer = false;
            }
        } else {
            isJvmRefer = isInjvm().booleanValue();
        }

        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 {
            if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
                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);
                        }
                        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 { // assemble URL from register center's configuration
                List<URL> us = loadRegistries(false);
                if (us != null && !us.isEmpty()) {
                    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.isEmpty()) {
                    throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
                }
            }

            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; // use last registry url
                    }
                }
                if (registryURL != null) { // registry url is available
                    // use AvailableCluster only when register's cluster is available
                    URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                    invoker = cluster.join(new StaticDirectory(u, invokers));
                } else { // not a registry url
                    invoker = cluster.join(new StaticDirectory(invokers));
                }
            }
        }

        Boolean c = check;
        if (c == null && consumer != null) {
            c = consumer.isCheck();
        }
        if (c == null) {
            c = true; // default true
        }
        if (c && !invoker.isAvailable()) {
            throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
        }
        if (logger.isInfoEnabled()) {
            logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
        }
        // create service proxy
        return (T) proxyFactory.getProxy(invoker);
    }

這個方法傳入了一個Map,裏面存了註冊中心的相關配置、服務的相關信息(接口名、方法名、超時時間等)

到中間,有一句最關鍵的:

invoker = refprotocol.refer(interfaceClass, urls.get(0));

它要使用refProtocol去註冊中心遠程引用目標服務接口的代理引用(url的內容就是註冊中心的地址)

而這個Protocol又是跟上面一樣,先進RegistryProtocol,再進DubboProtocol

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

與上面調用export類似,最關鍵的方法還是以do開頭的:doRefer方法

5.3.6 doRefer方法

    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);
        // all attributes of REFER_KEY
        Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
        URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
        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));

        Invoker invoker = cluster.join(directory);
        ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
        return invoker;
    }

在整個方法調用中,最核心的是去註冊中心訂閱服務

也就是說,這一步就是負責去訂閱服務提供者暴露的服務

當執行cluster.join方法時,會跳轉到DubboProtocol的refer方法中(SPI)

5.3.7 DubboProtocol的refer方法

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

與服務發佈類似,不過在這裏創建的是DubboInvoker,而這裏面最關鍵的是那一句getClients

5.3.8 【關鍵】getClients方法

    private ExchangeClient[] getClients(URL url) {
        // whether to share connection
        boolean service_share_connect = false;
        int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
        // if not configured, connection is shared, otherwise, one connection for one service
        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;
    }

這個方法跟上面的openServer類似

而這些客戶端從哪裏來,需要調用getSharedClient或initClient

5.3.9 getSharedClient方法與initClient方法

    private ExchangeClient getSharedClient(URL url) {
        String key = url.getAddress();
        ReferenceCountExchangeClient client = referenceClientMap.get(key);
        if (client != null) {
            if (!client.isClosed()) {
                client.incrementAndGetCount();
                return client;
            } else {
                referenceClientMap.remove(key);
            }
        }

        locks.putIfAbsent(key, new Object());
        synchronized (locks.get(key)) {
            if (referenceClientMap.containsKey(key)) {
                return referenceClientMap.get(key);
            }

            ExchangeClient exchangeClient = initClient(url);
            client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
            referenceClientMap.put(key, client);
            ghostClientMap.remove(key);
            locks.remove(key);
            return client;
        }
    }

    private ExchangeClient initClient(URL url) {

        // client type setting.
        String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));

        url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
        // enable heartbeat by default
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));

        // BIO is not allowed since it has severe performance issue.
        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 {
            // connection should be 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;
    }

可以發現,getSharedClient方法最終也是調用的initClient方法,所以核心還是在initClient方法中 前面還是值的檢驗、判空等邏輯

在最後的try-catch結構中,針對是否爲延遲連接,有兩種策略:

  • if中是延遲連接,在應用啓動時不連接到註冊中心

  • else中是非延遲連接,應用啓動時就與註冊中心建立連接

由於大多屬配置都是預先連接,故繼續看Exchangers的connect方法

5.3.10 【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);
    }

在最後一步,看到了與上面bind方法極爲相似的源碼

這也就意味着,這裏面的底層也是Netty

5.3.11 回到RegistryProtocol的refer方法

服務訂閱完成後,返回的還是Invoker,裏面存的是兩份directory,每一份directory中存放着服務的信息、客戶端的信息等

最後一步,又是那個註冊表,這次它把服務消費方也註冊進去了(不再貼源碼)

最終回到init方法,初始化完畢。


6. Dubbo的服務調用流程

Dubbo的調用鏈在官方文檔中也有描述:http://dubbo.apache.org/zh-cn/docs/dev/design.html

以源碼解析爲標準,分析調用過程

xxxService.xxxMethod();

當遠程調用方法時,經歷瞭如下過程

6.1 代理對象調用服務

上述的xxxService其實是一個代理對象,它基於InvokerInvocationHandler

public class InvokerInvocationHandler implements InvocationHandler {

    private final Invoker<?> invoker;

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

    @Override
    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.invoke(new RpcInvocation(method, args)).recreate();

在調用之前,要先把調用的方法和參數統一封裝成一個RpcInvocation對象

之後再調用invoke方法,它傳到了MockClusterInvoker類的invoke方法

(MockClusterInvoker是做本地僞裝,意爲如果建立連接後服務器宕機,客戶端不拋異常,而採取拋null)

(有關本地僞裝的配置可參照官方文檔:http://dubbo.apache.org/zh-cn/docs/user/demos/local-mock.html

6.2 MockClusterInvoker的invoke

    public Result invoke(Invocation invocation) throws RpcException {
        Result result = null;

        String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
        if (value.length() == 0 || value.equalsIgnoreCase("false")) {
            //no mock
            result = this.invoker.invoke(invocation);
        } else if (value.startsWith("force")) {
            if (logger.isWarnEnabled()) {
                logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
            }
            //force:direct mock
            result = doMockInvoke(invocation, null);
        } else {
            //fail-mock
            try {
                result = this.invoker.invoke(invocation);
            } catch (RpcException e) {
                if (e.isBiz()) {
                    throw e;
                } else {
                    if (logger.isWarnEnabled()) {
                        logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
                    }
                    result = doMockInvoke(invocation, e);
                }
            }
        }
        return result;
    }

在這裏面,第一個if結構裏又一個invoker.invoke方法,這個invoker實際上是FailoverClusterInvoker(集羣容錯與負載均衡的Invoker)

這就應該意識到這是層層包裝的結構了,類似於攔截器機制

再往裏走,因爲FailoverClusterInvoker有父類AbstractClusterInvoker,它重寫了invoke方法(FailoverClusterInvoker並沒有重寫)

6.3 AbstractClusterInvoker的invoke方法

    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();
        LoadBalance loadbalance = null;

        // binding attachments into invocation.
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }

        List<Invoker<T>> invokers = list(invocation);
        if (invokers != null && !invokers.isEmpty()) {
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                    .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
        }
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        return doInvoke(invocation, invokers, loadbalance);
    }

在中間,它要調用list方法來獲取一組Invoker,這個方法是去註冊中心找有多少個能遠程調用該服務的可用服務Invoker

——| 其實list方法裏只有一句:directory.list(invocation);

之後的if結構是負責處理負載均衡的

最後return的時候又去調doInvoke方法

由於在AbstractClusterInvoker中該方法爲模板方法,根據多態,又回到FailoverClusterInvoker中調用

6.4 FailoverClusterInvoker的doInvoke方法

    public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException{
        List<Invoker<T>> copyinvokers = invokers;
        checkInvokers(copyinvokers, invocation);
        int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
        if (len <= 0) {
            len = 1;
        }
        // retry loop.
        RpcException le = null; // last exception.
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
        Set<String> providers = new HashSet<String>(len);
        for (int i = 0; i < len; i++) {
            //Reselect before retry to avoid a change of candidate `invokers`.
            //NOTE: if `invokers` changed, then `invoked` also lose accuracy.
            if (i > 0) {
                checkWhetherDestroyed();
                copyinvokers = list(invocation);
                // check again
                checkInvokers(copyinvokers, invocation);
            }
            Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
            invoked.add(invoker);
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {
                    logger.warn("Although retry the method " + invocation.getMethodName()
                            + " in the service " + getInterface().getName()
                            + " was successful by the provider " + invoker.getUrl().getAddress()
                            + ", but there have been failed providers " + providers
                            + " (" + providers.size() + "/" + copyinvokers.size()
                            + ") from the registry " + directory.getUrl().getAddress()
                            + " on the consumer " + NetUtils.getLocalHost()
                            + " using the dubbo version " + Version.getVersion() + ". Last error is: "
                            + le.getMessage(), le);
                }
                return result;
            } catch (RpcException e) {
                if (e.isBiz()) { // biz exception.
                    throw e;
                }
                le = e;
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {
                providers.add(invoker.getUrl().getAddress());
            }
        }
        throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method "
                + invocation.getMethodName() + " in the service " + getInterface().getName()
                + ". Tried " + len + " times of the providers " + providers
                + " (" + providers.size() + "/" + copyinvokers.size()
                + ") from the registry " + directory.getUrl().getAddress()
                + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
                + Version.getVersion() + ". Last error is: "
                + (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
    }

在循環的try-catch中,又是invoker.invoke方法

但這裏面不再是ClusterInvoker了,根據調用流程圖,可以發現,接下來是一組Filter

把這組Filter走完之後,最終負責執行的是DubboInvoker

6.5 DubboInvoker的doInvoke方法

    protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
        inv.setAttachment(Constants.VERSION_KEY, version);

        ExchangeClient currentClient;
        if (clients.length == 1) {
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }
        try {
            boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
            if (isOneway) {
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                currentClient.send(inv, isSent);
                RpcContext.getContext().setFuture(null);
                return new RpcResult();
            } else if (isAsync) {
                ResponseFuture future = currentClient.request(inv, timeout);
                RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
                return new RpcResult();
            } else {
                RpcContext.getContext().setFuture(null);
                return (Result) currentClient.request(inv, timeout).get();
            }
        } catch (TimeoutException e) {
            throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        } catch (RemotingException e) {
            throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

第一組if-else中,需要拿到當前請求的客戶端,之後再try-catch的最後一個return中執行request方法

這個方法就是真正請求遠程調用的方法

6.6 【Netty】client.request方法

這個request經歷了兩次跳轉,最終來到HeaderExchangeChannel類的request方法

//ReferenceCountExchangeClient
    public ResponseFuture request(Object request, int timeout) throws RemotingException {
        return client.request(request, timeout);
    }

//HeaderExchangeClient
    public ResponseFuture request(Object request, int timeout) throws RemotingException {
        return channel.request(request, timeout);
    }

//HeaderExchangeChannel
    public ResponseFuture request(Object request, int timeout) throws RemotingException {
        if (closed) {
            throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
        }
        // create request.
        Request req = new Request();
        req.setVersion(Version.getProtocolVersion());
        req.setTwoWay(true);
        req.setData(request);
        DefaultFuture future = new DefaultFuture(channel, req, timeout);
        try {
            channel.send(req);
        } catch (RemotingException e) {
            future.cancel();
            throw e;
        }
        return future;
    }

在最後的try-catch中,用channel把請求發送出去

而這個Channel,就是底層調用Netty的Channel進行通信調用了。

最終獲取到結果後,判斷一次是否超時,如果沒有超時,完整的遠程服務調用就完成了。

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