IT教程 ·

[dubbo 源码之 ]1. 效劳提供方怎样宣布效劳

JVM类加载器是否可以加载自定义的String

效劳宣布

启动流程

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"
        java /** * @since 2.7.0 * ServiceData Store */ 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();
      
      /**
       * A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its
       * default implementation
       */
      // 扩大适配类
      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来猎取代办完成类,代码以下:
        /**
         * JavaassistRpcProxyFactory
         */
        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举行处置惩罚的。

工作五年的.neter的一些经历感想和对未来的一些疑惑

参与评论