
Dubbo是一款典型的高擴展、高性能、高可用的RPC微服務框架,用于解決微服務架構下的服務治理與通信問題。其核心模塊包含【RPC通信】和【服務治理】,其中服務治理又分為服務注冊與發現、服務容錯、負載均衡、流量調度等。今天將重點介紹Dubbo的服務注冊與發現。
(資料圖片)
在介紹服務注冊發現之前,先簡單介紹一下貫穿整個Dubbo源碼,也是Dubbo實現自適應擴展的核心--SPI機制,下圖為Dubbo SPI實現的簡單類圖。
?
??
?1、Dubbo SPI原理:通過讀取相應的配置文件找到具體實現類,然后通過以下兩種方式實例化對象:(1)通過自適應的動態字節碼編譯技術,生成相應的動態代理類,(2)利用反射機制實現實例化。相較于Java SPI,Dubbo SPI實現了內部的IoC和Aop
?2、Dubbo SPI 優點:(1)高擴展:用戶可以根據實際業務需求擴展相應的實現模塊,包含字節碼編譯技術、rpc協議、通信方式、注冊方式等,(2)解耦:通過封裝SPI調用機制,架構上實現了上層應用與底層邏輯之間的解耦,為高擴展提供了支撐條件
?3、Dubbo SPI 常用樣例(以getExtension和getAdaptiveExtension為例)
配置文件內容test1=com.dubbo.demo.service.TestServiceimpltest2=com.dubbo.demo.service.TestServiceImpl2一、通過getExtension方法生成實例 ExtensionLoaderextensionLoader = ExtensionLoader.getExtensionLoader(TestService.class); TestService t1 = extensionLoader.getExtension("test1"); TestService t2 = extensionLoader.getExtension("test2"); 二、通過getAdaptiveExtension生成實例(方法中需要@Adaptive注解,參數會對URL校驗) TestService testService = ExtensionLoader.getExtensionLoader(TestService.class).getAdaptiveExtension(); URL url = new URL("test", "localhost", 8080, new String[]{"test.service", "test1"}); testService.sayHello("bbb", url);
調用getAdaptiveExtension方法最終會生成相應的代理類,最終生成的代理類會根據URL參數里面的protocol決定(以內部Protocol為例)
?
??
?
?
??
?
??
首先需要實例化ServiceConfig實例,聲明“注冊接口、接口實例、注冊中心配置”,其中“ServiceBean”是實現Spring與Dubbo整合的橋梁。然后會由DubboBootstrap調用initialize方法實現configManager和Environment的初始化,其中就包括將ServiceConfig中的配置轉換成內部封裝的協議(ApplicationModel、ProviderModel等)
private static void startWithExport() throws InterruptedException { //初始化配置 ServiceConfigservice = new ServiceConfig<>(); service.setInterface(DemoService.class); service.setRef(new DemoServiceImpl()); service.setApplication(new ApplicationConfig("dubbo-demo-api-provider")); service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181")); //服務注冊入口 service.export();}
public synchronized void export() { if (bootstrap == null) { bootstrap = DubboBootstrap.getInstance(); // compatible with api call. if (null != this.getRegistry()) { bootstrap.registries(this.getRegistries()); } //初始化配置() bootstrap.initialize(); } ...... if (shouldDelay()) { DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); } else { //服務注冊 doExport(); } exported(); }
根據初始化配置組轉注冊接口服務的URL。其中URL也是Dubbo內部通過@Adaptive注解實現SPI的核心,通過修改URL的頭部協議(如:register、dubbo、injvm等),在調用
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();PROTOCOL.export(wrapperInvoker)
該方法的時候,會根據不同的協議切換不通的實現類,實現了Dubbo技術架構與業務邏輯的解耦。
private void doExportUrls() { //組裝后的URL格式樣例 //registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-api-provider&dubbo=2.0.2&pid=26212?istry=zookeeper×tamp=1663049763199 ListregistryURLs = ConfigValidationUtils.loadRegistries(this, true); int protocolConfigNum = protocols.size(); for (ProtocolConfig protocolConfig : protocols) { //組裝pathKey : org.apache.dubbo.demo.DemoService String pathKey = URL.buildKey(getContextPath(protocolConfig) .map(p -> p + "/" + path) .orElse(path), group, version); //保存接口服務 repository.registerService(pathKey, interfaceClass); //服務注冊 doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum); }}
通過內置的動態字節碼編譯(默認javassist)生成Invoker代理類,然后通過反射機制生成Wrapper實例。其中Invoker是Dubbo的核心模型,Invoker是Dubbo中的實體域,也就是真實存在的。其他模型都向它靠攏或轉換成它
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, ListregistryURLs, int protocolConfigNum) { ...... //組裝新的URL //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=46528&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663051456562 URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map); ...... //Invoker封裝 Invoker> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); //wrapper DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //服務注冊(此時URL頭部協議變成了register,實際會調用RegistryProtocol) Exporter> exporter = PROTOCOL.export(wrapperInvoker); exporters.add(exporter);}# PROXY_FACTORYpublic Invoker getInvoker(T proxy, Class type, URL url) { // 動態代理類生成,反射生成實例 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf("$") < 0 ? proxy.getClass() : type); return new AbstractProxyInvoker (proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class>[] parameterTypes, Object[] arguments) throws Throwable { return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } };}
此時會依次調用RegistryProtocol 、DubboProtocol 將Invoker封裝成Exporter,并將封裝后的Exporter存儲到本地map中(類似于spring bean)。然后會調用底層通信服務(默認netty)進行端口監聽,此時會通過責任鏈模式封裝Exchanger與Transporter,用于處理網絡傳輸消息的編碼/解碼。
# RegistryProtocol : exportpublicExporter export(final Invoker originInvoker) throws RpcException { ...... //此時URL頭部協議已變成dubbo //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098 providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); // export invoker final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl); // 此時Registry實例默認是ZookeeperRegistry final Registry registry = getRegistry(originInvoker); final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); // decide if we need to delay publish boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { //底層調用ZK,創建node節點 registry.register(registeredProviderUrl); } ....}# RegistryProtocol : doLocalExportprivate ExporterChangeableWrapper doLocalExport(final Invoker originInvoker, URL providerUrl) { String key = getCacheKey(originInvoker); return (ExporterChangeableWrapper ) bounds.computeIfAbsent(key, s -> { Invoker> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); //此時會調用DubboProtocol進行exporter封裝 return new ExporterChangeableWrapper<>((Exporter ) protocol.export(invokerDelegate), originInvoker); });}
# DubboProtocol : exportpublicExporter export(Invoker invoker) throws RpcException { ...... // export service. String key = serviceKey(url); //exporter封裝 DubboExporter exporter = new DubboExporter (invoker, key, exporterMap); exporterMap.put(key, exporter); ...... //開啟服務監聽 openServer(url); optimizeSerialization(url); return exporter;}
(5)步驟五:注冊服務節點
封裝Exporter并開啟服務端口監聽后,會調用注冊中心(默認Zookeeper)注冊服務節點信息
# RegistryProtocol : exportpublicExporter export(final Invoker originInvoker) throws RpcException { ...... //此時URL頭部協議已變成dubbo //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098 providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); // export invoker final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl); // 此時Registry實例默認是ZookeeperRegistry final Registry registry = getRegistry(originInvoker); final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); // decide if we need to delay publish boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { //底層調用ZK,創建node節點 registry.register(registeredProviderUrl); } ....}
至此,Dubbo服務注冊的整體流程已大致結束,文中如有不當或者錯誤觀點,歡迎大家評論區指出。感興趣的同學,可以關注后續“Dubbo架構設計與源碼解析”系列的文章。