二、dubbo原理详解

Dubbo的实现原理分析

Dubbo的技术框架如下图所示:

(摘自DUBBO官网——技术手册)

DUBBO官方文档中,对于上图中各层的功能描述:
1. config:配置层,对外配置接口,以ServiceConfig,ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。
2. proxy:服务代理层,服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。
3. registry:注册中心层,封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory,Registry,RegistryService。
4. cluster:路由层,封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster,Directory,Router,LoadBalance。
5. monitor:监控层,RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory,Monitor,MonitorService 。
6. protocol:远程调用层,封将RPC调用,以Invocation,Result为中心,扩展接口为Protocol, Invoker, Exporter。
7. exchange:信息交换层,封装请求响应模式,同步转异步,以Request, Response为中心,扩展接口为Exchanger,ExchangeChannel,ExchangeClient,ExchangeServer。
8. transport:网络传输层,抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel,Transporter,Client,Server,Codec。

9. serialize:数据序列化层,可复用的一些工具,扩展接口为Serialization,ObjectInput,ObjectOutput,ThreadPool。


2. 服务发布过程

2.1 发布过程

Dubbo利用Spring的解析(或自己实现)收集到很多一些配置,然后将这些配置都存至ServiceConfig中,然后调用ServiceConfig的export()方法来进行服务的发布与注册。

服务的发布过程如下图所示。首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl),然后通过ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化。接下来就是Invoker转换到Exporter的过程。
Dubbo处理服务暴露的关键就在Invoker转换到Exporter的过程(如下图中的红色部分),Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由Dubbo自己实现。

这里写图片描述

先看一个简单的服务端例子,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作为注册中心。则该注册过程的伪代码如下所示:

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

2.2 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,仅仅提供了Invocation所需要的参数而已。内容大致如下:
    public class RpcInvocation implements Invocation, Serializable {  
        private String              methodName;  
        private Class<?>[]          parameterTypes;  
        private Object[]            arguments;  
        private transient URL       url;  
    }  

Invoker可执行对象的执行过程分成三种类型:

类型1:本地执行类的Invoker
类型2:远程通信执行类的Invoker
类型3:多个类型2的Invoker聚合成的集群版的Invoker
以HelloService接口方法为例:
1. 本地执行类的Invoker: server端,含有对应的HelloServiceImpl实现,要执行该接口方法,仅仅只需要通过反射执行HelloServiceImpl对应的方法即可
2. 远程通信执行类的Invoker: client端,要想执行该接口方法,需要需要进行远程通信,发送要执行的参数信息给server端,server端利用上述本地执行的Invoker执行相应的方法,然后将返回的结果发送给client端。这整个过程算是该类Invoker的典型的执行过程
3. 集群版的Invoker:client端,拥有某个服务的多个Invoker,此时client端需要做的就是将这个多个Invoker聚合成一个集群版的Invoker,client端使用的时候,仅仅通过集群版的Invoker来进行操作。集群版的Invoker会从众多的远程通信类型的Invoker中选择一个来执行(从中加入负载均衡策略),还可以采用一些失败转移策略等。

2.3 ProxyFactory

proxy代理层按照DUBBO官方文档的解释,是用来生成RPC调用的Stub和Skeleton,这样做的目的是让您在DUBBO服务端定义的具体业务实现不需要关心“它将被怎样调用”,也是您定义的服务接口“与RPC框架脱耦”。

对于server端,ProxyFactory主要负责将服务如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);  
        }  
    }  

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

我们再来详细看看服务发布的第二步:

  1. Exporter<?> exporter = protocol.export(invoker);  
protocol的来历是:
  1. Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();  

最终的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);  
    }  

使用装饰器模式,类似AOP的功能。
下面主要讲解RegistryProtocolDubboProtocol,先暂时忽略ProtocolFilterWrapper、ProtocolListenerWrapper

所以上述服务发布的过程

export(Invoker invoker)的过程即根据Invoker中url的配置信息来最终选择Protocol的实现,默认实现是"dubbo"的扩展实现即DubboProtocol,然后再对DubboProtocol进行依赖注入,进行wrap包装。
这里写图片描述

可以看到在返回DubboProtocol之前,经过了ProtocolFilterWrapper、ProtocolListenerWrapper、RegistryProtocol的包装。
所谓的包装就是如下类似的内容:

[java] view plain copy

    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);  
            // ... 一些操作  
        }  
       
        // ...  
    }  

  1. Exporter<?> exporter = protocol.export(invoker);  
会先经过RegistryProtocol,它干了哪些事呢?

1. 利用内部的Protocol即DubboProtocol,将服务进行导出,如下

  1. exporter = protocol.export(new InvokerWrapper<T>(invoker, url));  
2. 根据注册中心的registryUrl获取注册服务Registry,然后将 serviceUrl注册到注册中心上,供客户端订阅
  1. Registry registry = registryFactory.getRegistry(registryUrl);  
  2. registry.register(serviceUrl)  
来详细看看上述DubboProtocol的服务导出功能:

1. 首先根据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));  
    }  

最后来张dubbo的解释图片:

2. 创建一个DubboExporter,封装invoker。然后根据url的port、path(接口的名称)、版本号、分组号作为key,将DubboExporter存至Map<String, Exporter<?>> exporterMap中

  1. key = serviceKey(url);  
  2. DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);  
  3. 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了,就可以顺利的调用服务了。

2.5 Exporter

Exporter负责维护invoker的生命周期。接口定义如下:

[java] view plain copy
  1. public interface Exporter<T> {  
  2.     Invoker<T> getInvoker();  
  3.     void unexport();  
  4. }  
包含了一个Invoker对象。一旦想撤销该服务,就会调用Invoker的 destroy()方法,同时清理上述exporterMap中的数据。对于RegistryProtocol来说就需要向注册中心撤销该服务。

3. 服务调用过程

下图是服务消费的主过程:

这里写图片描述
dubbo-extension

首先ReferenceConfig类的init方法调用Protocol的refer方法生成Invoker实例(如上图中的红色部分),这是服务消费的关键。接下来把Invoker通过ProxyFactory代理工厂转换为客户端需要的接口(如:HelloWorld)。

对于一个简单的客户端引用服务的例子,dubbo配置如下:

[java] view plain copy
  1. <dubbo:application name="consumer-of-helloService" />  
  2. <dubbo:registry  protocol="zookeeper"  address="127.0.0.1:2181" />  
  3. <dubbo:reference id="helloService" interface="com.demo.dubbo.service.HelloService" />  
使用zooKeeper作为注册中心,引用远程的HelloService接口服务

HelloService接口内容如下:

[java] view plain copy
  1. public interface HelloService {  
  2.     public String hello(String msg);  
  3. }  
利用Spring的xml配置创建出一系列的配置对象,存至Spring容器中,配置相关的对象不依赖Spring,也就是说你可以手动去创建上述对象。
为了在Spring启动的时候,也相应的启动provider发布服务注册服务的过程:又加入了一个和Spring相关联的 ServiceBean,继承了erviceConfig,为了在Spring启动的时候,也相应的启动consumer发现服务的过程:又加入了一个和Spring相关联的 ReferenceBean,继承了ReferenceConfig,利用Spring就做了上述过程,得到相应的配置数据,然后启动相应的服务。如果想剥离Spring,我们就可以手动来创建上述配置对象,通过 ServiceConfigReferenceConfig的API来启动相应的服务。

服务引用过程

将ReferenceConfig.init()中的内容拆成具体的步骤,如下
第一步:收集配置参数
从Spring或者手动创建的配置对象中获得收集到的配置参数,举例如下:
[java] view plain copy
  1. methods=hello,  
  2. timestamp=1443695417847,  
  3. dubbo=2.5.3  
  4. application=consumer-of-helloService  
  5. side=consumer  
  6. pid=7748  
  7. interface=com.demo.dubbo.service.HelloService  
第二步:从注册中心获取服务地址,返回Invoker对象
如果是单个注册中心,代码如下:
[java] view plain copy
  1. Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();  
  2. invoker = refprotocol.refer(interfaceClass, url);  
上述url的内容如下:
[java] view plain copy
  1. registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?  
  2. application=consumer-of-helloService&  
  3. dubbo=2.5.6&  
  4. pid=8292&  
  5. registry=zookeeper&  
  6. timestamp=1443707173909&  
  7.   
  8.   
  9. refer=  
  10.     application=consumer-of-helloService&  
  11.     dubbo=2.5.6&  
  12.     interface=com.demo.dubbo.service.HelloService&  
  13.     methods=hello&  
  14.     pid=8292&  
  15.     side=consumer&  
  16.     timestamp=1443707173884&  
第三步:使用ProxyFactory创建出Invoker的代理对象,代码如下:
[java] view plain copy
  1. ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();  
  2. proxyFactory.getProxy(invoker);  

3.1 Invoker

Invoker一个可执行对象。这个可执行对象的执行过程分成三种类型:

类型1:本地执行类的Invoker
类型2:远程通信执行类的Invoker
类型3:多个类型2的Invoker聚合成的集群版的Invoker

对于客户端来说,Invoker则应该是远程通信执行类的Invoker多个远程通信类型的Invoker聚合成的集群版的Invoker这两种类型。先来说说非集群版的Invoker,即远程通信类型的Invoker。来看下DubboInvoker的具体实现

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

大概内容就是:

将通过远程通信将Invocation信息传递给服务器端,服务器端接收到该Invocation信息后,找到对应的本地Invoker,然后通过反射执行相应的方法,将方法的返回值再通过远程通信将结果传递给客户端。
这里分成3种情况:
1. 执行的方法不需要返回值:直接使用ExchangeClientsend方法
2. 执行的方法的结果需要异步返回:使用ExchangeClientrequest方法,返回一个ResponseFuture,通过ThreadLocal方式与当前线程绑定,未等服务器端响应结果就直接返回
3. 执行的方法的结果需要同步返回:使用ExchangeClientrequest方法,返回一个ResponseFuture,一直阻塞到服务器端返回响应结果

3.2 Protocol

从上面得知服务引用的第二个过程就是
  1. invoker = refprotocol.refer(interfaceClass, url);  
使用协议Protocol根据上述的url和服务接口来引用服务,创建出一个Invoker对象,针对server端来说,会如下使用Protocol:
  1. Exporter<?> exporter = protocol.export(invoker);  
Protocol要解决的问题就是: 根据url中指定的协议(没有指定的话使用默认的dubbo协议) 对外公布这个HelloService服务,当客户端根据协议调用这个服务时,将客户端传递过来的Invocation参数交给服务器端的Invoker来执行。所以Protocol加入了远程通信协议的这一块,根据客户端的请求来获取参数Invocation invocation。
而针对客户端,则需要根据服务器开放的协议(服务器端在注册中心注册的url地址中含有该信息)来创建相应的协议的Invoker对象,如:DubboInvoker、InjvmInvoker和ThriftInvoker等。

如服务器端在注册中心中注册的url地址为:

    dubbo://192.168.1.104:20880/com.demo.dubbo.service.HelloService?  
    anyhost=true&  
    application=helloService-app&dubbo=2.5.3&  
    interface=com.demo.dubbo.service.HelloService&  
    methods=hello&  
    pid=3904&  
    side=provider&  
    timestamp=1444003718316  

会看到上述服务是以dubbo协议注册的,所以这里产生的Invoker就是DubboInvoker。我们来具体的看下这个过程

先来看下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();  
      
    }  

详细看看服务引用的第二步:

  1. invoker = refprotocol.refer(interfaceClass, url);  
protocol的来历是:
  1. Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();  

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

refer(interfaceClass, url)的过程即根据url的配置信息来最终选择的Protocol实现,默认实现是"dubbo"的扩展实现即DubboProtocol,然后再对DubboProtocol进行依赖注入,进行wrap包装。先来看看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的功能。所以上述服务引用的过程:
[java] view plain copy
  1. invoker = refprotocol.refer(interfaceClass, urls.get(0));  
中的refprotocol会先经过RegistryProtocol(先暂时忽略ProtocolFilterWrapper、ProtocolListenerWrapper),它干了哪些事呢?

1. 根据注册中心的registryUrl获取注册服务Registry,将自身的consumer信息注册到注册中心上

[java] view plain copy
  1. //先根据客户端的注册中心配置找到对应注册服务  
  2. Registry registry = registryFactory.getRegistry(url);  
  3.   
  4. //使用注册服务将客户端的信息注册到注册中心上  
  5. registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,  
  6.             Constants.CHECK_KEY, String.valueOf(false)));  
上述subscribeUrl地址如下:
[java] view plain copy
  1. consumer://192.168.1.104/com.demo.dubbo.service.HelloService?  
  2.     application=consumer-of-helloService&  
  3.     dubbo=2.5.3&  
  4.     interface=com.demo.dubbo.service.HelloService&  
  5.     methods=hello&  
  6.     pid=6444&  
  7.     side=consumer&  
  8.     timestamp=1444606047076  
该url表述了自己是 consumer,同时自己的ip地址是192.168.1.104,引用的服务是com.demo.dubbo.service.HelloService,以及注册时间等等

2. 创建一个RegistryDirectory,从注册中心中订阅自己引用的服务,将订阅到的url在RegistryDirectory内部转换成Invoker

[java] view plain copy
  1. RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);  
  2. directory.setRegistry(registry);  
  3. directory.setProtocol(protocol);  
  4. directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,   
  5.         Constants.PROVIDERS_CATEGORY   
  6.         + "," + Constants.CONFIGURATORS_CATEGORY   
  7.         + "," + Constants.ROUTERS_CATEGORY));  
上述RegistryDirectory是 Directory的实现,Directory代表多个Invoker,可以把它看成List类型的Invoker,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更。
RegistryDirectory内部含有两者重要属性:
  • 1. 注册中心服务Registry registry
  • 2. Protocol protocol。
它会利用注册中心服务 Registry registry来获取 最新的服务器端注册的url地址,然后再利用协议Protocol protocol将这些url地址转换成一个具有远程通信功能的Invoker对象,如 DubboInvoker
3. 然后使用 Cluster cluster对象将上述多个Invoker对象(此时还没有真正创建出来,异步订阅,订阅成功之后,回调时才会创建出Invoker)聚合成一个 集群版的Invoker对象

[java] view plain copy
  1. @SPI(FailoverCluster.NAME)  
  2. public interface Cluster {  
  3.   
  4.     /** 
  5.      * Merge the directory invokers to a virtual invoker. 
  6.      *  
  7.      * @param <T> 
  8.      * @param directory 
  9.      * @return cluster invoker 
  10.      * @throws RpcException 
  11.      */  
  12.     @Adaptive  
  13.     <T> Invoker<T> join(Directory<T> directory) throws RpcException;  
  14.   
  15. }  
Cluster只有一个功能就是把上述Directory(相当于一个List类型的Invoker)聚合成一个 Invoker,同时也可以对List进行 过滤处理(这些过滤操作也是配置在注册中心的)等实现路由的功能,主要是 对用户进行透明。看看接口实现情况:

Cluster接口实现情况

默认采用的是FailoverCluster,看下FailoverCluster:

[java] view plain copy
  1. /** 
  2.  * 失败转移,当出现失败,重试其它服务器,通常用于读操作,但重试会带来更长延迟。  
  3.  *  
  4.  * <a href="http://en.wikipedia.org/wiki/Failover">Failover</a> 
  5.  *  
  6.  * @author william.liangf 
  7.  */  
  8. public class FailoverCluster implements Cluster {  
  9.   
  10.     public final static String NAME = "failover";  
  11.   
  12.     public <T> Invoker<T> join(Directory<T> directory) throws RpcException {  
  13.         return new FailoverClusterInvoker<T>(directory);  
  14.     }  
  15.   
  16. }  
仅仅是创建了一个 FailoverClusterInvoker,具体的逻辑留在调用的时候即调用该Invoker的invoke(final Invocation invocation)方法时来进行处理。其中又会涉及到另一个接口 LoadBalance(从众多的Invoker中挑选出一个Invoker来执行此次调用任务),接口如下:
[java] view plain copy
  1. @SPI(RandomLoadBalance.NAME)  
  2. public interface LoadBalance {  
  3.   
  4.     /** 
  5.      * select one invoker in list. 
  6.      *  
  7.      * @param invokers invokers. 
  8.      * @param url refer url 
  9.      * @param invocation invocation. 
  10.      * @return selected invoker. 
  11.      */  
  12.     @Adaptive("loadbalance")  
  13.     <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;  
  14.   
  15. }  
实现情况如下:

LoadBalance接口实现情况

3.3 ProxyFactory

对于server端,ProxyFactory主要负责将服务如HelloServiceImpl统一进行包装成一个Invoker,这些Invoker通过反射来执行具体的HelloServiceImpl对象的方法。而对于client端,则是将上述创建的集群版Invoker创建出代理对象。接口定义如下:

[java] view plain copy
  1. @Extension("javassist")  
  2. public interface ProxyFactory {  
  3.   
  4.     //针对client端,对Invoker对象创建出代理对象  
  5.     @Adaptive({Constants.PROXY_KEY})  
  6.     <T> T getProxy(Invoker<T> invoker) throws RpcException;  
  7.   
  8.     //针对server端,将服务对象如HelloServiceImpl包装成一个Invoker对象  
  9.     @Adaptive({Constants.PROXY_KEY})  
  10.     <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;  
  11.   
  12. }  
ProxyFactory的接口实现有JdkProxyFactory、JavassistProxyFactory,默认是JavassistProxyFactory, JdkProxyFactory内容如下:
[java] view plain copy
  1. public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {  
  2.     return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker));  
  3. }  
可以看到是利用jdk自带的Proxy来动态代理目标对象Invoker。所以我们调用创建出来的代理对象如HelloService helloService的方法时,会执行 InvokerInvocationHandler中的逻辑:
[java] view plain copy
  1. public class InvokerInvocationHandler implements InvocationHandler {  
  2.   
  3.     private final Invoker<?> invoker;  
  4.       
  5.     public InvokerInvocationHandler(Invoker<?> handler){  
  6.         this.invoker = handler;  
  7.     }  
  8.   
  9.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  10.         String methodName = method.getName();  
  11.         Class<?>[] parameterTypes = method.getParameterTypes();  
  12.         if (method.getDeclaringClass() == Object.class) {  
  13.             return method.invoke(invoker, args);  
  14.         }  
  15.         if ("toString".equals(methodName) && parameterTypes.length == 0) {  
  16.             return invoker.toString();  
  17.         }  
  18.         if ("hashCode".equals(methodName) && parameterTypes.length == 0) {  
  19.             return invoker.hashCode();  
  20.         }  
  21.         if ("equals".equals(methodName) && parameterTypes.length == 1) {  
  22.             return invoker.equals(args[0]);  
  23.         }  
  24.         return invoker.invoke(new RpcInvocation(method, args)).recreate();  
  25.     }  
  26.   
  27. }  
可以看到还是交给目标对象Invoker来执行。至此Proxy分析完毕。

4. Dubbo中的通信模型

Dubbo支持Netty和Mina作为底层通信模型,其结构如下图所示:

这里写图片描述

Dubbo的Transporter层完成通信功能,底层的Netty和Mina委托给统一的ChannelHandler来完成具体的功能


4.1 服务端集成Netty


如NettyServer的启动流程: 按照netty自己的API启动方式,然后依据外界传递进来的com.alibaba.dubbo.remoting.ChannelHandler接口实现,创建出NettyHandler,最终对用户的连接请求的处理全部交给NettyHandler来处理,NettyHandler又交给了外界传递进来的com.alibaba.dubbo.remoting.ChannelHandler接口实现。至此就将所有底层不同的通信实现全部转化到了外界传递进来的com.alibaba.dubbo.remoting.ChannelHandler接口的实现上了。

而上述Server接口的另一个分支实现HeaderExchangeServer则充当一个装饰器的角色,为所有的Server实现增添了如下功能:
向该Server所有的Channel依次进行心跳检测:

  • 1. 如果当前时间减去最后的读取时间大于heartbeat时间或者当前时间减去最后的写时间大于heartbeat时间,则向该Channel发送一次心跳检测
  • 2. 如果当前时间减去最后的读取时间大于heartbeatTimeout,则服务器端要关闭该Channel,如果是客户端的话则进行重新连接(客户端也会使用这个心跳检测任务)

4.2 客户端集成Netty

服务器端了解了之后,客户端就也非常清楚了,整体类图如下:

Client接口实现情况

如NettyClient在使用netty的API开启客户端之后,仍然使用NettyHandler来处理。还是最终转化成com.alibaba.dubbo.remoting.ChannelHandler接口实现上了。
HeaderExchangeClient和上面的HeaderExchangeServer非常类似,就不再提了。

我们可以看到这样集成完成之后,就完全屏蔽了底层通信细节,将逻辑全部交给了com.alibaba.dubbo.remoting.ChannelHandler接口的实现上了。从上面我们也可以看到,该接口实现也会经过层层装饰类的包装,才会最终交给底层通信。
HeartbeatHandler装饰类:

[java] view plain copy
  1. public void sent(Channel channel, Object message) throws RemotingException {  
  2.     setWriteTimestamp(channel);  
  3.     handler.sent(channel, message);  
  4. }  
  5.   
  6. public void received(Channel channel, Object message) throws RemotingException {  
  7.     setReadTimestamp(channel);  
  8.     if (isHeartbeatRequest(message)) {  
  9.         Request req = (Request) message;  
  10.         if (req.isTwoWay()) {  
  11.             Response res = new Response(req.getId(), req.getVersion());  
  12.             res.setEvent(Response.HEARTBEAT_EVENT);  
  13.             channel.send(res);  
  14.             if (logger.isInfoEnabled()) {  
  15.                 int heartbeat = channel.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);  
  16.                 if(logger.isDebugEnabled()) {  
  17.                     logger.debug("Received heartbeat from remote channel " + channel.getRemoteAddress()  
  18.                                     + ", cause: The channel has no data-transmission exceeds a heartbeat period"  
  19.                                     + (heartbeat > 0 ? ": " + heartbeat + "ms" : ""));  
  20.                 }  
  21.             }  
  22.         }  
  23.         return;  
  24.     }  
  25.     if (isHeartbeatResponse(message)) {  
  26.         if (logger.isDebugEnabled()) {  
  27.             logger.debug(  
  28.                 new StringBuilder(32)  
  29.                     .append("Receive heartbeat response in thread ")  
  30.                     .append(Thread.currentThread().getName())  
  31.                     .toString());  
  32.         }  
  33.         return;  
  34.     }  
  35.     handler.received(channel, message);  
  36. }  
就会拦截那些上述提到的心跳检测请求。更新该Channel的最后读写时间。

4.3 同步调用和异步调用的实现

使用netty mina等异步事件驱动的通信框架,将Channel中信息都分发到Handler中去处理了,Handler中的send方法只负责不断的发送消息,receive方法只负责不断接收消息,这时候就产生一个问题:
客户端如何对应同一个Channel的接收的消息和发送的消息之间的匹配呢?
这也很简单,就需要在发送消息的时候,必须要产生一个请求id,将调用的信息连同id一起发给服务器端,服务器端处理完毕后,再将响应信息和上述请求id一起发给客户端,这样的话客户端在接收到响应之后就可以根据id来判断是针对哪次请求的响应结果了。来看下DubboInvoker中的具体实现:

[java] view plain copy
  1. boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);  
  2. boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);  
  3. int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY,Constants.DEFAULT_TIMEOUT);  
  4. if (isOneway) {  
  5.     boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);  
  6.     currentClient.send(inv, isSent);  
  7.     RpcContext.getContext().setFuture(null);  
  8.     return new RpcResult();  
  9. else if (isAsync) {  
  10.     ResponseFuture future = currentClient.request(inv, timeout) ;  
  11.     RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));  
  12.     return new RpcResult();  
  13. else {  
  14.     RpcContext.getContext().setFuture(null);  
  15.     return (Result) currentClient.request(inv, timeout).get();  
  16. }  
  • 如果不需要返回值,直接使用send方法,发送出去,设置当期和线程绑定RpcContext的future为null
  • 如果需要异步通信,使用request方法构建一个ResponseFuture,然后设置到和线程绑定RpcContext中
  • 如果需要同步通信,使用request方法构建一个ResponseFuture,阻塞等待请求完成
可以看到的是它把 ResponseFuture设置到与当前线程绑定的 RpcContext中了,如果我们要获取异步结果,则需要通过 RpcContext来获取当前线程绑定的RpcContext,然后就可以获取Future对象。如下所示:
[java] view plain copy
  1. String result1 = helloService.hello("World");  
  2. System.out.println("result :"+result1);  
  3. System.out.println("result : "+RpcContext.getContext().getFuture().get());  
当设置成异步请求的时候,result1则为null,然后通过RpcContext来获取相应的值。
然后我们来看下异步请求的整个实现过程,即上述currentClient.request方法的具体内容:
[java] view plain copy
  1. public ResponseFuture request(Object request, int timeout) throws RemotingException {  
  2.     // create request.  
  3.     Request req = new Request();  
  4.     req.setVersion("2.0.0");  
  5.     req.setTwoWay(true);  
  6.     req.setData(request);  
  7.     DefaultFuture future = new DefaultFuture(channel, req, timeout);  
  8.     try{  
  9.         channel.send(req);  
  10.     }catch (RemotingException e) {  
  11.         future.cancel();  
  12.         throw e;  
  13.     }  
  14.     return future;  
  15. }  
第一步:创建出一个 request对象,创建过程中就自动产生了 requestId,如下
[java] view plain copy
  1. public class Request {  
  2.     private final long    mId;  
  3.     private static final AtomicLong INVOKE_ID = new AtomicLong(0);  
  4.   
  5.     public Request() {  
  6.         mId = newId();  
  7.     }  
  8.   
  9.     private static long newId() {  
  10.         // getAndIncrement()增长到MAX_VALUE时,再增长会变为MIN_VALUE,负数也可以做为ID  
  11.         return INVOKE_ID.getAndIncrement();  
  12.     }  
  13. }  
第二步:根据request请求封装成一个 DefaultFuture对象,通过该对象的get方法就可以获取到请求结果。该方法会阻塞一直到请求结果产生。同时DefaultFuture对象会被存至 DefaultFuture类如下结构中:
[java] view plain copy
  1. private static final Map<Long, DefaultFuture> FUTURES   = new ConcurrentHashMap<Long, DefaultFuture>();  
key就是请求id
第三步:将上述请求对象发送给服务器端,同时将DefaultFuture对象返给上一层函数,即 DubboInvoker中,然后设置到当前线程中
第四步:用户通过 RpcContext来获取上述 DefaultFuture对象来获取请求结果,会 阻塞至服务器端返产生结果给客户端
第五步:服务器端产生结果,返回给客户端会在客户端的handler的receive方法中接收到,接收到之后判别接收的信息是 Response后,
[java] view plain copy
  1. static void handleResponse(Channel channel, Response response) throws RemotingException {  
  2.     if (response != null && !response.isHeartbeat()) {  
  3.         DefaultFuture.received(channel, response);  
  4.     }  
  5. }  
当某个线程多次发送异步请求时,都会将返回的DefaultFuture对象设置到当前线程绑定的RpcContext中,就会造成了覆盖问题,如下调用方式:
[java] view plain copy
  1. String result1 = helloService.hello("World");  
  2. String result2 = helloService.hello("java");  
  3. System.out.println("result :"+result1);  
  4. System.out.println("result :"+result2);  
  5. System.out.println("result : "+RpcContext.getContext().getFuture().get());  
  6. System.out.println("result : "+RpcContext.getContext().getFuture().get());  
即异步调用了hello方法,再次异步调用,则前一次的结果就被冲掉了,则就无法获取前一次的结果了。必须要调用一次就立马将DefaultFuture对象获取走,以免被冲掉。即这样写:
[java] view plain copy
  1. String result1 = helloService.hello("World");  
  2. Future<String> result1Future=RpcContext.getContext().getFuture();  
  3. String result2 = helloService.hello("java");  
  4. Future<String> result2Future=RpcContext.getContext().getFuture();  
  5. System.out.println("result :"+result1);  
  6. System.out.println("result :"+result2);  
  7. System.out.println("result : "+result1Future.get());  
  8. System.out.println("result : "+result2Future.get());  
    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);  
            // ... 一些操作  
        }  
       
        // ...  
    }  

    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);  
            // ... 一些操作  
        }  
       
        // ...  
    }  

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

    public interface Invocation {  
        URL getUrl();  
        String getMethodName();  
        Class<?>[] getParameterTypes();  
        Object[] getArguments();  
    }  

    public interface Invocation {  
        URL getUrl();  
        String getMethodName();  
        Class<?>[] getParameterTypes();  
        Object[] getArguments();  
    }  

猜你喜欢

转载自blog.csdn.net/xp_zyl/article/details/79972659