1. 程式人生 > >原始碼分析Dubbo服務提供者啟動流程-上篇

原始碼分析Dubbo服務提供者啟動流程-上篇

本節將詳細分析Dubbo服務提供者的啟動流程,請帶著如下幾個疑問進行本節的閱讀,因為這幾個問題將是接下來幾篇文章分析的重點內容。
1、什麼時候建立與註冊中心的連線。
2、服務提供者什麼時候向註冊中心註冊服務。
3、服務提供者與註冊中心的心跳機制。
從上文中我們得知,服務提供者啟動的核心入口為ServiceBean,本節將從原始碼級別詳細剖析ServcieBean的實現原理,即Dubbo服務提供者的啟動流程,ServiceBean的繼承層次如圖所示,dubbo:service標籤的所有屬性都被封裝在此類圖結構中。
這裡寫圖片描述
1、原始碼分析ServiceBean#afterPropertiesSet
ServiceBean#afterPropertiesSet

if (getProvider() == null) {  // @1
    Map<String, ProviderConfig> provide
             ConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class,   false, false); // @2
              // ......  具體解析程式碼省略。
    }
}

Step1:如果provider為空,說明dubbo:service標籤未設定provider屬性,如果一個dubbo:provider標籤,則取該例項,如果存在多個dubbo:provider配置則provider屬性不能為空,否則丟擲異常:”Duplicate provider configs”。
ServiceBean#afterPropertiesSet

 if (getApplication() == null
         && (getProvider() == null || getProvider().getApplication() == null)) {
       Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
                  ApplicationConfig.class, false, false);
        // ...
省略 }

Step2:如果application為空,則嘗試從BeanFactory中查詢dubbo:application例項,如果存在多個dubbo:application配置,則丟擲異常:”Duplicate application configs”。
Step3:如果ServiceBean的module為空,則嘗試從BeanFactory中查詢dubbo:module例項,如果存在多個dubbo:module,則丟擲異常:”Duplicate module configs: “。
Step4:嘗試從BeanFactory中載入所有的註冊中心,注意ServiceBean的List registries屬性,為註冊中心集合。
Step5:嘗試從BeanFacotry中載入一個監控中心,填充ServiceBean的MonitorConfig monitor屬性,如果存在多個dubbo:monitor配置,則丟擲”Duplicate monitor configs: “。
Step6:嘗試從BeanFactory中載入所有的協議,注意:ServiceBean的List protocols是一個集合,也即一個服務可以通過多種協議暴露給消費者。
ServiceBean#afterPropertiesSet

if (getPath() == null || getPath().length() == 0) {
       if (beanName != null && beanName.length() > 0 && getInterface() != null && getInterface().length() > 0  && beanName.startsWith(getInterface())) {
                setPath(beanName);
       }
 }

Step7:設定ServiceBean的path屬性,path屬性存放的是dubbo:service的beanName(dubbo:service id)。
ServiceBean#afterPropertiesSet

if (!isDelay()) {
     export();
}

Step8:如果為啟用延遲暴露機制,則呼叫export暴露服務。首先看一下isDelay的實現,然後重點分析export的實現原理(服務暴露的整個實現原理)。
ServiceBean#isDelay

private boolean isDelay() {
        Integer delay = getDelay();
        ProviderConfig provider = getProvider();
        if (delay == null && provider != null) {
            delay = provider.getDelay();
        }
        return supportedApplicationListener && (delay == null || delay == -1);
    }

如果有設定dubbo:service或dubbo:provider的屬性delay,或配置delay為-1,都表示啟用延遲機制,單位為毫秒,設定為-1,表示等到Spring容器初始化後再暴露服務。從這裡也可以看出,Dubbo暴露服務的處理入口為ServiceBean#export—》ServiceConfig#export。

1.1 原始碼分析ServiceConfig#export 暴露服務
呼叫鏈:ServiceBean#afterPropertiesSet——>ServiceConfig#export

public synchronized void export() {
        if (provider != null) {
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        if (export != null && !export) {   // @1
            return;
        }

        if (delay != null && delay > 0) {    // @2
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {
            doExport();    //@3
        }
    }

程式碼@1:判斷是否暴露服務,由dubbo:service export=”true|false”來指定。
程式碼@2:如果啟用了delay機制,如果delay大於0,表示延遲多少毫秒後暴露服務,使用ScheduledExecutorService延遲排程,最終呼叫doExport方法。
程式碼@3:執行具體的暴露邏輯doExport,需要大家=留意:delay=-1的處理邏輯(基於Spring事件機制觸發)。
1.2、原始碼分析ServiceConfig#doExport暴露服務
呼叫鏈:ServiceBean#afterPropertiesSet—呼叫——>ServiceConfig#export——>ServiceConfig#doExport
ServiceConfig#checkDefault

private void checkDefault() {
        if (provider == null) {
            provider = new ProviderConfig();
        }
        appendProperties(provider);
 }

Step1:如果dubbo:servce標籤也就是ServiceBean的provider屬性為空,呼叫appendProperties方法,填充預設屬性,其具體載入順序:
1)從系統屬性載入對應引數值,引數鍵:dubbo.provider.屬性名,System.getProperty。
2)載入屬性配置檔案的值。屬性配置檔案,可通過系統屬性:dubbo.properties.file,如果該值未配置,則預設取dubbo.properties屬性配置檔案。
ServiceConfig#doExport

if (ref instanceof GenericService) {
      interfaceClass = GenericService.class;
      if (StringUtils.isEmpty(generic)) {
           generic = Boolean.TRUE.toString();
      }
 } else {
      try {
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
       } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
       }
       checkInterfaceAndMethods(interfaceClass, methods);
       checkRef();
       generic = Boolean.FALSE.toString();
 }

Step2:校驗ref與interface屬性。如果ref是GenericService,則為dubbo的泛化實現,然後驗證interface介面與ref引用的型別是否一致。
ServiceConfig#doExport

if (local != null) {
      if ("true".equals(local)) {
            local = interfaceName + "Local";
      }
      Class<?> localClass;
      try {
             localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
       } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
       }
      if (!interfaceClass.isAssignableFrom(localClass)) {
           throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
       }
 }

Step3:dubbo:service local機制,已經廢棄,被stub屬性所替換。
Step4:處理本地存根Stub,

checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);

Step5:校驗ServiceBean的application、registry、protocol是否為空,並從系統屬性(優先)、資原始檔中填充其屬性。
系統屬性、資原始檔屬性的配置如下:、
application dubbo.application.屬性名,例如 dubbo.application.name
registry dubbo.registry.屬性名,例如 dubbo.registry.address
protocol dubbo.protocol.屬性名,例如 dubbo.protocol.port
service dubbo.service.屬性名,例如 dubbo.service.stub

ServiceConfig#doExport

checkStubAndMock(interfaceClass);

Step6:校驗stub、mock類的合理性,是否是interface的實現類。
ServiceConfig#doExport

doExportUrls();

Step7:執行doExportUrls()方法暴露服務,接下來會重點分析該方法。
ServiceConfig#doExport

ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);

Step8:將服務提供者資訊註冊到ApplicationModel例項中。
1.3、原始碼分析ServiceConfig#doExportUrls暴露服務具體實現邏輯
呼叫鏈:ServiceBean#afterPropertiesSet——>ServiceConfig#export——>ServiceConfig#doExport

private void doExportUrls() {
        List<URL> registryURLs = loadRegistries(true);  // @1
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);    // @2
        }
 }

程式碼@1:首先遍歷ServiceBean的List registries(所有註冊中心的配置資訊),然後將地址封裝成URL物件,關於註冊中心的所有配置屬性,最終轉換成url的屬性(?屬性名=屬性值),loadRegistries(true),引數的意思:true,代表服務提供者,false:代表服務消費者,如果是服務提供者,則檢測註冊中心的配置,如果配置了register=”false”,則忽略該地址,如果是服務消費者,並配置了subscribe=”false”則表示不從該註冊中心訂閱服務,故也不返回,一個註冊中心URL示例:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=7072&qos.port=22222&registry=zookeeper&timestamp=1527308268041
程式碼@2:然後遍歷配置的所有協議,根據每個協議,向註冊中心暴露服務,接下來重點分析doExportUrlsFor1Protocol方法的實現細節。

1.4、原始碼分析doExportUrlsFor1Protocol
呼叫鏈:ServiceBean#afterPropertiesSet——>ServiceConfig#export——>ServiceConfig#doExport——>ServiceConfig#doExportUrlsFor1Protocol
ServiceConfig#doExportUrlsFor1Protocol

String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
     name = "dubbo";
}
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
    map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);

Step1:用Map儲存該協議的所有配置引數,包括協議名稱、dubbo版本、當前系統時間戳、程序ID、application配置、module配置、預設服務提供者引數(ProviderConfig)、協議配置、服務提供Dubbo:service的屬性。
ServiceConfig#doExportUrlsFor1Protocol

if (methods != null && !methods.isEmpty()) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && !arguments.isEmpty()) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (methods != null && methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // target the method, and get its signature
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) {
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + 
                                                      argument.getType());
                                            }
                                        } else {
                                            // multiple callbacks in the method
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                        throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", 
                                                             type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }

                    }
                }
            } // end of methods for
        }

Step2:如果dubbo:service有dubbo:method子標籤,則dubbo:method以及其子標籤的配置屬性,都存入到Map中,屬性名稱加上對應的方法名作為字首。dubbo:method的子標籤dubbo:argument,其鍵為方法名.引數序號。
ServiceConfig#doExportUrlsFor1Protocol

if (ProtocolUtils.isGeneric(generic)) {
      map.put(Constants.GENERIC_KEY, generic);
      map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
 } else {
      String revision = Version.getVersion(interfaceClass, version);
      if (revision != null && revision.length() > 0) {
          map.put("revision", revision);
      }
      String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
      if (methods.length == 0) {
           logger.warn("NO method found in service interface " + interfaceClass.getName());
           map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
      } else {
           map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
      }
}

Step3:新增methods鍵值對,存放dubbo:service的所有方法名,多個方法名用,隔開,如果是泛化實現,填充genric=true,methods為”*”;
ServiceConfig#doExportUrlsFor1Protocol

if (!ConfigUtils.isEmpty(token)) {
      if (ConfigUtils.isDefault(token)) {
            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
       } else {
            map.put(Constants.TOKEN_KEY, token);
       }
}

Step4:根據是否開啟令牌機制,如果開啟,設定token鍵,值為靜態值或uuid。
ServiceConfig#doExportUrlsFor1Protocol

if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
       protocolConfig.setRegister(false);
       map.put("notify", "false");
}

Step5:如果協議為本地協議(injvm),則設定protocolConfig#register屬性為false,表示不向註冊中心註冊服務,在map中儲存鍵為notify,值為false,表示當註冊中心監聽到服務提供者傳送變化(服務提供者增加、服務提供者減少等事件時不通知。
ServiceConfig#doExportUrlsFor1Protocol

// export service
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
     contextPath = provider.getContextpath();
}

Step6:設定協議的contextPath,如果未配置,預設為/interfacename
ServiceConfig#doExportUrlsFor1Protocol

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

Step7:解析服務提供者的IP地址與埠。
服務IP地址解析順序:(序號越小越優先)
1)系統環境變數,變數名:DUBBO_DUBBO_IP_TO_BIND
2)系統屬性,變數名:DUBBO_DUBBO_IP_TO_BIND
3)系統環境變數,變數名:DUBBO_IP_TO_BIND
4)系統屬性,變數名:DUBBO_IP_TO_BIND
5)dubbo:protocol 標籤的host屬性 –》 dubbo:provider 標籤的host屬性
6)預設網絡卡IP地址,通過InetAddress.getLocalHost().getHostAddress()獲取,如果IP地址不符合要求,繼續下一個匹配。
判斷IP地址是否符合要求的標準是:

   public static boolean isInvalidLocalHost(String host) {
        return host == null
                || host.length() == 0
                || host.equalsIgnoreCase("localhost")
                || host.equals("0.0.0.0")
                || (LOCAL_IP_PATTERN.matcher(host).matches());
      }

7)選擇第一個可用網絡卡,其實現方式是建立socket,連線註冊中心,獲取socket的IP地址。其程式碼:

Socket socket = new Socket();
        try {
              SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
              socket.connect(addr, 1000);
              hostToBind = socket.getLocalAddress().getHostAddress();
              break;
         } finally {
              try {
                      socket.close();
              } catch (Throwable e) {
              }
        }

服務提供者埠解析順序:(序號越小越優先)
1)系統環境變數,變數名:DUBBO_DUBBO_PORT_TO_BIND
2)系統屬性,變數名:DUBBO_DUBBO_PORT_TO_BIND
3)系統環境變數,變數名:DUBBO_PORT_TO_BIND
4)系統屬性,變數名DUBBO_PORT_TO_BIND
5)dubbo:protocol標籤port屬性、dubbo:provider標籤的port屬性。
6)隨機選擇一個埠。
ServiceConfig#doExportUrlsFor1Protocol

URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

Step8:根據協議名稱、協議host、協議埠、contextPath、相關配置屬性(application、module、provider、protocolConfig、service及其子標籤)構建服務提供者URI。
URL執行效果圖:
這裡寫圖片描述
以dubbo協議為例,展示最終服務提供者的URL資訊如下:dubbo://192.168.56.1:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.56.1&bind.port=20880&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5916&qos.port=22222&side=provider&timestamp=1527168070857

ServiceConfig#doExportUrlsFor1Protocol

String scope = url.getParameter(Constants.SCOPE_KEY);
// don't export when none is configured
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
     // 介面暴露實現邏輯
}

Step9:獲取dubbo:service標籤的scope屬性,其可選值為none(不暴露)、local(本地)、remote(遠端),如果配置為none,則不暴露。預設為local。
ServiceConfig#doExportUrlsFor1Protocol

// export to local if the config is not remote (export to remote only when config is remote)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {     // @1
       exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {     // @2
        if (logger.isInfoEnabled()) {
              logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
         }
        if (registryURLs != null && !registryURLs.isEmpty()) {   // @3
              for (URL registryURL : registryURLs) {
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));  // @4
                    URL monitorUrl = loadMonitor(registryURL);       // @5
                    if (monitorUrl != null) {
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());  
                    }
                    if (logger.isInfoEnabled()) {
                        logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                    }
                   Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));  // @6
                   DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);    
                   Exporter<?> exporter = protocol.export(wrapperInvoker);    // @7
                   exporters.add(exporter);
               }
         } else {
               Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
               DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
               Exporter<?> exporter = protocol.export(wrapperInvoker);
               exporters.add(exporter);
         }
}

Step10:根據scope來暴露服務,如果scope不配置,則預設本地與遠端都會暴露,如果配置成local或remote,那就只能是二選一。
程式碼@1:如果scope不為remote,則先在本地暴露(injvm):,具體暴露服務的具體實現,將在remote 模式中詳細分析。
程式碼@2:如果scope不為local,則將服務暴露在遠端。
程式碼@3:remote方式,檢測當前配置的所有註冊中心,如果註冊中心不為空,則遍歷註冊中心,將服務依次在不同的註冊中心進行註冊。
程式碼@4:如果dubbo:service的dynamic屬性未配置, 嘗試取dubbo:registry的dynamic屬性,該屬性的作用是否啟用動態註冊,如果設定為false,服務註冊後,其狀態顯示為disable,需要人工啟用,當服務不可用時,也不會自動移除,同樣需要人工處理,此屬性不要在生產環境上配置。
程式碼@5:根據註冊中心url(註冊中心url),構建監控中心的URL,如果監控中心URL不為空,則在服務提供者URL上追加monitor,其值為監控中心url(已編碼)。
1)如果dubbo spring xml配置檔案中沒有配置監控中心(dubbo:monitor),如果從系統屬性-Ddubbo.monitor.address,-Ddubbo.monitor.protocol構建MonitorConfig物件,否則從dubbo的properties配置檔案中尋找這個兩個引數,如果沒有配置,則返回null。
2)如果有配置,則追加相關引數,dubbo:monitor標籤只有兩個屬性:address、protocol,其次會追加interface(MonitorService)、協議等。
程式碼@6:通過動態代理機制建立Invoker,dubbo的遠端呼叫實現類。
這裡寫圖片描述
Dubbo遠端呼叫器如何構建,這裡不詳細深入,重點關注WrapperInvoker的url為:registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo%3A%2F%2F192.168.56.1%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.56.1%26bind.port%3D20880%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D6328%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1527255510215&pid=6328&qos.port=22222&registry=zookeeper&timestamp=152725551020
2,這裡有兩個重點值得關注:
1)path屬性:com.alibaba.dubbo.registry.RegistryService,註冊中心也類似於服務提供者。
2)export屬性:值為服務提供者的URL,為什麼需要關注這個URL呢?請看程式碼@7,protocol屬性為Protocol$Adaptive,Dubbo在載入元件實現類時採用SPI(外掛機制,有關於外掛機制,在該專題後續文章將重點分析),在這裡我們只需要知道,根據URL冒號之前的協議名將會呼叫相應的方法。
這裡寫圖片描述
其對映關係(列出與服務啟動相關協議實現類):
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol //檔案位於dubbo-rpc-dubbo/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol //檔案位於dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
程式碼@7:根據程式碼@6的分析,將呼叫RegistryProtocol#export方法。

1.5、原始碼分析RegistryProtocol#export方法
呼叫鏈:ServiceBean#afterPropertiesSet——>ServiceConfig#export——>ServiceConfig#doExport——>ServiceConfig#doExportUrlsFor1Protocol——>RegistryProtocol#export
RegistryProtocol#export

@Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //export invoker
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);   // @1

        URL registryUrl = getRegistryUrl(originInvoker);       // @2

        //registry provider
        final Registry registry = getRegistry(originInvoker);                          // @3
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);     // @4start

        //to judge to delay publish whether or not
        boolean register = registedProviderUrl.getParameter("register", true);  

        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);

        if (register) {  
            register(registryUrl, registedProviderUrl);      // @4 end
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }

        // Subscribe the override data
        // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);          // @5 start
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);            // @5 end
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    }

程式碼@1:啟動服務提供者服務,監聽指定埠,準備服務消費者的請求,這裡其實就是從WrapperInvoker中的url(註冊中心url)中提取export屬性,描述服務提供者的url,然後啟動服務提供者。
這裡寫圖片描述
從上圖中,可以看出,將呼叫DubboProtocol#export完成dubbo服務的啟動,利用netty構建一個微型服務端,監聽埠,準備接受服務消費者的網路請求,本節旨在梳理其啟動流程,具體實現細節,將在後續章節中詳解,這裡我們只要知道,,會再此次監聽該埠,然後將dubbo:service的服務handler加入到命令處理器中,當有訊息消費者連線該埠時,通過網路解包,將需要呼叫的服務和引數等資訊解析處理後,轉交給對應的服務實現類處理即可。
程式碼@2:獲取真實註冊中心的URL,例如zookeeper註冊中心的URL:zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo%3A%2F%2F192.168.56.1%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.56.1%26bind.port%3D20880%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D10252%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1527263060882&pid=10252&qos.port=22222&timestamp=1527263060867
程式碼@3:根據註冊中心URL,從註冊中心工廠中獲取指定的註冊中心實現類:zookeeper註冊中心的實現類為:ZookeeperRegistry
程式碼@4:獲取服務提供者URL中的register屬性,如果為true,則呼叫註冊中心的ZookeeperRegistry#register方法向註冊中心註冊服務(實際由其父類FailbackRegistry實現)。
程式碼@5:服務提供者向註冊中心訂閱自己,主要是為了服務提供者URL傳送變化後重新暴露服務,當然,會將dubbo:reference的check屬性設定為false。

到這裡就對文章開頭提到的問題1,問題2做了一個解答,其與註冊中心的心跳機制等將在後續章節中詳細分析。
文字看起來可能不是很直觀,現整理一下Dubbo服務提供者啟動流程圖如下:
這裡寫圖片描述

本文重點梳理了Dubbo服務提供者啟動流程,其中Dubbo服務提供者在指定埠監聽服務的啟動流程將在下一節中詳細分析。