Dubbo——服务暴露过程分析

版权声明:本文为博主原创文章,欢迎转载哦 https://blog.csdn.net/wgp15732622312/article/details/82316698

    这篇文章来叙述对dubbo的服务暴露前的准备工作:

使用Spring配置文件,通过main方法来启动spring容器,来观察dubbo服务的启动过程。

dubbo配置文件

    <context:component-scan base-package="com.wk.order.service.impl,com.wk.order.facade.impl"/>

    <dubbo:application name="dubbo-order-service"/>
    <!--<dubbo:registry address="multicast://224.5.6.7:1234" />-->
    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" />

    <dubbo:protocol name="dubbo" port="20880"/>
    <dubbo:service interface="com.wk.common.facade.OrderFacade" ref="orderFacadeImpl" loadbalance="roundrobin"/>

在说明暴露服务的流程之前,先明确知道几个对象,在dubbo里,默认使用JavassisFactory动态代理工厂来动态的产生扩展对象,然后根据扩展对象的export方法,根据url的参数配置,例如上述为dubbo协议,注册中心为zookeeper。这样呢就会根据url的配置使用DubboProtocol来进行服务的暴露,创建NettyServer。默认为netty启动。默认也是Dubbo。会使用ZookeeperRegistry把服务注册的注册中心。

在Spring容器启动时,会对Bean进行加载,Dubbo通过Spring自定义标签扩展了自己的标签。把属于Dubbo的实体Bean注册到Spring里。


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

上面注册了ServiceBean这个类,那么ServiceBean是什么什么东西呢,观察ServiceBean对象,它实现了InitializingBean接口,在该接口中有一个afterPropertiesSet方法,在Bean的属性初始化时,Spring会默认调用该方法。该方法里调用了关于dubbo的开始 export方法。那么dubbo的服务暴露export方法就在这里开始起航了。

第一步:在afterPropertiesSet里会从applicationContext里获取诸多实例,诸多实例就是上述注册器里的Bean.class。经过这一层之后,就会调用export方法。此时在ServiceConfig对象里,已经有了关于dubbo配置的一些内容。

走入export方法后共有多个方法一连串的调用:

doExport方法对一些配置进行检测和获取,检查provider,application,module,registries,monitor这些参数是否为空,是否是GenericService类型的服务,检查要注册的bean的引用和方法等。虽然不知道这些是做什么用的。

doExportUrls:很明显,它这是获取注册中心的url路径。因为dubbo支持多注册中心,所以需要获取多个注册中心的url。

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-order-service&dubbo=2.5.7&pid=9720&registry=zookeeper&timestamp=1535884939183

doExportUrlsFor1Protocol:该方法是真正的开始暴露服务了,在这个方法里有一大堆的参数在往map里塞。这就是在为服务暴露做准备。dubbo的主线是Invoker对象,而url就是包含各种配置在内的一个协议字符串。根据这个字符串能够做好多的事情。

doExportUrlsFor1Protocol方法很长,其中有几处需要关注:

1、获取主机地址,这段逻辑曾经在线上部署时,出现过问题。如今恍然大悟。在获取主机地址的逻辑中:优先从配置里获取主机地址,否则从本地的InetAddress获取地址,再者从Socket里获取地址。

2、本地服务不需要notify。即injvm是不需要进行notiry的。injvm是本地暴露的协议。类似:registry等协议。

   if ("injvm".equals(protocolConfig.getName())) {
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }
        // 导出服务
        String contextPath = protocolConfig.getContextpath();
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }

        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);

在一大堆参数配置完毕后,根据scope的配置决定是否要进行本地暴露。

本地暴露过程:本地暴露过程相对简单,主要是通过扩展机制使用JavassitFactory对业务类进行动态代理生成一个动态代理对象,使用injvmProtocol把invoker包装为一个Exporter。最后放到ServiceConfig的一个属性exports里。exports是一个list集合。

对于本地暴露的过程,为什么要这样进行暴露,以及包装过程中都做了什么,有什么值得去包装的呢?

根据我的理解,在这里其实是一个IOC和AOP的体现,从Dubbo的扩展机制,实现了一个SPI自定义扩展,你可以加上无数个扩展。再加上Adaptive增强注解+装饰机制达到了AOP的效果。对于这里的包装有什么效果呢,目前知道和过滤器链有关。先打上个标记。

对于远程暴露,和本地暴露也是同样的过程,只不过Dubbo在把Invoker包装为Exporter的过程中,做了两件事情:

1、创建序列化和反序列化实例,根据扩展机制生成Server实例,默认使用NettyServer,还给Server添加了经过一系列包装的handler对象,因为对网络编程编码,解码,Netty拆包,粘包,dubbo的线程池处理不了解,只能先到这里了。总之创建Server实例完成之后,就返回了Exporter对象。

2、在返回Exporter对象之后,RegisterProtocol继续发挥它的余热,但是却也做了一个了不起的事情。接下来它需要把服务通过扩展机制得到的注册中中心,把服务注册到注册中心上。

在返回Export对象之后,register第一个要做的事情就是打通注册中心,本例为zookeeper,在看源码过程中,这里会在zk上先建立一个/dubbo的节点,并生成一个监听。然后返回回来后,依据提供者的配置,获取url参数。接下来的事情就比较简单了,根据zkClient直接在dubbo上创建节点就OK了。

还会调用registry的subscribe方法,这里主要是加了注册订阅Listener,在创建出其他节点之后会调用notify方法。notify方法会做两件事,1. 会将url改动更新到缓存的配置文件中。2. 会通知listener变动,此通知为全量通知

下面来看一下我的zookeeper的节点内容,从第一行看起,会看到在 /dubbo/com.wk.common.facade.OrderFacade下一共有4个节点,分别是 consumers,configurators,routers,providers。其中在providers下的内容见第一个红框,根据显示,可以看到providers下有一大堆的内容,想来这就是我们在dubbo上注册的那个url喽。

对于configurators和routers都是为空,相比应该是在dubbo控制台上做一些处理,之后就会有内容了。也就是说,当这些节点有变化的时候,就会通知ZookeeperRegister发生变化。

从官方的说明来说,服务启动的时候,会向providers目录下写入自己的url地址。估计我这里的consumbers是以前消费者启动写入的。

暂时先这样,有不同见解的,欢迎评论。

猜你喜欢

转载自blog.csdn.net/wgp15732622312/article/details/82316698