世界今亮點!Dubbo架構設計與源碼解析(二) 服務注冊

2022-12-21 10:01:52 來源:51CTO博客

一、Dubbo簡介

Dubbo是一款典型的高擴展、高性能、高可用的RPC微服務框架,用于解決微服務架構下的服務治理與通信問題。其核心模塊包含【RPC通信】和【服務治理】,其中服務治理又分為服務注冊與發現、服務容錯、負載均衡、流量調度等。今天將重點介紹Dubbo的服務注冊與發現。


(資料圖片)

二、SPI機制

在介紹服務注冊發現之前,先簡單介紹一下貫穿整個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方法生成實例    ExtensionLoader extensionLoader = 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為例)

?

??

?

三、服務注冊

1、服務注冊流程

?

??

2、服務注冊類圖詳解

?

??

3、服務注冊步驟

(1)步驟一:初始化配置(類圖:抽象Config與初始化配置)

首先需要實例化ServiceConfig實例,聲明“注冊接口、接口實例、注冊中心配置”,其中“ServiceBean”是實現Spring與Dubbo整合的橋梁。然后會由DubboBootstrap調用initialize方法實現configManager和Environment的初始化,其中就包括將ServiceConfig中的配置轉換成內部封裝的協議(ApplicationModel、ProviderModel等)

private static void startWithExport() throws InterruptedException {    //初始化配置    ServiceConfig service = 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(); }

(2)步驟二:組裝URL

根據初始化配置組轉注冊接口服務的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    List registryURLs = 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);    }}

(3)步驟三:Invoker封裝(類圖:Ref -> Invoker)

通過內置的動態字節碼編譯(默認javassist)生成Invoker代理類,然后通過反射機制生成Wrapper實例。其中Invoker是Dubbo的核心模型,Invoker是Dubbo中的實體域,也就是真實存在的。其他模型都向它靠攏或轉換成它

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs, 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);        }    };}

(4)步驟四:Exporter封裝(類圖:Invoker-> Exporter)

此時會依次調用RegistryProtocol 、DubboProtocol 將Invoker封裝成Exporter,并將封裝后的Exporter存儲到本地map中(類似于spring bean)。然后會調用底層通信服務(默認netty)進行端口監聽,此時會通過責任鏈模式封裝Exchanger與Transporter,用于處理網絡傳輸消息的編碼/解碼。

# RegistryProtocol : exportpublic  Exporter 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 : exportpublic  Exporter 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 : exportpublic  Exporter 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架構設計與源碼解析”系列的文章。

標簽: 配置文件 根據不同 簡單介紹

上一篇:天天快資訊:一網打盡Linux核心多面性,升職面試必備
下一篇:環球焦點!centos7.5安裝redmine-3.4.6-5