dubbo服务注册与发现源码解析

1、zookeeper中注册的数据

我们一般使用zookeeper作为dubbo的注册中心,但是我们注册到dubbo中的数据是什么样的呢,这里我们就来看一下:

1、初始时zookeeper中的数据:

这是zookeeper中只有testRoot 和zookeeper两个路径

2、我们启动dubbo的提供者和消费者:

消费者: 

3、查看zookeeper中注册的信息

下面是我们将获取的两段url解码后的信息:

consumer: 

consumer://192.168.5.1/cn.zsm.dubbo.service.PersonService?
    application=dubboConsumer&category=consumers&check=false&default.timeout=1000&dubbo=2.6.2
    &interface=cn.zsm.dubbo.service.PersonService&methods=getStudent&pid=32092&retries=3
    &revision=2.0.0&side=consumer&timestamp=1621514797507&version=2.0.0

provider:

dubbo://192.168.16.1:20880/cn.zsm.dubbo.service.PersonService?
    anyhost=false&application=dubboProvider&default.timeout=4000&dubbo=2.6.2&generic=false
    &interface=cn.zsm.dubbo.service.PersonService&methods=getStudent&pid=36184&revision=2.0.0
    &side=provider&timestamp=1621514737703&version=2.0.0, 
dubbo://192.168.16.1:20880/cn.zsm.dubbo.service.PersonService?
    anyhost=false&application=dubboProvider&default.timeout=4000&dubbo=2.6.2&generic=false
    &interface=cn.zsm.dubbo.service.PersonService&methods=getStudent&pid=36184&retries=2
    &revision=1.0.0&side=provider&timestamp=1621514738394&version=1.0.0

从上面我们可以看出,zookeeper中服务的目录结构大概为: 

从图中我们可以知道: 

  • 服务提供者在providers目录下进行注册
  • 服务消费者会在consumers目录下进行注册,并监听providers目录,通过箭筒提供者的增加或减少,实现服务发现
  • Monitor模块会对整个服务级别进行监听,用来统计整体的服务情况。

2、服务注册流程

2.1 服务注册的整个流程大致如下图所示:

  1. ServiceConfig类,获取到对外提供服务的类ref(如我们上边提到的:PersionServiceImpl类)
  2. 通过ProxyFactory接口实现类中的 getInvoker 方法,使ref生成一个 AbstractProxyInvoker 实例, 完成ref 到invoker的转换
  3. 将转换后得到的invoker, 再转换得到Exporter。(这一步很关键,我们重点来看这步转换过程)

这一切操作都是在ServiceConfig中进行的,我们就来进入到ServiceConfig中看一下。

2.2 ServiceConfig 源码解读

ServiceConfig中有三个比较重要的属性: protocol(协议)、 ProxyFactory(代理工厂)、 ref(业务逻辑类,这里是泛型)

按照上面我们说的服务注册流程,ServiceConfig先获取ref,通过ProxyFactory 获取invoker, 再将invoker转换成 exporter 。 后面这两步都是在doExportUrlsFor1Protocol (执行一种协议下的url)方法中完成的。从下面的截图中我们可以看到这个方法很长,接近200行, 但是它的大部分篇幅都是用来封装一个map集合、拼接URL串。

下面我们debug模式启动服务提供者,看看服务注册的过程:

我们在proxyFactory 将ref转换成invoker处打个断点,看看这之前程序都做了些什么:

前面的一百多行代码,实际处理的事情主要是拼装一个map集合, 还有拼接URL信息。

处理ref 、invoker 、 exporter的代码只有图中红色框内部分:

  •  proxyFactory将ref 转换成invoker
  • invoker 被封装成 DelegateProviderMetaDataInvoder
  • 将封装后的invoker 转换成一个exporter (这一步是转换是服务注册的主要步骤)

下面我们重点来看看invoker是如何被转换成exporter的。

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

(1) potocol的扩展点

从potocol属性我们知道,该属性是通过SPI动态扩展的, 那么potocol都有哪些实现类:

从图中我们可以看出,potocol有11个扩展点,那么这里应用的扩展点是哪一个怎么判断??

dubbo的SPI配置中,可以根据URL信息进行选择扩展点的,那么我们只需要知道这里使用的URL信息即可:

从上图我们可以看到这里使用的事registry协议, 所以我们可以认定,这里使用的是 : RegistryProtocol的export方法

    @Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        // 获取注册中心地址
        URL registryUrl = getRegistryUrl(originInvoker);
        // 服务提供者的注册地址
        URL providerUrl = getProviderUrl(originInvoker);

        // 获取进行注册override协议的访问地址
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        // 增加override 的监听器
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
 
        // 根据现有的override协议, 对注册地址进行改写操作
        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
        // 导出当前服务, 完成后即可在本地20880端口号启动,且暴露服务
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

        // 获取注册中心实例 和 注册到注册中心的地址, 一般是 ZookeeperRegistry
        final Registry registry = getRegistry(originInvoker);
        final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);

        // 获取当前url是否需要进行注册参数
        boolean register = providerUrl.getParameter(REGISTER_KEY, true);
        if (register) { // 注册服务
            registry.register(registeredProviderUrl);
        }

        // 在服务提供者的model上注册声明的url
        registerStatedUrl(registryUrl, registeredProviderUrl, register);

        // 设置当前导出中的相关信息
        exporter.setRegisterUrl(registeredProviderUrl);
        exporter.setSubscribeUrl(overrideSubscribeUrl);

        //  注册override协议, 用于适配2.6.x 及之前的版本
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        
        notifyExport(exporter);
        // 返回导出对象
        return new DestroyableExporter<>(exporter);
    }

这里我们重点看一下服务注册的方法: 

        if (register) { // 注册服务
            registry.register(registeredProviderUrl);
        }

Registry接口及实现类: 

我们这里用的是zookeeper作为注册中心,所以这里使用的是 ZookeeperRegistry , 但是ZookeeperRegistry 中没有实现重写register方法,所以这里调用的事它的父类中的register方法:

FailbackRegistry 的register方法: 

这里有一个doRegister方法, 这个方法在 FailbackRegistry 类中是抽象方法,实际调用的是ZookeeperRegistry 中的实现:

    @Override
    public void doRegister(URL url) {
        try {
            zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
        } catch (Throwable e) {
            throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

到这里服务注册的流程已经介绍完毕。

3、URL和本地缓存

3.1 URL规则详解:

URL的格式:

potocol://host:port/path?key=val&key=val

如下面我们启动服务提供者时的url:

dubbo://192.168.16.1:20880/cn.zsm.dubbo.service.PersonService?
anyhost=false&application=dubboProvider&bind.ip=192.168.16.1&bind.port=20880&default.
timeout=4000&dubbo=2.6.2&generic=false&interface=cn.zsm.dubbo.service.PersonService
&methods=getStudent&pid=49148&revision=2.0.0&side=provider&timestamp=1621585834888
&version=2.0.0

URL 的结构及说明:

  1. protocol :协议, 一般像我们的provider、consume 着这里是认为具体的协议
  2. host : 当前provider或其他协议的具体地址, 边角特殊的像override协议所指定的host是0.0.0.0代表所有机器生效
  3. port: 代表处理的端口号
  4. path: 服务路径,在provider和consumer中代表我们真实地业务接口
  5. key=value: 代表服务的一些参数,也可以理解为对这个服务的配置

需要注意的事,dubbo中的URL与java中的URL是有一些区别的, 如:

  • dubbo提供了针对参数的parameter的增加和减少,支持动态更改
  • dubbo提供了缓存功能,对一些基础数据做缓存

3.2 服务本地缓存

dubbo支持对服务路径的本地缓存。dubbo服务消费者通过注册中心注册信息,获取服务提供者,但如果拼单从zk中获取信息,可能会出现单点故障问题,这时dubbo就需要将提供者的信息缓存到本地。

dubbo在订阅注册中心的回调处理逻辑中,会保存服务提供者的信息到本地缓存文件中,有同步和异步两种方式, 以URL为维度进行全量保存。

dubbo在服务引用过程中会创建registry对象,并加载本地缓存文件,会有限订阅注册中心,订阅注册中心失败后,才会访问本地缓存文件内容来获取服务提供者的信息。

4、服务消费流程

服务消费过程: 

  1. ReferenceConfig 使用protocol 调用refer 方法生成invoker实例
  2. ProxyFactory 将invoker实例转换成ref代理对象
  3. 调用ref代理对象中的方法,执行真实的业务逻辑操作

我们将服务注册的过程拿过来对比一下: 很容易发现,服务消费的过程就是服务注册过程的逆向操作过程

下面我们看一下服务消费过程的一些具体实现: ReferenceConfig 

在ReferenceConfig类中我们可以发现在ServiceConfig中我们重点强调的三个属性, 如下图:

上面的这些过程都是在ReferenceConfig的 init() 方法中完成的,下面我们看一下这个init方法:

init方法也要近100行,这里我们只关注里面的创建代理对象 ref 的过程:

1、调用createProxy 方法

2、createProxy 方法: 

2.1 、 REF_PROTOCOL.refer 方法生成invoker

2.2 、 PROXY_FACTORY.getProxy 方法生成代理对象

生成代理对象的底层逻辑有两种: JDK和Javassis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值