本節將詳細分析Dubbo服務提供者的啓動流程,請帶着以下幾個疑問進行本節的閱讀,由於這幾個問題將是接下來幾篇文章分析的重點內容。spring
從上文中咱們得知,服務提供者啓動的核心入口爲ServiceBean,本節將從源碼級別詳細剖析ServcieBean的實現原理,即Dubbo服務提供者的啓動流程,ServiceBean的繼承層次如圖所示,dubbo:service標籤的全部屬性都被封裝在此類圖結構中。 api
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< RegistryConfig> registries屬性,爲註冊中心集合。 Step5:嘗試從BeanFacotry中加載一個監控中心,填充ServiceBean的MonitorConfig monitor屬性,若是存在多個dubbo:monitor配置,則拋出"Duplicate monitor configs: "。 Step6:嘗試從BeanFactory中加載全部的協議,注意:ServiceBean的List< ProtocolConfig> protocols是一個集合,也即一個服務能夠經過多種協議暴露給消費者。app
ServiceBean#afterPropertiesSetdom
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)。jvm
ServiceBean#afterPropertiesSetsocket
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。
調用鏈: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事件機制觸發)。
調用鏈: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方法,填充默認屬性,其具體加載順序:
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,<dubbo:service 的stub屬性,能夠設置爲true,此時stub的類名爲:interface+stub,stub也能夠指定自定義的全類名。本地存根說明如圖所示(dubbo官方文檔) ![這裏寫圖片描述](https: user-gold-cdn.xitu.io 2019 12 17 16f13ef443c96f79?w="924&h=755&f=png&s=187936)" serviceconfig#doexport ``` 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 checkstubandmock(interfaceclass); step6:校驗stub、mock類的合理性,是不是interface的實現類。 doexporturls(); step7:執行doexporturls()方法暴露服務,接下來會重點分析該方法。 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](https://my.oschina.net/u/1198) for (ProtocolConfig protocolConfig : protocols) { doExportUrlsFor1Protocol(protocolConfig, registryURLs); // @2 } }
代碼@1:首先遍歷ServiceBean的List< RegistryConfig> 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®istry=zookeeper×tamp=1527308268041 代碼@2:而後遍歷配置的全部協議,根據每一個協議,向註冊中心暴露服務,接下來重點分析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地址解析順序:(序號越小越優先)
判斷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()); }
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) { } }
服務提供者端口解析順序:(序號越小越優先)
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運行效果圖:
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(已編碼)。
代碼@6:經過動態代理機制建立Invoker,dubbo的遠程調用實現類。
其映射關係(列出與服務啓動相關協議實現類): 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方法。
調用鏈: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:protocol name="dubbo" port="20880" />,會再這次監聽該端口,而後將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×tamp=1527263060867 代碼@3:根據註冊中心URL,從註冊中心工廠中獲取指定的註冊中心實現類:zookeeper註冊中心的實現類爲:ZookeeperRegistry 代碼@4:獲取服務提供者URL中的register屬性,若是爲true,則調用註冊中心的ZookeeperRegistry#register方法向註冊中心註冊服務(實際由其父類FailbackRegistry實現)。 代碼@5:服務提供者向註冊中心訂閱本身,主要是爲了服務提供者URL發送變化後從新暴露服務,固然,會將dubbo:reference的check屬性設置爲false。
到這裏就對文章開頭提到的問題1,問題2作了一個解答,其與註冊中心的心跳機制等將在後續章節中詳細分析。
文字看起來可能不是很直觀,現整理一下Dubbo服務提供者啓動流程圖以下:
本文重點梳理了Dubbo服務提供者啓動流程,其中Dubbo服務提供者在指定端口監聽服務的啓動流程將在下一節中詳細分析。
做者介紹:丁威,《RocketMQ技術內幕》做者,RocketMQ 社區佈道師,公衆號:中間件興趣圈 維護者,目前已陸續發表源碼分析Java集合、Java 併發包(JUC)、Netty、Mycat、Dubbo、RocketMQ、Mybatis等源碼專欄。能夠點擊連接:中間件知識星球,一塊兒探討高併發、分佈式服務架構,交流源碼。
</t></t></t></t></t></string></argumentconfig></string,></string,></url></dubbo:service></string,></string,>