Dubbo原始碼筆記-服務註冊
今天來簡單做一下Dubbo服務註冊部分原始碼學習手記。
一、Dubbo配置解析
目前Dubbo最多的用法就是跟Spring整合,既然跟Spring整合,那麼,Dubbo物件的例項化都將交由Spring統一處理。而Dubbo配置,對於Spring來說其實就是自定標籤。這裡Dubbo自定義標籤解析類,在Dubbo配置模組(\dubbo-config\dubbo-config-spring\src\main\resources\META-INF/spring.handlers)進行了宣告:
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
1 public class DubboNamespaceHandler extends NamespaceHandlerSupport { 2 static { 3 Version.checkDuplicate(DubboNamespaceHandler.class); 4 } 5 @Override 6 public void init() { 7 registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true)); 8 registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true)); 9 registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true)); 10 registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true)); 11 registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true)); 12 registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true)); 13 registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true)); 14 registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true)); 15 registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true)); 16 registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false)); 17 registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser()); 18 } 19 }
對應一下Dubbo服務提供方的配置檔案:
<dubbo:application name="demo-provider"/> <dubbo:registry address="zookeeper://127.0.0.1:2181" /> <dubbo:protocol name="dubbo"/> <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/> <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
就是將這些標籤解析成ApplicationConfig...ServiceBean等等物件。而服務註冊部分最主要的橋樑就在於ServiceBean初始化解析完畢的時候。有Spring容器呼叫ServiceBean.afterPropertiesSet方法:
1 public void afterPropertiesSet() throws Exception { 2 // 此處省略配置檢查 3 export(); 4 }
二、配置檢查與預設填充
進入export方法中,首先會對ServiceBean進行釋出前的配置檢查與填充。
1 public synchronized void export() { 2 checkAndUpdateSubConfigs(); // 載入並更新配置資訊到Bean物件中,並檢查 3 }
這裡會有一系列的配置校驗與配置填充的邏輯:
1 public void checkAndUpdateSubConfigs() { 2 checkDefault(); 3 if (provider != null) { 4 inheritIfAbsentFromProvider(); 5 } 6 if (module != null) { 7 inheritIfAbsentFromModule(); 8 } 9 if (application != null) { 10 inheritIfAbsentFromApplication(); 11 } 12 checkApplication(); 13 checkRegistry(); 14 checkProtocol(); 15 this.refresh(); 16 checkMetadataReport(); 17 checkRegistryDataConfig(); 18 try { 19 interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader()); 20 } catch (ClassNotFoundException e) { 21 throw new IllegalStateException(e.getMessage(), e); 22 } 23 checkInterfaceAndMethods(interfaceClass, methods); 24 checkRef(); 25 checkStubAndLocal(interfaceClass); 26 checkMock(interfaceClass); 27 }
這裡只列了部分配置檢查與填充。總體配置的區分是否配置中心優先,優先順序為:
1 public void refresh() { 2 // getPrefix為對應配置類的字首,ProviderConfig->Provider, ServiceBean->Service, 3 // getId為beanId, 4 CompositeConfiguration compositeConfiguration = Environment.getInstance().getConfiguration(getPrefix(), getId()); 5 InmemoryConfiguration config = new InmemoryConfiguration(getPrefix(), getId()); 6 config.addProperties(getMetaData()); // Bean物件的初始值 7 if (Environment.getInstance().isConfigCenterFirst()) { 8 // -D系統屬性 > 配置中心應用配置 > 配置中心全域性配置 > Bean物件的初始值 > 屬性檔案中的資訊 9 compositeConfiguration.addConfiguration(3, config); 10 } else { 11 // -D > Bean物件的初始值 > 配置中心應用配置 > 配置中心全域性配置 > 屬性檔案中的資訊 12 compositeConfiguration.addConfiguration(1, config); 13 } 14 }
三、獲取和構建註冊中心統一資源定位器
1 private void doExportUrls() { 2 // 獲取註冊中心統一資源列表,如果註冊中心沒有初始化,則先初始化 3 List<URL> registryURLs = loadRegistries(true); 4 // 將目標服務所有協議模式註冊到每一個註冊中心上 5 for (ProtocolConfig protocolConfig : protocols) { 6 doExportUrlsFor1Protocol(protocolConfig, registryURLs); 7 } 8 }
四、服務啟動與服務註冊
在doExportUrlsForProtocal方法裡邊其實就做了以下幾件事:
1 private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { 2 Map map = builderUrl(); 3 // 代表一個服務 4 URL url = new URL(name, host, port, (StringUtils.isEmpty(contextPath) ? "" : contextPath + "/") + path, map); 5 // 通過代理工廠將ref物件轉化成invoker物件 6 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); 7 //代理invoker物件 8 DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); 9 // 使用配置協議啟動、暴露服務 10 Exporter<?> exporter = protocol.export(wrapperInvoker); 11 //一個服務可能有多個提供者,儲存在一起 12 exporters.add(exporter); 13 }
這裡,我們著重看第10行服務暴露部分,這裡呼叫export方法,如果是Dubbo協議的話,分別列舉一下DubboProtocal、HttpProtocol的export:
1 public class DubboProtocol extends AbstractProtocol { 2 public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { 3 URL url = invoker.getUrl(); 4 //忽略若干程式碼 5 //開啟服務 6 openServer(url); 7 optimizeSerialization(url); 8 return exporter; 9 } 10 }
1 public class HttpProtocol extends AbstractProxyProtocol { 2 protected <T> Runnable doExport(final T impl, Class<T> type, URL url) throws RpcException { 3 String addr = getAddr(url); 4 HttpServer server = serverMap.get(addr); 5 server = httpBinder.bind(url, new InternalHandler()); 6 } 7 }
其實這塊就是,根據不同協議分別做了服務啟動,如果是Dubbo協議則啟動NettyServer,如果是Http協議則啟動一個Tomcat。
那麼現在服務啟動了,但是在哪裡註冊了呢?其實我們要關注這個protocol物件的構建:
1 /** 2 * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios. 3 * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}. 4 * For example: 5 * 6 * <li>when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample, 7 * then the protocol is <b>RegistryProtocol</b></li> 8 * 9 * <li>when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then 10 * the protocol is <b>DubboProtocol</b></li> 11 * <p> 12 * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two 13 * layers, and eventually will get a <b>ProtocolFilterWrapper</b> or <b>ProtocolListenerWrapper</b> 14 */ 15 private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
這段原始碼的意思是,在獲取Protocol例項的時候,Dubbo框架自動給包裝了兩個切面,其實服務註冊就是在這個切面裡邊完成的:
1 public class ProtocolListenerWrapper implements Protocol { 2 private final Protocol protocol; 3 @Override 4 public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { 5 //如果是registerProtocol,則呼叫RegisterProtocol.export方法 6 if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { 7 return protocol.export(invoker); 8 } 9 return new ListenerExporterWrapper<T>(protocol.export(invoker), 10 Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class) 11 .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY))); 12 } 13 }
再看看RegisterProtocol的export:
1 public class RegistryProtocol implements Protocol { 2 @Override 3 public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { 4 register(registryUrl, registeredProviderUrl); // 服務註冊 5 } 6 }
如果註冊中心是ZK的話其實就是給ZK寫資料:
1 @Override 2 public void register(URL url) { 3 //忽略很多程式碼 4 doRegister(url); 5 //忽略很多程式碼 6 } 7 protected void doRegister(URL url) { 8 try { 9 zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true)); 10 } catch (Throwable e) { 11 throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); 12 } 13 }
相關文章:https://www.jianshu.com/p/7f3871492c71
&n