资讯专栏INFORMATION COLUMN

Dubbo服务提供者发布过程

lifesimple / 1784人阅读

摘要:将标记为服务,使用对象来提供具体的服务。这整个过程算是该类的典型的执行过程。从上面得知服务发布的第一二个过程获取注册中心信息和协议信息。对于端来说,上述服务发布的第步中要解决的问题是根据指定协议向注册中心注册服务。

上图是服务提供者暴露服务的主过程

首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloServiceImpl),然后通过ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化。接下来就是Invoker转换到Exporter的过程。

Dubbo处理服务暴露的关键就在Invoker转换到Exporter的过程(如上图中的红色部分),Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由Dubbo自己实现.

服务发布过程大致分成3步

1、获取注册中心信息,构建协议信息,然后将其组合。
2、通过ProxyFactory将HelloServiceImpl封装成一个Invoker执行 。
3、使用Protocol将invoker导出成一个Exporter(包括去注册中心注册服务等)。

这里面就涉及到几个大的概念,ProxyFactory、Invoker、Protocol、Exporter

Export 服务暴露的步骤

1、首先会检查各种配置信息 等标签的配置,填充各种属性,总之就是保证我在开始暴露服务之前,所有的东西都准备好了,并且是正确的。
2、加载所有的注册中心,因为我们暴露服务需要注册到注册中心中去。
3、根据配置的所有协议和注册中心url分别进行导出。
4、进行导出的时候,又是一波属性的获取设置检查等操作。
5、如果配置的不是remote,则做本地导出。
6、如果配置的不是local,则暴露为远程服务。
7、不管是本地还是远程服务暴露,首先都会获取Invoker。
8、获取完Invoker之后,转换成对外的Exporter,缓存起来。
9、执行DubboProtocol类的export方法,打开socket侦听服务,并接收客户端发来的各种请求。

概念介绍

先看一个简单的服务端例子,dubbo配置如下:





有一个服务接口HelloService,以及它对应的实现类HelloServiceImpl。

将HelloService标记为dubbo服务,使用HelloServiceImpl对象来提供具体的服务。

使用zooKeeper作为注册中心。

Invoker

Invoker,一个可执行对象,能够根据方法名称、参数得到相应的执行结果。接口如下:

public interface Invoker {

    Class getInterface();

    URL getUrl();

    Result invoke(Invocation invocation) throws RpcException;

    void destroy();

}

而Invocation则包含了需要执行的方法、参数等信息,接口如下:

public interface Invocation {

    URL getUrl();

    String getMethodName();

    Class[] getParameterTypes();

    Object[] getArguments();

}

目前其实现类只有一个RpcInvocation

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

本地执行的Invoker

远程通信执行的Invoker

多个类型2的Invoker聚合成的集群版Invoker

以HelloService接口为例:

本地执行的Invokerserver端,含有对应的HelloServiceImpl实现,要执行该接口方法,仅仅只需要通过反射执行HelloServiceImpl对应的方法即可。

远程通信执行的Invokerclient端,要想执行该接口方法,需要需要进行远程通信,发送要执行的参数信息给server端;server端,利用上述本地执行的Invoker执行相应的方法,然后将返回的结果发送给client端。这整个过程算是该类Invoker的典型的执行过程。

集群版的Invokerclient端,拥有某个服务的多个Invoker,此时client端需要做的就是将多个Invoker聚合成一个集群版的Invoker,client端使用的时候,仅仅通过集群版的Invoker来进行操作。集群版的Invoker会从众多的远程通信类型的Invoker中选择一个来执行(从中加入负载均衡、服务降级等策略),类似服务治理,dubbo已经实现了、

看下Invoker的实现情况:

ProxyFactory

对于Server端,主要负责将服务如HelloServiceImpl统一进行包装成一个Invoker,通过反射来执行具体的HelloServiceImpl对象的方法

接口定义如下:

@SPI("javassist")
public interface ProxyFactory {

     //针对client端,创建出代理对象
    @Adaptive({Constants.PROXY_KEY})
     T getProxy(Invoker invoker) throws RpcException;

    //针对server端,将服务对象如HelloServiceImpl包装成一个Invoker对象
    @Adaptive({Constants.PROXY_KEY})
     Invoker getInvoker(T proxy, Class type, URL url) throws RpcException;

}

ProxyFactory的接口实现有JdkProxyFactory、JavassistProxyFactory,默认是JavassistProxyFactory, JavassistProxyFactory内容如下:

public class JavassistProxyFactory extends AbstractProxyFactory {

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

    public  Invoker getInvoker(T proxy, Class type, URL url) {
        // TODO Wrapper类不能正确处理带$的类名
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf("$") < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker(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。
2、使用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
     Exporter export(Invoker invoker) throws RpcException;

    //这个是针对客户端的,客户端从注册中心获取服务器端发布的服务信息
    //通过服务信息得知服务器端使用的协议,然后客户端仍然使用该协议构造一个Invoker。这个Invoker是远程通信类的Invoker。
    //执行时,需要将执行信息通过指定协议发送给服务器端,服务器端接收到参数Invocation,然后交给服务器端的本地Invoker来执行
    @Adaptive
     Invoker refer(Class 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 export(final Invoker 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 {

    Invoker 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

作者:鹏磊

出处:http://www.ymq.io/2018/06/13/dubbo_rpc_export

版权归作者所有,转载请注明出处

Wechat:关注公众号,搜云库,专注于开发技术的研究与知识分享

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71184.html

相关文章

  • Dubbo 一篇文章就够了:从入门到实战

    摘要:启动容器,加载,运行服务提供者。服务提供者在启动时,在注册中心发布注册自己提供的服务。注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 一 为什么需要 dubbo 很多时候,其实我们使用这个技术的时候,可能都是因为项目需要,所以,我们就用了,但是,至于为什么我们需要用到这个技术,可能自身并不是很了解的,但是,其实了解技术的来由及背景知识,对...

    tomener 评论0 收藏0
  • Dubbo服务暴露过程

    摘要:根据的值,进行服务暴露。如果配置为则不暴露,如果服务未配置成,则本地暴露如果未配置成,则暴露远程服务。提供者向注册中心订阅所有注册服务当注册中心有此服务的覆盖配置注册进来时,推送消息给提供者,重新暴露服务,这由管理页面完成。 概览 dubbo暴露服务有两种情况,一种是设置了延迟暴露(比如delay=5000),另外一种是没有设置延迟暴露或者延迟设置为-1(delay=-1): 设置了...

    bigdevil_s 评论0 收藏0
  • Dubbo 2.7.1 踩坑记

    摘要:面试题服务提供者能实现失效踢出是什么原理高频题服务宕机的时候,该节点由于是持久节点会永远存在,而且当服务再次重启的时候会将重新注册一个新节点。 Dubbo 2.7 版本增加新特性,新系统开始使用 Dubbo 2.7.1 尝鲜新功能。使用过程中不慎踩到这个版本的 Bug。 系统架构 Spring Boot 2.14-Release + Dubbo 2.7.1 现象 Dubbo 服务者启动...

    wudengzan 评论0 收藏0
  • 网关实现灰度发布

    摘要:就是一种灰度发布方式,让一部分用户继续用,一部分用户开始用,如果用户对没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现调整问题,以保证其影响度。 一、背景互联网产品开发有个非常特别的地方,就是不停的升级,升级,再升级。采用敏捷开发的方式,基本上保持每周或者每两周一次的发布频率,系统升级总是伴随着各种风险,新旧版本兼...

    stormjun 评论0 收藏0

发表评论

0条评论

lifesimple

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<