本文接著深入分析服務引用的核心流程。
Dubbo 支持兩種方式引用遠程的服務:
1、服務直連的方式,僅適合在調試服務的時候使用。
2、基于注冊中心引用服務,這是生產環境中使用的服務引用方式。
在介紹服務發布的時候,介紹了 DubboBootstrap.start() 方法的核心流程,其中除了會調用 exportServices() 方法完成服務發布之外,還會調用 referServices() 方法完成服務引用。
在 DubboBootstrap.referServices() 方法中,會從 ConfigManager 中獲取所有 ReferenceConfig 列表,并根據 ReferenceConfig 獲取對應的代理對象,入口邏輯如下:
public class DubboBootstrap extends GenericEventListener { private final ConfigManager configManager; private ReferenceConfigCache cache; private final ExecutorRepository executorRepository = getExtensionLoader(ExecutorRepository.class).getDefaultExtension(); private volatile boolean referAsync; private List<CompletableFuture> asyncReferringFutures = new ArrayList<>(); private void referServices() { if (cache == null) { // 初始ReferenceConfigCache cache = ReferenceConfigCache.getCache(); } // 遍歷ReferenceConfig列表 configManager.getReferences().forEach(rc -> { ReferenceConfig referenceConfig = (ReferenceConfig) rc; referenceConfig.setBootstrap(this); // 檢測ReferenceConfig是否已經初始化 if (rc.shouldInit()) { // 異步 if (referAsync) { CompletableFuturefuture = ScheduledCompletableFuture.submit( executorRepository.getServiceExporterExecutor(), () -> cache.get(rc) ); asyncReferringFutures.add(future); } else { // 同步 cache.get(rc); } } }); } }
Dubbo 服務引用的時機有兩個,第一個是在 Spring 容器調用 ReferenceBean 的 afterPropertiesSet 方法時引用服務,第二個是在 ReferenceBean 對應的服務被注入到其他類中時引用。這兩個引用服務的時機區別在于,第一個是餓漢式的,第二個是懶漢式的。默認情況下,Dubbo 使用懶漢式引用服務。
新建的 ReferenceConfig 對象會通過 DubboBootstrap.reference() 方法添加到 ConfigManager 中進行管理,如下所示:
public class DubboBootstrap extends GenericEventListener { private final ConfigManager configManager; public DubboBootstrap reference(ReferenceConfig referenceConfig) { configManager.addReference(referenceConfig); return this; } }
服務引用的核心實現在 ReferenceConfig 之中,一個 ReferenceConfig 對象對應一個服務接口,每個 ReferenceConfig 對象中都封裝了與注冊中心的網絡連接,以及與 Provider 的網絡連接,這是一個非常重要的對象。
為了避免底層連接泄漏造成性能問題,從 Dubbo 2.4.0 版本開始,Dubbo 提供了 ReferenceConfigCache 用于緩存 ReferenceConfig 實例。
在 dubbo-demo-api-consumer 示例中,我們可以看到 ReferenceConfigCache 的基本使用方式:
ReferenceConfigreference = new ReferenceConfig<>(); reference.setInterface(DemoService.class); ... // 這一步在DubboBootstrap.start()方法中完成 ReferenceConfigCache cache = ReferenceConfigCache.getCache(); ... DemoService demoService = ReferenceConfigCache.getCache().get(reference);
在 ReferenceConfigCache 中維護了一個靜態的 Map(CACHE_HOLDER)字段,其中 Key 是由 Group、服務接口和 version 構成,Value 是一個 ReferenceConfigCache 對象。在 ReferenceConfigCache 中可以傳入一個 KeyGenerator 用來修改緩存 Key 的生成邏輯,KeyGenerator 接口的定義如下:
public class ReferenceConfigCache { public interface KeyGenerator { String generateKey(ReferenceConfigBase referenceConfig); } }
默認的 KeyGenerator 實現是 ReferenceConfigCache 中的匿名內部類,其對象由 DEFAULT_KEY_GENERATOR 這個靜態字段引用,具體實現如下:
public class ReferenceConfigCache { public static final String DEFAULT_NAME = "_DEFAULT_"; public static final KeyGenerator DEFAULT_KEY_GENERATOR = referenceConfig -> { String iName = referenceConfig.getInterface(); if (StringUtils.isBlank(iName)) { // 獲取服務接口名稱 Class clazz = referenceConfig.getInterfaceClass(); iName = clazz.getName(); } if (StringUtils.isBlank(iName)) { throw new IllegalArgumentException("No interface info in ReferenceConfig" + referenceConfig); } // Key的格式是group/interface:version StringBuilder ret = new StringBuilder(); if (!StringUtils.isBlank(referenceConfig.getGroup())) { ret.append(referenceConfig.getGroup()).append("/"); } ret.append(iName); if (!StringUtils.isBlank(referenceConfig.getVersion())) { ret.append(":").append(referenceConfig.getVersion()); } return ret.toString(); }; }
在 ReferenceConfigCache 實例對象中,會維護下面兩個 Map 集合:
proxies(ConcurrentMap<Class, ConcurrentMap
referredReferences(ConcurrentMap<String, ReferenceConfigBase> 類型):該集合用來存儲已經被處理的 ReferenceConfig 對象。
回到 DubboBootstrap.referServices() 方法中,看一下其中與 ReferenceConfigCache 相關的邏輯。
首先是 ReferenceConfigCache.getCache() 這個靜態方法,會在 CACHE_HOLDER 集合中添加一個 Key 為“DEFAULT”的 ReferenceConfigCache 對象(使用默認的 KeyGenerator 實現),它將作為默認的 ReferenceConfigCache 對象。
接下來,無論是同步服務引用還是異步服務引用,都會調用 ReferenceConfigCache.get() 方法,創建并緩存代理對象。下面就是 ReferenceConfigCache.get() 方法的核心實現:
public class ReferenceConfigCache { publicT get(ReferenceConfigBasereferenceConfig) { // 生成服務提供方對應的Key String key = generator.generateKey(referenceConfig); // 獲取接口類型 Class type = referenceConfig.getInterfaceClass(); // 獲取該接口對應代理對象集合 proxies.computeIfAbsent(type, _t -> new ConcurrentHashMap<>()); ConcurrentMapproxiesOfType = proxies.get(type); // 根據Key獲取服務提供方對應的代理對象 proxiesOfType.computeIfAbsent(key, _k -> { // 服務引用 Object proxy = referenceConfig.get(); // 將ReferenceConfig記錄到referredReferences集合 referredReferences.put(key, referenceConfig); return proxy; }); return (T) proxiesOfType.get(key); } }
通過前面的介紹知道,ReferenceConfig 是服務引用的真正入口,其中會創建相關的代理對象。下面先來看 ReferenceConfig.get() 方法:
public class ReferenceConfigextends ReferenceConfigBase{ private transient volatile T ref; public synchronized T get() { // 檢測當前ReferenceConfig狀態 if (destroyed) { throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!"); } // 檢測 ref 是否為空,為空則通過 init 方法創建 if (ref == null) {// ref指向了服務的代理對象 // 啟動初始化操作 init 方法主要用于處理配置,以及調用 createProxy 生成代理類 init(); } return ref; } }
在 ReferenceConfig.init() 方法中,首先會對服務引用的配置進行處理,以保證配置的正確性。
ReferenceConfig.init() 方法的核心邏輯是調用 createProxy() 方法,調用之前會從配置中獲取 createProxy() 方法需要的參數:
public class ReferenceConfigextends ReferenceConfigBase{ private transient volatile T ref; private transient volatile boolean initialized; private DubboBootstrap bootstrap; public synchronized void init() { //避免重復加載 if (initialized) { return; } //獲取Dubbo核心容器 if (bootstrap == null) { bootstrap = DubboBootstrap.getInstance(); //進行Dubbo核心配置的加載和檢查 bootstrap.initialize(); } //在對象創建后在使用其他配置模塊配置對象之前檢查對象配置并重寫默認配置 checkAndUpdateSubConfigs(); //檢查并生成sub配置和Local配置是否合法 checkStubAndLocal(interfaceClass); //判斷對象是否有mock并生成mock信息 ConfigValidationUtils.checkMock(interfaceClass, this); //保存對象屬性map信息 Mapmap = new HashMap(); map.put(SIDE_KEY, CONSUMER_SIDE); //添加版本信息,包含dubbo版本,release版本,timestamp運行時間戳和sid_key等信息 ReferenceConfigBase.appendRuntimeParameters(map); //添加泛型 revision信息 if (!ProtocolUtils.isGeneric(generic)) { String revision = Version.getVersion(interfaceClass, version); if (revision != null && revision.length() > 0) { map.put(REVISION_KEY, revision); } //生成服務的代理對象,跟服務導出是一樣,通過代理對象來代理,返回代理方法 String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); if (methods.length == 0) { logger.warn("No method found in service interface " + interfaceClass.getName()); map.put(METHODS_KEY, ANY_VALUE); } else { //添加需要代理的方法 map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), COMMA_SEPARATOR)); } } //添加interface名 map.put(INTERFACE_KEY, interfaceName); //添加重試信息 AbstractConfig.appendParameters(map, getMetrics()); //檢查獲取并添加Application信息 AbstractConfig.appendParameters(map, getApplication()); //檢查獲取并添加Module信息 AbstractConfig.appendParameters(map, getModule()); // remove 'default.' prefix for configs from ConsumerConfig // appendParameters(map, consumer, Constants.DEFAULT_KEY); //檢查獲取并添加consumer信息 AbstractConfig.appendParameters(map, consumer); AbstractConfig.appendParameters(map, this); MetadataReportConfig metadataReportConfig = getMetadataReportConfig(); if (metadataReportConfig != null && metadataReportConfig.isValid()) { map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE); } //設置方法重試信息并收集方法異步調用信息 Mapattributes = null; if (CollectionUtils.isNotEmpty(getMethods())) { attributes = new HashMap<>(); for (MethodConfig methodConfig : getMethods()) { AbstractConfig.appendParameters(map, methodConfig, methodConfig.getName()); String retryKey = methodConfig.getName() + ".retry"; if (map.containsKey(retryKey)) { String retryValue = map.remove(retryKey); if ("false".equals(retryValue)) { map.put(methodConfig.getName() + ".retries", "0"); } } AsyncMethodInfo asyncMethodInfo = AbstractConfig.convertMethodConfig2AsyncInfo(methodConfig); if (asyncMethodInfo != null) { // consumerModel.getMethodModel(methodConfig.getName()).addAttribute(ASYNC_KEY, asyncMethodInfo); attributes.put(methodConfig.getName(), asyncMethodInfo); } } } //獲取服務消費者 ip 地址 String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY); if (StringUtils.isEmpty(hostToRegistry)) { hostToRegistry = NetUtils.getLocalHost(); } else if (isInvalidLocalHost(hostToRegistry)) { throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry); } //添加服務注冊信息 map.put(REGISTER_IP_KEY, hostToRegistry); //將配置保存如服務元信息中 serviceMetadata.getAttachments().putAll(map); //創建代理 ref = createProxy(map); serviceMetadata.setTarget(ref); serviceMetadata.addAttribute(PROXY_CLASS_REF, ref); // 根據服務名,ReferenceConfig,代理類構建 ConsumerModel, // 并將 ConsumerModel 存入到 ApplicationModel 中 ConsumerModel consumerModel = repository.lookupReferredService(serviceMetadata.getServiceKey()); consumerModel.setProxyObject(ref); consumerModel.init(attributes); initialized = true; //檢查引入的服務是否可用 checkInvokerAvailable(); // dispatch a ReferenceConfigInitializedEvent since 2.7.4 dispatch(new ReferenceConfigInitializedEvent(this, invoker)); } }
ReferenceConfig.createProxy() 方法中處理了多種服務引用的場景,例如,直連單個/多個Provider、單個/多個注冊中心。下面是 createProxy() 方法的核心流程,大致可以梳理出這么 5 個步驟:
1、根據傳入的參數集合判斷協議是否為 injvm 協議,如果是,直接通過 InjvmProtocol 引用服務。
2、構造 urls 集合。Dubbo 支持直連 Provider和依賴注冊中心兩種服務引用方式。如果是直連服務的模式,我們可以通過 url 參數指定一個或者多個 Provider 地址,會被解析并填充到 urls 集合;如果通過注冊中心的方式進行服務引用,則會調用 AbstractInterfaceConfig.loadRegistries() 方法加載所有注冊中心。
3、如果 urls 集合中只記錄了一個 URL,通過 Protocol 適配器選擇合適的 Protocol 擴展實現創建 Invoker 對象。如果是直連 Provider 的場景,則 URL 為 dubbo 協議,這里就會使用 DubboProtocol 這個實現;如果依賴注冊中心,則使用 RegistryProtocol 這個實現。
4、如果 urls 集合中有多個注冊中心,則使用 ZoneAwareCluster 作為 Cluster 的默認實現,生成對應的 Invoker 對象;如果 urls 集合中記錄的是多個直連服務的地址,則使用 Cluster 適配器選擇合適的擴展實現生成 Invoker 對象。
5、通過 ProxyFactory 適配器選擇合適的 ProxyFactory 擴展實現,將 Invoker 包裝成服務接口的代理對象。
通過上面的流程我們可以看出createProxy() 方法中有兩個核心:
1、通過 Protocol 適配器選擇合適的 Protocol 擴展實現創建 Invoker 對象。
2、通過 ProxyFactory 適配器選擇合適的 ProxyFactory 創建代理對象。
下面我們來看 createProxy() 方法的具體實現:
public class ReferenceConfigextends ReferenceConfigBase{ private T createProxy(Mapmap) { //jvm本地引入 // 根據url的協議、scope以及injvm等參數檢測是否需要本地引用 if (shouldJvmRefer(map)) { // 創建injvm協議的URL URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map); // 本地引用invoker生成 // 通過Protocol的適配器選擇對應的Protocol實現創建Invoker對象 invoker = REF_PROTOCOL.refer(interfaceClass, url); if (logger.isInfoEnabled()) { logger.info("Using injvm service " + interfaceClass.getName()); } } else { urls.clear(); // 用戶配置url信息,表明用戶可能想進行點對點調用 if (url != null && url.length() > 0) { // 當需要配置多個 url 時,可用分號進行分割,這里會進行切分 String[] us = SEMICOLON_SPLIT_PATTERN.split(url); if (us != null && us.length > 0) { for (String u : us) { URL url = URL.valueOf(u); if (StringUtils.isEmpty(url.getPath())) { // 設置接口全限定名為 url 路徑 url = url.setPath(interfaceName); } // 檢測 url 協議是否為 registry,若是,表明用戶想使用指定的注冊中心 if (UrlUtils.isRegistry(url)) { // 將 map 轉換為查詢字符串,并作為 refer 參數的值添加到 url 中 urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map))); } else { // 合并 url,移除服務提供者的一些配置(這些配置來源于用戶配置的 url 屬性), // 比如線程池相關配置。并保留服務提供者的部分配置,比如版本,group,時間戳等 // 最后將合并后的配置設置為 url 查詢字符串中。 urls.add(ClusterUtils.mergeUrl(url, map)); } } } } else { // assemble URL from register center's configuration // 從注冊中心的配置中組裝url信息 // if protocols not injvm checkRegistry // 如果協議不是在jvm本地中 if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) { //檢查注冊中心是否存在(如果當前配置不存在則獲取服務默認配置),然后將他們轉換到RegistryConfig中 checkRegistry(); //通過注冊中心配置信息組裝URL Listus = ConfigValidationUtils.loadRegistries(this, false); if (CollectionUtils.isNotEmpty(us)) { for (URL u : us) { //添加monitor監控信息 URL monitorUrl = ConfigValidationUtils.loadMonitor(this, u); if (monitorUrl != null) { map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString())); } // 將map中的參數整理成refer參數,添加到RegistryURL中 urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map))); } } // 既不是服務直連,也沒有配置注冊中心,拋出異常 if (urls.isEmpty()) { throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please configto your spring config."); } } } //單個注冊中心或服務提供者(服務直連,下同) if (urls.size() == 1) { // 調用 RegistryProtocol 的 refer 構建 Invoker 實例 // 在單注冊中心或是直連單個服務提供方的時候,通過Protocol的適配器選擇對應的Protocol實現創建Invoker對象 invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)); } else { //多個注冊中心或多個服務提供者,或者兩者混合 // 多注冊中心或是直連多個服務提供方的時候,會根據每個URL創建Invoker對象 List<Invoker> invokers = new ArrayList<Invoker>(); URL registryURL = null; // 獲取所有的 Invoker for (URL url : urls) { invokers.add(REF_PROTOCOL.refer(interfaceClass, url)); if (UrlUtils.isRegistry(url)) {// 確定是多注冊中心,還是直連多個Provider // 保存使用注冊中心的最新的URL信息 registryURL = url; // use last registry url } } // 注冊中心URL存在 if (registryURL != null) { // registry url is available // for multi-subscription scenario, use 'zone-aware' policy by default // 多注冊中心的場景中,會使用ZoneAwareCluster作為Cluster默認實現,多注冊中心之間的選擇 // 對于對區域訂閱方案,默認使用"zone-aware"區域 String cluster = registryURL.getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME); // The invoker wrap sequence would be: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker // invoker 包裝順序: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers)); } else { // not a registry url, must be direct invoke. // 如果不存在注冊中心連接,只能使用直連 //如果訂閱區域未設置,則設置為默認區域"zone-aware" String cluster = CollectionUtils.isNotEmpty(invokers) ? (invokers.get(0).getUrl() != null ? invokers.get(0).getUrl().getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME) : Cluster.DEFAULT) : Cluster.DEFAULT; // 創建 StaticDirectory 實例,并由 Cluster 對多個 Invoker 進行合并 invoker = Cluster.getCluster(cluster).join(new StaticDirectory(invokers)); } } } if (logger.isInfoEnabled()) { logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl()); } URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map); MetadataUtils.publishServiceDefinition(consumerURL); // 通過ProxyFactory適配器選擇合適的ProxyFactory擴展實現,創建代理對象 return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic)); } }
在直連 Provider 的場景中,會使用 DubboProtocol.refer() 方法完成服務引用,DubboProtocol.refer() 方法的具體實現在之前已經詳細介紹過了,這里我們重點來看存在注冊中心的場景中,Dubbo Consumer 是如何通過 RegistryProtocol 完成服務引用的。
在 RegistryProtocol.refer() 方法中,會先根據 URL 獲取注冊中心的 URL,再調用 doRefer 方法生成 Invoker,在 refer() 方法中會使用 MergeableCluster 處理多 group 引用的場景。
public class RegistryProtocol implements Protocol { @Override @SuppressWarnings("unchecked") publicInvokerrefer(Classtype, URL url) throws RpcException { // 從URL中獲取注冊中心的URL url = getRegistryUrl(url); // 獲取Registry實例,這里的RegistryFactory對象是通過Dubbo SPI的自動裝載機制注入的 Registry registry = registryFactory.getRegistry(url); if (RegistryService.class.equals(type)) { return proxyFactory.getInvoker((T) registry, type, url); } // 從注冊中心URL的refer參數中獲取此次服務引用的一些參數,其中就包括group Mapqs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY)); String group = qs.get(GROUP_KEY); if (group != null && group.length() > 0) { if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) { // 如果此次可以引用多個group的服務,則Cluser實現使用MergeableCluster實現, // 這里的getMergeableCluster()方法就會通過Dubbo SPI方式找到MergeableCluster實例 return doRefer(Cluster.getCluster(MergeableCluster.NAME), registry, type, url); } } Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY)); // 如果沒有group參數或是只指定了一個group,則通過Cluster適配器選擇Cluster實現 return doRefer(cluster, registry, type, url); } }
在 doRefer() 方法中,首先會根據 URL 初始化 RegistryDirectory 實例,然后生成 Subscribe URL 并進行注冊,之后會通過 Registry 訂閱服務,最后通過 Cluster 將多個 Invoker 合并成一個 Invoker 返回給上層,具體實現如下:
public class RegistryProtocol implements Protocol { protectedInvokerdoRefer(Cluster cluster, Registry registry, Classtype, URL url) { return interceptInvoker(getInvoker(cluster, registry, type, url), url); } protectedClusterInvokergetInvoker(Cluster cluster, Registry registry, Classtype, URL url) { // 創建RegistryDirectory實例 DynamicDirectorydirectory = createDirectory(type, url); directory.setRegistry(registry); directory.setProtocol(protocol); // 生成SubscribeUrl,協議為consumer,具體的參數是RegistryURL中refer參數指定的參數 Mapparameters = new HashMap(directory.getConsumerUrl().getParameters()); URL urlToRegistry = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters); if (directory.isShouldRegister()) { // 在SubscribeUrl中添加category=consumers和check=false參數 directory.setRegisteredConsumerUrl(urlToRegistry); // 服務注冊,在Zookeeper的consumers節點下,添加該Consumer對應的節點 registry.register(directory.getRegisteredConsumerUrl()); } // 根據SubscribeUrl創建服務路由 directory.buildRouterChain(urlToRegistry); // 訂閱服務,toSubscribeUrl()方法會將SubscribeUrl中category參數修改為"providers,configurators,routers" // RegistryDirectory的subscribe()在前面詳細分析過了,其中會通過Registry訂閱服務,同時還會添加相應的監聽器 directory.subscribe(toSubscribeUrl(urlToRegistry)); // 注冊中心中可能包含多個Provider,相應地,也就有多個Invoker, // 這里通過前面選擇的Cluster將多個Invoker對象封裝成一個Invoker對象 return (ClusterInvoker) cluster.join(directory); } protectedInvokerinterceptInvoker(ClusterInvokerinvoker, URL url) { // 根據URL中的registry.protocol.listener參數加載相應的監聽器實現 Listlisteners = findRegistryProtocolListeners(url); if (CollectionUtils.isEmpty(listeners)) { return invoker; } //引入了RegistryProtocol偵聽器,以使用戶有機會自定義或更改導出并引用RegistryProtocol的行為。 // 例如:在滿足某些條件時立即重新導出或重新引用。 for (RegistryProtocolListener listener : listeners) { listener.onRefer(this, invoker); } return invoker; } }
本文重點介紹了 Dubbo 服務引用的整個流程:
首先,我們介紹了 DubboBootStrap 這個入口門面類與服務引用相關的方法,其中涉及 referServices()、reference() 等核心方法。
接下來,我們分析了 ReferenceConfigCache 這個 ReferenceConfig 對象緩存,以及 ReferenceConfig 實現服務引用的核心流程。
最后,我們還講解了 RegistryProtocol 從注冊中心引用服務的核心實現。
本文來源:IT精英團--Dubbo | 詳解服務引用流程
本文地址:http://www.njgybxg.com/news/166617756365142.html
版權聲明:本文采用[BY-NC-SA]協議進行授權,如無特別說明,轉載請注明本文地址!
HDU1166:敵兵布陣(樹狀數組),C國的死對頭A國這段時間正在進行軍事演習,所以C國間諜頭子Derek和他手下Tidy又開始忙乎了。A國在海岸線沿直線布置了N個工兵營地,Derek和Tidy的任務就是要監視這些工兵營地的活動情況。由于采取了某種先進的監測手段,所以每個工兵營地的人數C國都掌握的一清二楚,每個工兵營地的人數都有可能發生變動,可能增加或減少若干人手,但這些都逃不過C國的監視。 中央情報局要研究敵人究竟
Dubbo——服務發布全流程,前言本文就先來重點關注Provider節點發布服務的過程,從DubboBootstrap這個入口類開始介紹,分析ProviderURL的組裝以及服務發布流程,其中會詳細介紹本地發布和遠程發布的核心流程。DubboBootstrap入口整個Provider節點的啟動入口是DubboBootstrap.start()方法,在該方法中會執行一些初始化操作,以及一些狀態控制字段的更新