dubbo源码分析6(服务暴露之本地暴露)

dubbo源码分析6(服务暴露之本地暴露)

  前面我们做了一大堆的准备工作,包括dubbo是怎么跟spring进行整合的,然后一步一步是怎么找到启动入口的,而且还知道了,由于我们的dubbo的版本是2.7.5,所以其实启动的入口是DubboBootstrap类,一切的开始都从这个启动器开始出发

  下面,开始我们这一次的开车之旅;

  提前须知:

  服务提供者肯定就是提供一个接口,然后供别的消费者使用的,那么问题来了,服务提供者怎么做才能提供给别的人使用呢?

  其实服务提供者在初始化的时候,做了6件事(假设注册中心是zookeeper)

  (1)  启动容器(就是spring的ioc容器)

  (2)暴露服务(本地暴露和远程暴露)

  (3)开启netty服务端

  (4)连接zookeeper

  (5)将服务信息写入zookeeper

     (6)监听zookeeper节点(或者说订阅服务信息)

  

  只要做完了这6件事情,一个新鲜可口的服务就可以使用了,我们可以使用官方的图来看看,下图所示,其实就是完成了下图中的步骤0和步骤1

 

1.启动容器

  大家猜猜我们启动一个dubbo服务需要些什么东西?需要tomcat吗?  

  其实启动dubbo服务的话,我们只需要有spring+注册中心就可以了,其他的都不需要,为什么不需要tomcat这种servlet容器啊?

  你想想啊,我们在服务提供者这里,使用到了Controller了么?使用到了http协议了么?都没有吧,而且如果dubbo需要使用servlet容器,那么还需要额外消耗内存,性能也会下降

  dubbo启动方式有三种

  1.1  servlet容器:这种是最消耗性能的,我们肯定不会使用

  1.2 自己写一个main方法运行(Spring容器):官方demo使用的就是这种方式,下图所示,其实就是我们自己创建一个spring容器,去启动,就能加载自定义的配置文件,这种方式适用于测试和调试(我们看源码就是使用这种启动方式)

 

  1.3 官方提供的启动方式:org.apache.dubbo.container.Main类有个main方法,这里可以启动,而且可以看到默认使用的是SpringContainer

 

  我们看看SpringContainer类中的start()方法,发现也是sping容器启动的

 

  如果到了这里你还是不会启动,官方还提供了脚本方式启动,脚本给你准备好了,可以支持你的服务在linux中去启动

 

  总结一下,有三种启动dubbo服务的方式,第一种是使用tomcat等servlet容器去启动,第二种是我们自己简单的使用一下spring容器加载配置文件,第三种就是官方提供的Main类,内部对spring容器的启动和停止进行了更好的封装,并提供了一系列的服务端启动脚本

  开发调试的时候可以使用第二种,生产上线使用第三种,第一种狗都不用( ̄▽ ̄)ノ,哈哈哈哈

 

2.暴露服务(本地暴露和远程暴露)

  记得前面说过,暴露服务分为本地暴露和远程暴露,再说明一下,为什么会有本地暴露?

  就是当前同一个jvm中有其他的服务要使用到当前的服务,肯定不会去走注册中心调用服务呀(就类似于你家开餐馆的,难道你吃东西会使用美团外卖点自己家的菜么?生草๑乛◡乛๑) 

  我们还是从DubboBootstrap的start()方法开始看:

 

 

  看看下面SerrviceBean的类继承图,这个export()方法在ServiceConfig类中

 

 

  继续跟进这个doExport()方法

 

 

 

  上面说了一大堆的垃圾话,接下来才是重点,由于下面这个方法太长,我把代码拷贝下来,然后大篇幅的省略一些代码

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
       
       //省略了好多好多代码

        String scope = url.getParameter(SCOPE_KEY);
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            //1.本地暴露服务
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            //2.远程暴露服务
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                //2.1如果存在注册中心,就把服务暴露到注册中心
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {

                        //省略代码

                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        //2.1.1 加载dubbo的监控中心,如果有监控中心
                        //就将配置添加到url中
                        URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        }
                        //省略一些代码
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }

                        //2.1.2 这里将需要导出的服务封装成Invoker对象
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        //2.1.3 将invoker导出到注册中心,并生成exporter
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        //2.1.4 将export收集
                        exporters.add(exporter);
                    }
                    //2.2 没有服务注册中心,就采用直连的方式导出服务,不需要配置监听,直接将invoker暴露到注册中心,并生成exporter
                } else {
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
               //省略代码
            }
        }
        this.urls.add(url);
    }

  

  接下来又是一个很重要的地方!!!这个invoker表示一个什么呀?

  我们首先需要将日志级别修改一下:

 

  然后重新启动调试,根据控制台打印的ProxyFactory$Adaptive类和,自己去对应的包下新建目录,把控制台的代码拷贝过去

  最后将日志级别改为info,重新启动调试就好了,嘿嘿!

  没办法,就是要这样奇葩的操作,才能在等会儿调试的时候才会进入到这两个类里面(-_-メ)

 

 

3.本地暴露服务

  进入到ServiceConfig的doExportUrlsFor1Protocol()方法,然后下图的exportLocal(url)方法

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
       
       //省略了好多好多代码

        String scope = url.getParameter(SCOPE_KEY);
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            //1.本地暴露服务
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }

 

  注意下图中URL local,是以injvm开头的,说明是暴露在本地jvm中的(后续会说到的,如果是暴露在远程的,那就是registry开头的)

 

  在上图的getInvoker方法,就是进入到之前我们自己创建的那个类中,下面就贴一下好多代码当流程图看了(-_-メ)

 

 

 

  最后就是进入到InjvmExporter的构造器了

  注意:这里的 exporterMap对这个exporter做了缓存, 后面这个缓存map会很有用的

 

4.总结

  根据服务暴露到jvm的流程,总结出来就是下面这个图,应该很清晰了,就是将当前服务提供者中的服务首先使用代理工厂封装成Invoker,然后调用Protocal的export()方法,将invoker转换为Exporter,并且还会缓存一份这个Exporter在exporterMap中;

  后面暴露远程服务会稍微比这个复杂一丢丢,就是增加了本地启动netty的步骤和远程操作zookeeper创建节点以及监听zookeeper的变化

 

--------------以上皆原创,给未来的自己留下一点学习的痕迹!--------