[dubbo 源码之 ]1. 服务提供方如何发布服务

服务发布

启动流程

1.ServiceConfig#export

服务提供方在启动部署时,dubbo会调用ServiceConfig#export来激活服务发布流程,如下所示:

  • Java API:

// 1. 创建ServiceConfig实例
ServiceConfig<IGreetingService> serviceConfig = new ServiceConfig<>();
// 2. 设置应用程序配置
serviceConfig.setApplication(new ApplicationConfig("deep-in-dubbo-first-provider"));
// 3. 设置注册中心
RegistryConfig registryConfig = new RegistryConfig("zookeeper://127.0.0.1:2181/");
serviceConfig.setRegistry(registryConfig);
// 4. 设置接口和实现类
// 5. 设置服务分组和版本
// dubbo中,服务接口+服务分组+服务版本 唯一的确定一个服务,同一个接口可以有不同版本,方便维护升级
serviceConfig.setInterface(IGreetingService.class);
serviceConfig.setRef(new GreetingServiceImpl());
serviceConfig.setVersion("1.0.0");
serviceConfig.setGroup("dubbo-sxzhongf-group");
RpcContext.getContext().setAttachment("age","18");

        // 7. 导出服务,启动Netty监听链接请求,并将服务注册到注册中心
        serviceConfig.export();

        // 8. 挂起线程,避免服务停止
        System.out.println("api provider service is started...");
        System.in.read();
```
  • XML
  <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
         xmlns="http://www.springframework.org/schema/beans"
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
         http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
  
      <!-- provider's application name, used for tracing dependency relationship -->
      <dubbo:application name="first-xml-provider"/>
      <!-- use multicast registry center to export service -->
      <dubbo:registry address="zookeeper://127.0.0.1:2181/"/>
      <!-- use dubbo protocol to export service on port 20880 -->
      <dubbo:protocol name="dubbo" port="20880"/>
      <!-- service implementation, as same as regular local bean -->
      <bean id="demoService" class="com.sxzhongf.deep.in.dubbo.provider.service.impl.GreetingServiceImpl"/>
      <!-- declare the service interface to be exported -->
      <dubbo:service interface="com.sxzhongf.deep.in.dubbo.api.service.IGreetingService"
                     ref="demoService" version="1.0.0" group="dubbo-sxzhongf-group">
          <dubbo:method name="sayHello" async="false" timeout="0" retries="3"></dubbo:method>
          <dubbo:method name="testGeneric" async="false" timeout="10000" retries="3"></dubbo:method>
      </dubbo:service>
  </beans>

查看export源码可知,总共有三种服务导出选项:
java public synchronized void export() { //1. 是否导出 if (!shouldExport()) { return; } ... //2.延迟导出 if (shouldDelay()) { DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); } else { //3.立刻导出 doExport(); } }

2.ServiceConfig#doExport

此方法主要是根据设置的属性进行合法性检查,主要包含是否已被导出,doExportUrls();

3.doExportUrls
4.ConfigValidationUtils#loadRegistries

此方法用来加载所有的服务注册中心对象,在dubbo中,一个service可以被注册到多个注册中心。

通过doExportUrlsFor1Protocol(protocolConfig, registryURLs);

5.doExportUrlsFor1Protocol

在此方法中会将所有的参数封装成org.apache.dubbo.common.URL对象,然后执行具体的服务导出。

具体过程分为:

  • 1.解析MethodConfig配置(单独的方法调用参数设置)

  • 2.泛型调用类型设置

  • 3.拼接URL参数

  • 4.导出具体服务

    导出又分为四种范围(scope):

    • SCOPE_NONE = "none",如果设定为none,表示该服务不导出。

    • SCOPE_LOCAL = "local" ,如果设定为local,表示该服务导出到本地(injvm--伪协议,实现类为:org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol

      • SCOPE_REMOTE = "remote",如果设定为remote,表示该服务导出到远程。
    • 如果有注册中心,发布到注册中心

    • 如果没有注册中心,则表示服务是直连方式

    • dubbo-2.7.0开始,新增加了WritableMetadataService 来存储dubbo 服务的元数据,元数据可以存储在远端配置中心和本地,默认是存储在本地,通过设置:METADATA_KEY = "metadata"

      • DEFAULT_METADATA_STORAGE_TYPE = "local"

      • REMOTE_METADATA_STORAGE_TYPE = "remote"

            WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
            if (metadataService != null) { metadataService.publishServiceDefinition(url);
            }
        
      • 不设置,导出到本地和远端

    • 最终执行导出的代码如下

      // 扩展适配类
      private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
      
      // 扩展适配类
      private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
      ...
      
      Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
      DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
      
      Exporter<?> exporter = protocol.export(wrapperInvoker);
      exporters.add(exporter);
      

      由于protocolPROXY_FACTORY都是扩展适配类,跟踪代码我们可以发现:

      • 执行PROXY_FACTORY.getInvoker的时候实际上首先执行扩展接口ProxyFactory的适配类ProxyFactory$AdaptivegetInvoker方法,根据URL中参数proxy的设置类型选择具体的代理工厂,默认使用的是javassist,,因此又调用了org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker来获取代理实现类,代码如下:

        
        public class JavassistProxyFactory extends AbstractProxyFactory {
            ...
            @Override
            public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
                // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
                // 这里使用javassist动态代理生成serviceImpl实现类的包装类`Wraaper...`
                final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
                return new AbstractProxyInvoker<T>(proxy, type, url) {
                    @Override
                    protected Object doInvoke(T proxy, String methodName,
                                              Class<?>[] parameterTypes,
                                              Object[] arguments) throws Throwable {
                        return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
                    }
                };
            }
            ...
        }
        

        上面代码有2个目的:

        1. inal Wrapper wrapper = Wrapper.getWrapper(...);用来生成具体serviceImpl的包装类,减少反射的性能损耗;
        2. return new AbstractProxyInvoker<T>... 返回了一个抽象的代理invoker,并且重写了doInvoker方法,重写之后使用包装类中的invokeMethod来调用方法。

经过上述2步,服务提供方就将具体的实现类转换为Invoker代理。

  • 然后,当执行protocol.export(),实际上也是调用了Protocol$Adaptive#export()方法,同时也分为两种情况
  • 如果为远程暴露,则执行RegistryProtocol#export
  • 如果为本地暴露,则只需InjvmProtocol#export
    由于dubbo的增强SPI特性支持,injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));,则在调用之前会一层一层调用,ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper,最后会调用export方法,此方法会将Invoker转换为Exporter对象,在org.apache.dubbo.registry.integration.RegistryProtocol#export方法中,org.apache.dubbo.registry.integration.RegistryProtocol#doLocalExport方法启NettyServer来监听服务,org.apache.dubbo.registry.integration.RegistryProtocol#register将当前的服务注册到注册中心。
  • doLocalExport 是如何启动NettyServer呢?
                private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
                    String key = getCacheKey(originInvoker);
            
                    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
                        Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
                        return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
                    });
                }

此时URL中的protocol类型为默认的dubbo,因此会执行DubboProtocol#export进行转换,如下:

            @Override
                public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
                    URL url = invoker.getUrl();
            
                    // export service.
                    String key = serviceKey(url);
                    // invoker->exporter
                    DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
                    exporterMap.put(key, exporter);
            
                    //export an stub service for dispatching event
                    Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
                    Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
                    if (isStubSupportEvent && !isCallbackservice) {
                        String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
                        if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                            if (logger.isWarnEnabled()) {
                                logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
                                        "], has set stubproxy support event ,but no stub methods founded."));
                            }
            
                        } else {
                            stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
                        }
                    }
                    //创建server
                    openServer(url);
                    //序列化提示
                    optimizeSerialization(url);
            
                    return exporter;
                }

可以看到代码执行到openServer,因为key=getAddress()=ip+port,因此,同一台机器只会开启一个NettyServer.

               private void openServer(URL url) {
                   // find server.
                   String key = url.getAddress();
                   //client can export a service which's only for server to invoke
                   boolean isServer = url.getParameter(IS_SERVER_KEY, true);
                   if (isServer) {
                       ProtocolServer server = serverMap.get(key);
                       if (server == null) {
                           synchronized (this) {
                               server = serverMap.get(key);
                               if (server == null) {
                                   serverMap.put(key, createServer(url));
                               }
                           }
                       } else {
                           // server supports reset, use together with override
                           server.reset(url);
                       }
                   }
               }

对于org.apache.dubbo.remoting.Transporter 的适配类选择有三种:MinaTransporterNettyTransporterGrizzlyTransporter,关于JavaNIO:Apache Mina、JBoss Netty、Sun Grizzly 框架对比:传送门

  • NettyServer启动之后,回到org.apache.dubbo.registry.integration.RegistryProtocol#export方法,继续执行将服务注册到注册中心,我们以Zookeeper为例:
  • 1.首先查找所有注册中心
                final Registry registry = getRegistry(originInvoker);
                ...
                protected Registry getRegistry(final Invoker<?> originInvoker) {
                    URL registryUrl = getRegistryUrl(originInvoker);
                    return registryFactory.getRegistry(registryUrl);
                }

因为RegistryFactory是一个SPI扩展接口,代码中设置的为zookeeper,因此这里调用的是ZookeeperRegistryFactory,继承自:org.apache.dubbo.registry.support.AbstractRegistryFactory#getRegistry(org.apache.dubbo.common.URL),在此方法中调用了createRegistry,但是ZookeeperRegistryFactory重写了createRegistry,因此具体调用的是ZookeeperRegistryFactory#createRegistry,该方法返回了一个new ZookeeperRegistry(url, zookeeperTransporter)实例对象。

  • 2.开始注册,RegistryProtocol#register方法执行注册动作,首先获取到我们在上一步找到的注册中心ZookeeperRegistry,ZookeeperRegistry 执行父类org.apache.dubbo.registry.support.FailbackRegistry#register,在该方法中会调用抽象方法:doRegister,ZookeeperRegistry 重写了改方法,则执行ZookeeperRegistry#doRegister ,如下:
                @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.toUrlPath方法会把org.apache.dubbo.common.URL转换格式后存储到zookeeper,如下:
                dubbo://172.16.44.21:20880/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService?anyhost=true&application=deep-in-dubbo-first-provider&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=dubbo-sxzhongf-group&interface=com.sxzhongf.deep.in.dubbo.api.service.IGreetingService&methods=sayHello,testGeneric&pid=8480&release=2.7.5&revision=1.0.0&side=provider&timestamp=1582872610313&version=1.0.0
                
                -----------------------转换------------------------
                
                /dubbo/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService/providers/dubbo%3A%2F%2F172.16.44.21%3A20880%2Fcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%3Fanyhost%3Dtrue%26application%3Ddeep-in-dubbo-first-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Ddubbo-sxzhongf-group%26interface%3Dcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%26methods%3DsayHello%2CtestGeneric%26pid%3D8480%26release%3D2.7.5%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1582872610313%26version%3D1.0.0

转换之后的格式其实就是我们在zookeeper中看到的一样了,不过有几个目录:

  • dubbo
  • com.sxzhongf.deep.in.dubbo.api.service.IGreetingService
  • providers
               [zk: localhost:2181(CONNECTED) 2] ls  /dubbo/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService/providers
               [dubbo%3A%2F%2F172.16.44.21%3A20880%2Fcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%3Fanyhost%3Dtrue%26application%3Ddeep-in-dubbo-first-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Ddubbo-sxzhongf-group%26interface%3Dcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%26methods%3DsayHello%2CtestGeneric%26pid%3D15716%26release%3D2.7.5%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1582872850187%26version%3D1.0.0]

至此,服务消费端就可以从注册中心获取服务提供service进行调用了,下节我们继续来分析,消费端是如何从注册中心拉取service进行处理的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345