Dubbo服務提供者發佈過程

服務提供者暴露服務的主過程

上圖是服務提供者暴露服務的主過程html

首先ServiceConfig類拿到對外提供服務的實際類ref(如:HelloServiceImpl),而後經過ProxyFactory類的getInvoker方法使用ref生成一個AbstractProxyInvoker實例,到這一步就完成具體服務到Invoker的轉化。接下來就是Invoker轉換到Exporter的過程。java

Dubbo處理服務暴露的關鍵就在Invoker轉換到Exporter的過程(如上圖中的紅色部分),Dubbo協議的Invoker轉爲Exporter發生在DubboProtocol類的export方法,它主要是打開socket偵聽服務,並接收客戶端發來的各類請求,通信細節由Dubbo本身實現.apache

服務發佈過程大體分紅3步

一、獲取註冊中心信息,構建協議信息,而後將其組合。
二、經過ProxyFactory將HelloServiceImpl封裝成一個Invoker執行 。
三、使用Protocol將invoker導出成一個Exporter(包括去註冊中心註冊服務等)。緩存

這裏面就涉及到幾個大的概念,ProxyFactory、Invoker、Protocol、Exporter服務器

Export 服務暴露的步驟

一、首先會檢查各類配置信息<dubbo:service/>、<dubbo:registry/>、<dubbo:protocol/> 等標籤的配置,填充各類屬性,總之就是保證我在開始暴露服務以前,全部的東西都準備好了,而且是正確的。
二、加載全部的註冊中心,由於咱們暴露服務須要註冊到註冊中心中去。
三、根據配置的全部協議和註冊中心url分別進行導出。
四、進行導出的時候,又是一波屬性的獲取設置檢查等操做。
五、若是配置的不是remote,則作本地導出。
六、若是配置的不是local,則暴露爲遠程服務。
七、不論是本地仍是遠程服務暴露,首先都會獲取Invoker。
八、獲取完Invoker以後,轉換成對外的Exporter,緩存起來。
九、執行DubboProtocol類的export方法,打開socket偵聽服務,並接收客戶端發來的各類請求。app

概念介紹

先看一個簡單的服務端例子,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" />
<dubbo:protocol name="dubbo" port="20880" />
<bean id="helloService" class="com.demo.dubbo.server.serviceimpl.HelloServiceImpl"/>
  • 有一個服務接口HelloService,以及它對應的實現類HelloServiceImpl。
  • 將HelloService標記爲dubbo服務,使用HelloServiceImpl對象來提供具體的服務。
  • 使用zooKeeper做爲註冊中心。

Invoker

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

public interface Invoker<T> {

    Class<T> getInterface();

    URL getUrl();

    Result invoke(Invocation invocation) throws RpcException;

    void destroy();

}

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

public interface Invocation {

    URL getUrl();

    String getMethodName();

    Class<?>[] getParameterTypes();

    Object[] getArguments();

}

目前其實現類只有一個RpcInvocationurl

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

  1. 本地執行的Invoker
  2. 遠程通訊執行的Invoker
  3. 多個類型2的Invoker聚合成的集羣版Invoker

以HelloService接口爲例:

  1. 本地執行的Invokerserver端,含有對應的HelloServiceImpl實現,要執行該接口方法,僅僅只須要經過反射執行HelloServiceImpl對應的方法便可。
  2. 遠程通訊執行的Invokerclient端,要想執行該接口方法,須要須要進行遠程通訊,發送要執行的參數信息給server端;server端,利用上述本地執行的Invoker執行相應的方法,而後將返回的結果發送給client端。這整個過程算是該類Invoker的典型的執行過程。
  3. 集羣版的Invokerclient端,擁有某個服務的多個Invoker,此時client端須要作的就是將多個Invoker聚合成一個集羣版的Invoker,client端使用的時候,僅僅經過集羣版的Invoker來進行操做。集羣版的Invoker會從衆多的遠程通訊類型的Invoker中選擇一個來執行(從中加入負載均衡、服務降級等策略),相似服務治理,dubbo已經實現了、

看下Invoker的實現狀況:
Invoker的實現狀況

ProxyFactory

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

接口定義以下:

@SPI("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, JavassistProxyFactory內容以下:

public class JavassistProxyFactory extends AbstractProxyFactory {

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper類不能正確處理帶$的類名
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, 
                                      Class<?>[] parameterTypes, 
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

}

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

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 method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

綜上所述,服務發佈的第二個過程就是:使用ProxyFactory將HelloServiceImpl封裝成一個本地執行的Invoker。

Protocol

從上面得知服務發佈的第1、二個過程:

一、獲取註冊中心信息dubbo:registry和協議信息dubbo:protocol。
二、使用ProxyFactory將HelloServiceImpl封裝成一個本地執行的Invoker。

執行這個服務->執行這個本地的Invoker->調用AbstractProxyInvoker.invoke(Invocation invocation)方法,方法的執行過程就是經過反射執行HelloServiceImpl。

如今的問題是:客戶端如何調用服務端的方法(服務註冊到註冊中心->客戶端向註冊中心訂閱服務->客戶端調用服務端的方法)和上述Invocation參數的來源問題

對於Server端來講,上述服務發佈的第3步中Protocol要解決的問題是:

  • 根據指定協議向註冊中心註冊HelloService服務。
  • 當客戶端根據協議調用這個服務時,將客戶端傳遞過來的Invocation參數交給上述的Invoker來執行。
  • 因此Protocol會加入遠程通訊這塊,根據客戶端的請求來獲取參數Invocation。

先來看下Protocol接口的定義:

@SPI("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();

}

咱們再來詳細看看服務發佈的第3步(ServiceConfig):

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

protocol的來歷是:

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
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的實現,默認實現是DubboProtocol,而後再對DubboProtocol進行依賴注入,進行wrap包裝(getExtension()方法)。

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

RegistryProtocol.export() 主要功能是將服務註冊到註冊中心

DubboProtocol.export() 服務導出功能:

  • 建立一個DubboExporter,封裝Invoker。
  • 根據Invoker的url獲取ExchangeServer通訊對象(負責與客戶端的通訊模塊)。

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

在DubboProtocol中,每一個ExchangeServer通訊對象都綁定了一個ExchangeHandler對象,內容以下:

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()來獲取本地的Invoker(先從exporterMap中獲取Exporter),就能夠調用服務了。

而對於通訊這塊,接下來會專門來詳細的說明,從reply參數可知,重點在瞭解ExchangeChannel

Exporter

負責維護invoker的生命週期,包含一個Invoker對象,接口定義以下:

public interface Exporter<T> {

    Invoker<T> getInvoker();

    void unexport();

}

結束語

以上就是本文簡略地介紹了及服務發佈過程當中的幾個 ProxyFactory、Invoker、Protocol、Exporter 概念

參考:http://dubbo.apache.org/books/dubbo-dev-book/implementation.html
參考:https://blog.csdn.net/qq418517226/article/details/51818769

Contact

關注公衆號-搜雲庫

相關文章
相關標籤/搜索