dubbo源碼分析系列(2)服務的發佈

#1 系列目錄java

#2 dubbo與spring接入spring

dubbo的官方文檔也說明了,dubbo能夠不依賴任何Spring。這一塊往後再詳細說明,目前先介紹dubbo與Spring的集成。與spring的集成是基於Spring的Schema擴展進行加載服務器

##2.1 Spring對外留出的擴展網絡

用過Spring就知道能夠在xml文件中進行以下配置:app

<context:component-scan base-package="com.demo.dubbo.server.serviceimpl"/>

<context:property-placeholder location="classpath:config.properties"/>

<tx:annotation-driven transaction-manager="transactionManager"/>

Spring是如何來解析這些配置呢?若是咱們想本身定義配置該如何作呢?負載均衡

對於上述的xml配置,分紅三個部分ide

  • 命名空間namespace,如tx、context
  • 元素element,如component-scan、property-placeholder、annotation-driven
  • 屬性attribute,如base-package、location、transaction-manager

Spring定義了兩個接口,來分別解析上述內容:源碼分析

  • NamespaceHandler:註冊了一堆BeanDefinitionParser,利用他們來進行解析
  • BeanDefinitionParser: 用於解析每一個element的內容

來看下具體的一個案例,就以Spring的context命名空間爲例,對應的NamespaceHandler實現是ContextNamespaceHandler:url

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}

}

註冊了一堆BeanDefinitionParser,若是咱們想看"component-scan"是如何實現的,就能夠去看對應的ComponentScanBeanDefinitionParser的源碼了spa

若是自定義了NamespaceHandler,如何加入到Spring中呢?

Spring默認會在加載jar包下的 META-INF/spring.handlers文件下尋找NamespaceHandler,默認的Spring文件以下:

Spring Handlers

文件內容以下:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

相應的命名空間使用相應的NamespaceHandler

##2.2 dubbo的接入實現

dubbo就是自定義類型的,因此也要給出NamespaceHandler、BeanDefinitionParser。NamespaceHandler是DubboNamespaceHandler:

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

	static {
		Version.checkDuplicate(DubboNamespaceHandler.class);
	}

    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.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));
    }

}

給出的BeanDefinitionParser所有是DubboBeanDefinitionParser,若是咱們想看看dubbo:registry是怎麼解析的,就能夠去看看DubboBeanDefinitionParser的源代碼。

而dubbo的jar包下,存在着META-INF/spring.handlers文件,內容以下:

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

具體解析過程就再也不說明了。結果就是不一樣的配置分別轉換成Spring容器中的一個bean對象。

  • application對應ApplicationConfig
  • registry對應RegistryConfig
  • monitor對應MonitorConfig
  • provider對應ProviderConfig
  • consumer對應ConsumerConfig
  • protocol對應ProtocolConfig
  • service對應ServiceConfig
  • reference對應ReferenceConfig

上面的對象不依賴Spring,也就是說你能夠手動去建立上述對象。

爲了在Spring啓動的時候,也相應的啓動provider發佈服務註冊服務的過程:又加入了一個和Spring相關聯的ServiceBean,繼承了ServiceConfig

爲了在Spring啓動的時候,也相應的啓動consumer發現服務的過程:又加入了一個和Spring相關聯的ReferenceBean,繼承了ReferenceConfig

利用Spring就作了上述過程,獲得相應的配置數據,而後啓動相應的服務。若是想剝離Spring,咱們就能夠手動來建立上述配置對象,經過ServiceConfig和ReferenceConfig的API來啓動相應的服務

#3 服務的發佈過程

##3.1 案例介紹

從上面知道,利用Spring的解析收集到不少一些配置,而後將這些配置都存至ServiceConfig中,而後調用ServiceConfig的export()方法來進行服務的發佈與註冊

先看一個簡單的服務端例子,dubbo配置以下:

<dubbo:application name="helloService-app" />

<dubbo:registry  protocol="zookeeper"  address="127.0.0.1:2181"  />

<dubbo:service interface="com.demo.dubbo.service.HelloService" ref="helloService" />

<bean id="helloService" class="com.demo.dubbo.server.serviceimpl.HelloServiceImpl"/>
  • 有一個服務接口,HelloService,以及它對應的實現類HelloServiceImpl
  • 將HelloService標記爲dubbo服務,使用HelloServiceImpl對象來提供具體的服務
  • 使用zooKeeper做爲註冊中心

##3.2 服務發佈過程

一個服務能夠有多個註冊中心、多個服務協議

多註冊中心信息:

首選根據註冊中心配置,即上述的ZooKeeper配置信息,將註冊信息聚合在一個URL對象中,registryURLs內容以下:

[registry://192.168.1.104:2181/com.alibaba.dubbo.registry.RegistryService?application=helloService-app&localhost=true&registry=zookeeper]

多協議信息:

因爲上述咱們沒有配置任何協議信息,就會使用默認的dubbo協議,開放在20880端口,也就是在該端口,對外提供上述的HelloService服務,註冊的協議信息也轉化成一個URL對象,以下:

dubbo://192.168.1.104:20880/com.demo.dubbo.service.HelloService?anyhost=true&application=helloService-app&dubbo=2.0.13&interface=com.demo.dubbo.service.HelloService&methods=hello&prompt=dubbo&revision=

依據註冊中心信息和協議信息的組合起來,依次來進行服務的發佈。整個過程僞代碼以下:

List<URL> registryURLs = loadRegistries();
for (ProtocolConfig protocolConfig : protocols) {
	//根據每個協議配置構建一個URL
	URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);		
	for (URL registryURL : registryURLs) {
        String providerURL = url.toFullString();
        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(RpcConstants.EXPORT_KEY, providerURL));
        Exporter<?> exporter = protocol.export(invoker);
    }
}

因此服務發佈過程大體分紅兩步:

  • 第一步:經過ProxyFactory將HelloServiceImpl封裝成一個Invoker
  • 第二步:使用Protocol將invoker導出成一個Exporter

這裏面就涉及到幾個大的概念。ProxyFactory、Invoker、Protocol、Exporter。下面來一一介紹

##3.3 概念介紹

分別介紹下Invoker、ProxyFactory、Protocol、Exporter的概念

###3.3.1 Invoker概念

Invoker: 一個可執行的對象,可以根據方法名稱、參數獲得相應的執行結果。接口內容簡略以下:

public interface Invoker<T> {

    Class<T> getInterface();

    URL getUrl();
    
    Result invoke(Invocation invocation) throws RpcException;

	void destroy();

}

而Invocation則包含了須要執行的方法、參數等信息,接口定義簡略以下:

public interface Invocation {

    URL getUrl();
    
	String getMethodName();

	Class<?>[] getParameterTypes();

	Object[] getArguments();

}

目前其實現類只有一個RpcInvocation。內容大體以下:

public class RpcInvocation implements Invocation, Serializable {

    private String              methodName;

    private Class<?>[]          parameterTypes;

    private Object[]            arguments;

    private transient URL       url;
}

僅僅提供了Invocation所須要的參數而已,繼續回到Invoker

這個可執行對象的執行過程分紅三種類型:

  • 類型1:本地執行類的Invoker

  • 類型2:遠程通訊執行類的Invoker

  • 類型3:多個類型2的Invoker聚合成的集羣版的Invoker

以HelloService接口方法爲例:

  • 本地執行類的Invoker: server端,含有對應的HelloServiceImpl實現,要執行該接口方法,僅僅只須要經過反射執行HelloServiceImpl對應的方法便可

  • 遠程通訊執行類的Invoker: client端,要想執行該接口方法,須要須要進行遠程通訊,發送要執行的參數信息給server端,server端利用上述本地執行的Invoker執行相應的方法,而後將返回的結果發送給client端。這整個過程算是該類Invoker的典型的執行過程

  • 集羣版的Invoker:client端,擁有某個服務的多個Invoker,此時client端須要作的就是將這個多個Invoker聚合成一個集羣版的Invoker,client端使用的時候,僅僅經過集羣版的Invoker來進行操做。集羣版的Invoker會從衆多的遠程通訊類型的Invoker中選擇一個來執行(從中加入負載均衡策略),還能夠採用一些失敗轉移策略等

因此來看下Invoker的實現狀況:

Invoker的實現狀況

###3.3.2 ProxyFactory概念

對於server端,主要負責將服務如HelloServiceImpl統一進行包裝成一個Invoker,這些Invoker經過反射來執行具體的HelloServiceImpl對象的方法。

接口定義以下:

@Extension("javassist")
public interface ProxyFactory {

  	//針對client端,建立出代理對象
    @Adaptive({Constants.PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker) throws RpcException;

	//針對server端,將服務對象如HelloServiceImpl包裝成一個Invoker對象
    @Adaptive({Constants.PROXY_KEY})
    <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;

}

ProxyFactory的接口實現有JdkProxyFactory、JavassistProxyFactory,默認是JavassistProxyFactory, JdkProxyFactory內容以下:

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    return new AbstractProxyInvoker<T>(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName, 
                                  Class<?>[] parameterTypes, 
                                  Object[] arguments) throws Throwable {
            Method method = proxy.getClass().getMethod(methodName, parameterTypes);
            return method.invoke(proxy, arguments);
        }
    };
}

能夠看到是建立了一個AbstractProxyInvoker(這類就是本地執行的Invoker),它對Invoker的Result invoke(Invocation invocation)實現以下:

public Result invoke(Invocation invocation) throws RpcException {
    try {
        return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
    } catch (InvocationTargetException e) {
        return new RpcResult(e.getTargetException());
    } catch (Throwable e) {
        throw new RpcException("Failed to invoke remote proxy " + invocation + " to " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

綜上所述,服務發佈的第一個過程就是:

使用ProxyFactory將HelloServiceImpl封裝成一個本地執行的Invoker。

###3.3.3 Protocol概念

從上面得知服務發佈的第一個過程就是:

使用ProxyFactory將HelloServiceImpl封裝成一個本地執行的Invoker。

執行這個服務,即執行這個本地Invoker,即調用這個本地Invoker的invoke(Invocation invocation)方法,方法的執行過程就是經過反射執行了HelloServiceImpl的內容。如今的問題是:這個方法的參數Invocation invocation的來源問題。

針對server端來講,Protocol要解決的問題就是:根據指定協議對外公佈這個HelloService服務,當客戶端根據協議調用這個服務時,將客戶端傳遞過來的Invocation參數交給上述的Invoker來執行。因此Protocol加入了遠程通訊協議的這一塊,根據客戶端的請求來獲取參數Invocation invocation。

先來看下Protocol的接口定義:

@Extension("dubbo")
public interface Protocol {
    
    int getDefaultPort();

	//針對server端來講,將本地執行類的Invoker經過協議暴漏給外部。這樣外部就能夠經過協議發送執行參數Invocation,而後交給本地Invoker來執行
    @Adaptive
	<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

	//這個是針對客戶端的,客戶端從註冊中心獲取服務器端發佈的服務信息
	//經過服務信息得知服務器端使用的協議,而後客戶端仍然使用該協議構造一個Invoker。這個Invoker是遠程通訊類的Invoker。
	//執行時,須要將執行信息經過指定協議發送給服務器端,服務器端接收到參數Invocation,而後交給服務器端的本地Invoker來執行
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

	void destroy();

}

咱們再來詳細看看服務發佈的第二步:

Exporter<?> exporter = protocol.export(invoker);

protocol的來歷是:

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

咱們從第一篇文章dubbo源碼分析系列(1)擴展機制的實現,能夠知道上述獲取Protocol protocol的原理,這裏就再也不多說了,直接貼出最終的Protocol的實現代碼:

public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException{
    if (arg0 == null)  { 
        throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); 
    }
    if (arg0.getUrl() == null) { 
        throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); 
    }
    com.alibaba.dubbo.common.URL url = arg0.getUrl();
    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)com.alibaba.dubbo.common.ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
    return extension.export(arg0);
}

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)com.alibaba.dubbo.common.ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
    return extension.refer(arg0, arg1);
}

export(Invoker invoker)的過程即根據Invoker中url的配置信息來最終選擇的Protocol實現,默認實現是"dubbo"的擴展實現即DubboProtocol,而後再對DubboProtocol進行依賴注入,進行wrap包裝。先來看看Protocol的實現狀況:

Protocol的實現狀況

能夠看到在返回DubboProtocol以前,通過了ProtocolFilterWrapper、ProtocolListenerWrapper、RegistryProtocol的包裝。

所謂的包裝就是以下相似的內容:

package com.alibaba.xxx;

import com.alibaba.dubbo.rpc.Protocol;
 
public class XxxProtocolWrapper implemenets Protocol {
    Protocol impl;
 
    public XxxProtocol(Protocol protocol) { impl = protocol; }
 
    // 接口方法作一個操做後,再調用extension的方法
    public Exporter<T> export(final Invoker<T> invoker) {
        //... 一些操做
        impl .export(invoker);
        // ... 一些操做
    }
 
    // ...
}

使用裝飾器模式,相似AOP的功能。

下面主要講解RegistryProtocol和DubboProtocol,先暫時忽略ProtocolFilterWrapper、ProtocolListenerWrapper

因此上述服務發佈的過程

Exporter<?> exporter = protocol.export(invoker);

會先通過RegistryProtocol,它幹了哪些事呢?

  • 利用內部的Protocol即DubboProtocol,將服務進行導出,以下

    exporter = protocol.export(new InvokerWrapper<T>(invoker, url));
  • 根據註冊中心的registryUrl獲取註冊服務Registry,而後將serviceUrl註冊到註冊中心上,供客戶端訂閱

    Registry registry = registryFactory.getRegistry(registryUrl);
    registry.register(serviceUrl)

來詳細看看上述DubboProtocol的服務導出功能:

  • 首先根據Invoker的url獲取ExchangeServer通訊對象(負責與客戶端的通訊模塊),以url中的host和port做爲key存至Map<String, ExchangeServer> serverMap中。便可以採用所有服務的通訊交給這一個ExchangeServer通訊對象,也能夠某些服務單獨使用新的ExchangeServer通訊對象。

    String key = url.getAddress();
    //client 也能夠暴露一個只有server能夠調用的服務。
    boolean isServer = url.getParameter(RpcConstants.IS_SERVER_KEY,true);
    if (isServer && ! serverMap.containsKey(key)) {
        serverMap.put(key, getServer(url));
    }
  • 建立一個DubboExporter,封裝invoker。而後根據url的port、path(接口的名稱)、版本號、分組號做爲key,將DubboExporter存至Map<String, Exporter<?>> exporterMap中

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

如今咱們要搞清楚咱們的目的:經過通訊對象獲取客戶端傳來的Invocation invocation參數,而後找到對應的DubboExporter(即可以獲取到本地Invoker)就能夠執行服務了。

上述每個ExchangeServer通訊對象都綁定了一個ExchangeHandler requestHandler對象,內容簡略以下:

private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
    
    public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
        if (message instanceof Invocation) {
            Invocation inv = (Invocation) message;
            Invoker<?> invoker = getInvoker(channel, inv);
            RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
            return invoker.invoke(inv);
        }
        throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
    }
};

能夠看到在獲取到Invocation參數後,調用getInvoker(channel, inv)來獲取本地Invoker。獲取過程就是根據channel獲取port,根據Invocation inv信息獲取要調用的服務接口、版本號、分組號等,以此組裝成key,從上述Map<String, Exporter<?>> exporterMap中獲取Exporter,而後就能夠找到對應的Invoker了,就能夠順利的調用服務了。

而對於通訊這一塊,接下來會專門來詳細的說明。

###3.3.4 Exporter概念

負責維護invoker的生命週期。接口定義以下:

public interface Exporter<T> {

    Invoker<T> getInvoker();

    void unexport();

}

包含了一個Invoker對象。一旦想撤銷該服務,就會調用Invoker的destroy()方法,同時清理上述exporterMap中的數據。對於RegistryProtocol來講就須要向註冊中心撤銷該服務。

#4 結束語

本文簡略地介紹了接入Spring過程的原理,以及服務發佈過程當中的幾個概念。接下來的打算是:

  • 客戶端訂閱服務與使用服務涉及的概念
  • 註冊中心模塊
  • 客戶端與服務器端網絡通訊模塊
相關文章
相關標籤/搜索