[dubbo 源碼之 ]1. 服務提供方如何發佈服務

服務發佈

啓動流程

1.ServiceConfig#export

服務提供方在啓動部署時,dubbo會調用ServiceConfig#export來激活服務發佈流程,如下所示:

  • Java API:

// 1. 創建ServiceConfig實例
ServiceConfig 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進行處理的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章