dubbo源碼解析之——原理入門

概述

該瞭解dubbo哪些基本知識?

首先,根據官網介紹,Apache Dubbo™ 是一款高性能Java RPC框架,所謂RPC框架,就是指一臺服務器可以像調用本地對象一樣調用另一臺服務器上對應的方法。這就是RPC,而dubbo只是其中的一種。像RMI,gRPC(Google),Motan都屬於RPC框架。

官方背景介紹:http://dubbo.apache.org/zh-cn/docs/user/preface/background.html

 

Spring集成

一般通過Spring集成的框架,都要看看它的初始化步驟,dubbo也不例外,通過Spring套路可以很好地學習dubbo的原理;

首先,要搞清楚一些基本認知:

  • dubbo的配置在spring中都有被管理,就是說dubbo的那些<dubbo:service/>,<dubbo:application/>,<dubbo:protocol/>,<dubbo:consumer/>等等,都是Spring管理的bean;
  • dubbo的初始化也遵循Spring的套路,無非就是通過實現初始化接口,後置處理器接口,監聽器接口等實現;
  • dubbo的基本操作:服務導出,服務註冊(創建註冊中心),服務發現,服務推送等等。

 

dubbo bean的註冊

首先來看第一點,既然是Bean,那麼就一定有對應的BeanClass,dubbo的配置裏面又沒有給出具體的BeanClass,那Spring是怎麼給這些BeanDefinition賦Class的呢?

如果跟蹤Spring源碼,會發現在這一步完成後BeanClass就已經賦值了。

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

所以肯定是obtainFreshBeanFactory方法裏面進行的賦值;

 詳細Spring步驟就不貼代碼,最終是走到了XmlBeanDefinitionReader裏面,貼個調用鏈:

 

org.springframework.beans.factory.xml.XmlBeanDefinitionReader

private NamespaceHandlerResolver namespaceHandlerResolver;

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 大家看這個類是純Spring的類,但是dubbo:service最終的BeanClass一定是dubbo的Class,那怎麼會賦值dubbo的Class呢?
    // 猜想這裏肯定是通過命名空間Handler來獲取BeanClass的。

    // 這裏要仔仔細細的看,先看這個createReaderContext方法
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

public XmlReaderContext createReaderContext(Resource resource) {
    // 這裏是重點,因爲這個getNamespaceHandlerResolver方法,很可能就跟dubbo產生關係了
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
            this.sourceExtractor, this, getNamespaceHandlerResolver());
}

public NamespaceHandlerResolver getNamespaceHandlerResolver() {
    if (this.namespaceHandlerResolver == null) {
        // 跟代碼debug的話,到這裏就會得到dubboNamespaceHandler
        this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
    }
    return this.namespaceHandlerResolver;
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
    // 這裏根據ClassLoader來確定
    return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}

 org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

META-INF/spring.handlers

http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

META-INF/dubbo.xsd 

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://code.alibabatech.com/schema/dubbo"
			xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			xmlns:beans="http://www.springframework.org/schema/beans"
			xmlns:tool="http://www.springframework.org/schema/tool"
			targetNamespace="http://code.alibabatech.com/schema/dubbo">

    <!-- dubbo:service 結點 -->
    <xsd:element name="service" type="serviceType">
        <xsd:annotation>
        <xsd:documentation><![CDATA[ Export service config ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>

</xsd:schema>

 至於如何取dubbo下面的spring.handler文件,更詳細的可以參考這篇文章:https://www.jianshu.com/p/91f6068adff2

 

繼續看註冊代碼

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    Element root = doc.getDocumentElement();
    // 套路命名doXXXXX
    doRegisterBeanDefinitions(root);
}

protected void doRegisterBeanDefinitions(Element root) {
    // Any nested <beans> elements will cause recursion in this method. In
    // order to propagate and preserve <beans> default-* attributes correctly,
    // keep track of the current (parent) delegate, which may be null. Create
    // the new (child) delegate with a reference to the parent for fallback purposes,
    // then ultimately reset this.delegate back to its original (parent) reference.
    // this behavior emulates a stack of delegates without actually necessitating one.
    BeanDefinitionParserDelegate parent = this.delegate; // 首先拿到一個委託解析類的對象用於文件解析
    this.delegate = createDelegate(getReaderContext(), root, parent);
    // 判斷釋放解析過了,如果已經解析過了,就返回不解析,acceptsProfiles這個方法控制
    // 判斷是否是root結點:beans,只有beans結點才解析
    // 所以這就是dubbo的配置文件根節點是beans
    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                            "] not matching: " + getReaderContext().getResource());
                }
                return;
            }
        }
    }

    // 預處理,就是可以擴展原有的結點,但需要自己實現,這裏Spring給的是空實現,所以不關心
    preProcessXml(root);

    // 實際解析在這一步
    parseBeanDefinitions(root, this.delegate);

    // 對應preProcessXml
    postProcessXml(root);

    this.delegate = parent;
}

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 解析配置xml,逐一處理結點,從root:beans開始
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = getNamespaceURI(ele);
    // 這裏面獲得的handler就是DubboNamespaceHandler
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    // 返回BeanDefinition
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

// 這一步返回真正的NamespaceHandler
public NamespaceHandler resolve(String namespaceUri) {
    // 解析spring.handlers獲取映射關係
    Map<String, Object> handlerMappings = getHandlerMappings();
    // 這裏的namespaceUri = http://code.alibabatech.com/schema/dubbo
    // 對應value = com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {
        return (NamespaceHandler) handlerOrClassName;
    }
    else {
        String className = (String) handlerOrClassName;
        try {
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                        "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            }
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            namespaceHandler.init();
            handlerMappings.put(namespaceUri, namespaceHandler);
            return namespaceHandler;
        }
        catch (ClassNotFoundException ex) {
            throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "] not found", ex);
        }
        catch (LinkageError err) {
            throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "]: problem with handler class file or dependent class", err);
        }
    }
}

先來看看 com.alibaba.dubbo.config.spring.schema. DubboNamespaceHandler

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }
    // 這個handler的作用就是給它的不同的結點定義不同的BeanClass
    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 DubboBeanDefinitionParser(AnnotationBean.class, true));
    }

}

可以看到DubboNamespaceHandler的繼承關係,父類中的Map:parsers用於結點BeanClass的註冊,註冊的方式是通過<K,V>的形式,註冊的內容是一個BeanDefinitionParser,所以到這裏我們又看到一種BeanClass的註冊方式,正常Bean可以通過xml配置,可以通過直接掃描目錄,也可以像現在這樣,通過一個BeanDefinitionParser來實現;

DubboNamespaceHandler類沒有重寫parse方法,所以它用的是父類的方法,如下:

org.springframework.beans.factory.xml.NamespaceHandlerSupport 

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 先找到BeanDefinitionParser,再調用 ***** parse ***** 方法得到BeanDefinition
    return findParserForElement(element, parserContext).parse(element, parserContext);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    String localName = parserContext.getDelegate().getLocalName(element);
    // dubbo通過初始化init方法已經把相應的<key:節點名稱, Value:對應的parser>註冊進parsers了
    // 這裏只需要根據Bean的名稱得到對應的parser
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

com.alibaba.dubbo.config.spring.schema.DubboBeanDefinitionParser

@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
    RootBeanDefinition beanDefinition = new RootBeanDefinition();
    // 看到了吧!!!!終於找到setBeanClass了!!!!!
    beanDefinition.setBeanClass(beanClass);
    beanDefinition.setLazyInit(false);
    String id = element.getAttribute("id");

    // 後面省略n行代碼
}

綜上,總結了dubbo各個結點的註冊邏輯。下面進入服務導出和註冊邏輯

 

 

dubbo服務導出和註冊

其實這部分代碼,在官網上面有詳細的介紹,我也不打算貼的很詳細了,但是幾個概念要先明確;

服務導出,服務註冊,服務訂閱等

首先,服務註冊操作對於 Dubbo 來說不是必需的,通過服務直連的方式就可以繞過註冊中心。但通常我們不會這麼做,直連方式不利於服務治理,僅推薦在測試服務時使用。對於 Dubbo 來說,註冊中心雖不是必需,但卻是必要的。——官網原話

1、當沒有註冊中心的時候,只需要服務導出就可以了,它分爲本地服務導出和遠程服務導出,又叫本地暴露和遠程暴露。

  • 本地暴露就是暴露在當前JVM下,處理類是InjvmProtocal,簡單理解就是把服務封裝好放到某個類的集合或者Map中去;
  • 遠程暴露就簡單說就是起一個Server Socket(dubbo協議實際是用Netty,http協議使用HttpServer,不是所有協議都支持遠程暴露,像redis就不支持,doExport直接拋異常),等待遠端客戶端連接,根據協議選用不同Protocal,比如:dubbo協議的處理類是DubboProtocal;

但不管是本地暴露還是單純的遠程暴露,都沒有服務治理功能,因此dubbo又引入的註冊中心,默認使用ZK作爲註冊中心;在dubbo源碼中,遠程暴露分爲有註冊中心和無註冊中心兩種;

爲什麼會有本地暴露和遠程暴露呢?

在dubbo中,一個服務可能既是Provider又是Consumer,因此就存在自己調用自己服務的情況,如果這種場景下再通過網絡去訪問,那自然是捨近求遠,因此就有了本地暴露這個設計。

  • 本地暴露是暴露在本機JVM中,調用本地服務不需要網絡通信,本地暴露的url是以injvm開頭的;
  • 遠程暴露是將ip,端口等信息暴露給遠程客戶端,使用Netty作爲服務端,調用遠程服務時需要網絡通信;

2、當有註冊中心的時候,除了服務導出還要服務註冊服務註冊其實是在/root/interface/providers下創建一個臨時節點,這個節點的路徑就是服務的url。而取消註冊就是將該節點刪除。服務註冊必然還要創建註冊中心,註冊中心可以有很多,下面以ZK的代碼介紹;

如圖,以dubbo協議爲例,如果有註冊中心,遠程暴露先走封裝類RegistryProtocol,先服務導出(服務導出就起Netty服務了),後服務註冊;如果是沒有註冊中心的話只起Netty服務。

 

先看下ServiceBean的繼承關係

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

    private static final long serialVersionUID = 213195494150089726L;

    private static transient ApplicationContext SPRING_CONTEXT;
    
    private transient ApplicationContext applicationContext;

    private transient String beanName;

}

這個BeanClass繼承了幾個關鍵的接口:

  • InitializingBean:初始化接口,實現方法:afterPropertiesSet
  • ApplicationListener:監聽器接口,實現方法:onApplicationEvent
  • DisposableBean:銷燬接口,實現方法:destroy
public void afterPropertiesSet() throws Exception {   
    if (getProvider() == null) {
        ..............
        //獲取provider配置
    }
    if (getApplication() == null && (getProvider() == null || getProvider().getApplication() == null)) {
        ...............
        //獲取application配置
    }
    if (getModule() == null && (getProvider() == null || getProvider().getModule() == null)) {
        ...............
        //獲取module配置
    }
    if ((getRegistries() == null || getRegistries().size() == 0)
         && (getProvider() == null || getProvider().getRegistries() == null || getProvider().getRegistries().size() == 0)
         && (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().size() == 0)) {
      .................
        //獲取註冊中心的配置        
    }
    if (getMonitor() == null
         && (getProvider() == null || getProvider().getMonitor() == null)
         && (getApplication() == null || getApplication().getMonitor() == null)) {
        ................
        //獲取monitor配置        
    }
    if ((getProtocols() == null || getProtocols().size() == 0)
         && (getProvider() == null || getProvider().getProtocols() == null || getProvider().getProtocols().size() == 0)) {
        ...............
        //獲取protocol配置         
    }
    if (getPath() == null || getPath().length() == 0) { //獲取<dubbo:service/>的path屬性,path即服務的發佈路徑
        if (beanName != null && beanName.length() > 0 
             && getInterface() != null && getInterface().length() > 0
             && beanName.startsWith(getInterface())) {
            setPath(beanName);  //如果沒有設置path屬性,則默認會以beanName作爲path
        }
    }
    // delay:dubbo屬性配置,延遲註冊服務時間(毫秒) ,默認值0,設爲-1時,表示延遲到Spring容器初始化完成時暴露服務
    // 需要注意的是這裏說的默認0是指需要明確加上delay屬性的,如果不加delay屬性,那就是null,null相當於-1,即:延遲
    // 如果設置service是延遲的,那麼這裏就不加載了
    // isDelay方法返回 true 時,表示無需延遲導出。返回 false 時,表示需要延遲導出
    if (! isDelay()) {
        export();      //進行服務導出
    }
}


public void onApplicationEvent(ApplicationEvent event) {
    if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
        // 延遲的 && 未發佈的 && 未被取消導出
        // isDelay方法返回 true 時,表示無需延遲導出。返回 false 時,表示需要延遲導出。
        if (isDelay() && ! isExported() && ! isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            // 服務發佈
            export();
        }
    }
}

public void destroy() throws Exception {
    unexport();
}

重點看服務導出方法,export是父類的方法

com.alibaba.dubbo.config.ServiceConfig

public synchronized void export() {
    if (provider != null) {
        if (export == null) {
            export = provider.getExport();
        }
        if (delay == null) {
            delay = provider.getDelay();
        }
    }
    if (export != null && ! export.booleanValue()) {
        return;
    }
    if (delay != null && delay > 0) {
        // 延遲加載,爲啥這麼處理,我覺得應該跟服務依賴有關吧
        // delay > 0,延時導出服務
        Thread thread = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(delay);
                } catch (Throwable e) {
                }
                // 起一個守護線程進行服務發佈
                doExport();
            }
        });
        thread.setDaemon(true);
        thread.setName("DelayExportServiceThread");
        thread.start();
    } else {
        // 非延遲加載的情況,立即導出服務
        doExport();
    }
}

protected synchronized void doExport() {
    // 。。。。。。。。。。。。。。。。。。。。。。。。。。。
    doExportUrls();
}

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

private void exportLocal(URL url) {
    // 官網註釋:如果 URL 的協議頭等於 injvm,說明已經導出到本地了,無需再次導出
    if (!"injvm".equalsIgnoreCase(url.getProtocol())) {
        // 設置協議頭injvm
        URL local = URL.valueOf(url.toFullString()).setProtocol("injvm").setHost("127.0.0.1").setPort(0);
        // push service
        ServiceImplHolder.getInstance().pushServiceImpl(this.ref);
        // 創建 Invoker,並導出服務,這裏的 protocol 會在運行時調用 InjvmProtocol 的 export 方法
        Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(this.ref, this.interfaceClass, local));
        this.exporters.add(exporter);
        logger.info("Export dubbo service " + this.interfaceClass.getName() + " to local registry");
    }

}


private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    
    // 省略無關代碼
    
    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
        // 加載 ConfiguratorFactory,並生成 Configurator 實例,然後通過實例配置 url
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }

    String scope = url.getParameter(Constants.SCOPE_KEY);
    // 如果 scope = none,則什麼都不做
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
        // scope != remote,導出到本地
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
            exportLocal(url);
        }

        // scope != local,導出到遠程
        if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
            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 = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }

                    String proxy = url.getParameter(Constants.PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                    }

                    // 爲服務提供類(ref)生成 Invoker
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                    // DelegateProviderMetaDataInvoker 用於持有 Invoker 和 ServiceConfig
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    // 導出服務,並生成 Exporter
                    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);
}

服務註冊,首先要創建註冊中心,下圖是註冊中心的獲取流程

 

zookeeper客戶端通過SPI 提供

@SPI("zkclient")
public interface ZookeeperTransporter {

    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    ZookeeperClient connect(URL url);
}

zkclient=com.alibaba.dubbo.remoting.zookeeper.zkclient.ZkclientZookeeperTransporter
curator=com.alibaba.dubbo.remoting.zookeeper.curator.CuratorZookeeperTransporter

 

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