1. 程式人生 > >Dubbo原始碼筆記-服務註冊

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