服務(wù)自省,Dubbo面向了應(yīng)用級

微信公眾號:九點半的馬拉
路途雖遙遠,將來更美好
學海無涯,大家一起加油!

Dubbo是一款很優(yōu)秀的RPC框架,目前Github的Star數(shù)已經(jīng)達到34.6k,有效的反映出它的受歡迎程度。Dubbo提供高性能的基于代理的遠程調(diào)用能力,服務(wù)以接口為粒度,為開發(fā)者屏蔽遠程調(diào)用底層細節(jié)。Dubbo設(shè)計的穩(wěn)定架構(gòu)為數(shù)萬服務(wù)的穩(wěn)定運行提供了堅實的基礎(chǔ)。

Dubbo的傳統(tǒng)架構(gòu)

對于傳統(tǒng)架構(gòu),Dubbo主要可以分為3個組件:ConsumerProviderRegistry,Monitor組件不是較為重要的組件,主要負責服務(wù)相關(guān)數(shù)據(jù)的統(tǒng)計與查詢,可以忽略掉。

1.png

Provider為服務(wù)提供者,在啟動時會根據(jù)設(shè)置的協(xié)議(如:Dubbo協(xié)議),將服務(wù)進行服務(wù)暴露,生成對應(yīng)的Invoker(AbstractProxyInvoker),并存儲在HashMap中,key為端口、接口名、接口版本和接口分組組成的字符串。最后將服務(wù)元數(shù)據(jù)信息注冊到注冊中心。

Consumer為服務(wù)消費者,從注冊中心訂閱要引用的服務(wù)元數(shù)據(jù)信息,封裝成DubboInvoker,最后通過動態(tài)代理轉(zhuǎn)換成一個代理對象。

在服務(wù)消費時,它會通過上述的代理對象進行調(diào)用,在調(diào)用之前會通過Directory獲取所有可以調(diào)用的遠程服務(wù)Invoker列表,然后通過負載均衡策略選擇出一個進行調(diào)用,在具體的調(diào)用之前,它會經(jīng)過一系列的Filter調(diào)用鏈,可以進行處理上下文,限流,回聲檢測,超時日志打印等。

傳統(tǒng)結(jié)構(gòu)的不足

從上面的分析中我們可以看出,傳統(tǒng)的Dubbo架構(gòu)過多依賴注冊中心,當注冊的服務(wù)元數(shù)據(jù)信息發(fā)生變化時,Consumer通過訂閱感知信息變化后,會從注冊中心重新拉取信息,如果變化頻繁,對其網(wǎng)絡(luò)也造成了一定的壓力。同時,注冊中心存儲的是接口級別的服務(wù)信息,容易造成數(shù)據(jù)存儲容量驟增,較多的信息存在冗余。

現(xiàn)在云原生技術(shù)興起,Spring Cloud等提供了面向應(yīng)用的服務(wù)注冊與發(fā)現(xiàn)。從Dubbo2.7.5開始,Dubbo開始提出一種服務(wù)自省的概念,開始提供面向應(yīng)用級別的服務(wù)注冊與發(fā)現(xiàn)。此時,服務(wù)的維度從接口級別降到了應(yīng)用級別,注冊中心存儲應(yīng)用級別的信息,數(shù)據(jù)容量大幅度減少。

Dubbo2.7.5.png

假設(shè)定義了兩個服務(wù)DemoService和RoadService,傳統(tǒng)架構(gòu)下的節(jié)點信息和服務(wù)自省下的節(jié)點信息如下所示(選用Zookeeper作為注冊中心)

傳統(tǒng)節(jié)點.png

上圖是傳統(tǒng)架構(gòu)下的注冊中心節(jié)點。它主要是一種樹形結(jié)構(gòu),該結(jié)構(gòu)分為四層。

/dubbo作為服務(wù)信息的根節(jié)點

org.apache.dubbo.demo.DemoServiceorg.apache.dubbo.demo.RoadService作為第二級節(jié)點,表示服務(wù)的全限定接口名,

comsumers、configcuratorsprovidersroutors為第三級節(jié)點,其中:

consumers下的子節(jié)點表示多個消費者URL元數(shù)據(jù)信息;

configcurators下的子節(jié)點包含多個用于提供者動態(tài)配置URL元數(shù)據(jù)信息;

providers下的子節(jié)點包含多個服務(wù)提供者URL元數(shù)據(jù)信息

routors下的子節(jié)點包含用于多個消費者路由策略URL元數(shù)據(jù)信息。

服務(wù)自省節(jié)點.png

上圖是服務(wù)自省機制下的注冊中心節(jié)點信息。(注意,為了方便,我們將Zookeeper也作為配置中心使用)

在最外層節(jié)點中,有兩個重要的節(jié)點dubboservice

對于service節(jié)點

它主要存儲的不同應(yīng)用的元數(shù)據(jù)信息。比如,dubbo-zookeeper-service-introspection-provider-sample表示一個應(yīng)用名,它的子節(jié)點名是該應(yīng)用的ip地址,節(jié)點值存儲的是有關(guān)元數(shù)據(jù)信息。

對于dubbo節(jié)點

最重要的是config節(jié)點,該節(jié)點是配置中心的節(jié)點,下面的子節(jié)點org.apache.dubbo.spring.boot.sample.consumer.DemoServiceorg.apache.dubbo.spring.boot.sample.consumer.RoadServce表示Dubbo服務(wù)接口名,下面的子節(jié)點表示對應(yīng)的提供對應(yīng)服務(wù)的應(yīng)用名。這樣就做到了Dubbo服務(wù)接口與應(yīng)用的映射。

服務(wù)自省在服務(wù)端和消費端都有體現(xiàn),出于篇幅的考慮,將服務(wù)自省拆分為服務(wù)端和消費端,各自用部分篇幅介紹。

服務(wù)端的服務(wù)自省部分

DubboBootstrap主要處理dubbo所有的配置信息,當調(diào)用start方法時,表示dubbo進行啟動,服務(wù)暴露和服務(wù)自省等均在該方法中執(zhí)行。

執(zhí)行時機

我們可以使用注解@EnableDubbo開啟Dubbo,其中包含了@DubboComponentScan注解。

通過@DubboComponentScan引入類DubboComponentScanRegistrar,后續(xù)創(chuàng)建了ServiceAnnotationBeanPostProcessor后置處理器,它實現(xiàn)了BeanDefinitionRegistryPostProcessor接口,Spring容器中的所有Bean注冊之后會回調(diào)postProcessBeanDefinitionRegistry方法,將@Service注解的服務(wù)BeanDefinition注冊到spring容器中,Spring容器啟動完成后會發(fā)布ContextRefreshEvent事件,DubboBootstrapApplicationListener類會監(jiān)聽到該事件,之后調(diào)用dubboBootstrap.start方法,此時開啟了Dubbo相關(guān)邏輯。

code1.png

我們對其中的幾個重要方法進行解釋。

1. exportServices方法

在exportServices方法中進行服務(wù)暴露,這是一個很重要的額工作。

我們對定義的一個ServiceBean變量進行解釋,即上面提到的DemoService。比如:

beanName : ServiceBean:org.apache.dubbo.spring.boot.sample.consumer.DemoService:1.0.0

interfaceName : org.apache.dubbo.spring.boot.sample.consumer.DemoService

ref : 實現(xiàn)該接口的具體類

id: ServiceBean:org.apache.dubbo.spring.boot.sample.consumer.DemoService:1.0.0

code5.png
code3.png

獲取到具體的ServiceConfig對象后,開始執(zhí)行它的export方法,進行服務(wù)暴露。

在具體的服務(wù)暴露之前,會更新一些相關(guān)的serviceMetadata,然后調(diào)用doExportUrls方法進行暴露。

code6.png

2. doExportUrls方法

2.1)獲取ServiceRepository對象,(該對象記錄發(fā)布的服務(wù)信息,客戶端需要訪問的服務(wù)),從中獲取 ServiceDescriptor對象存儲暴露的服務(wù)

public class ApplicationModel {
        private static final ExtensionLoader<FrameworkExt> LOADER = ExtensionLoader.getExtensionLoader(FrameworkExt.class);
        public static ServiceRepository getServiceRepository() {
                return (ServiceRepository)LOADER.getExtension("repository");
    }
}

public ServiceDescriptor registerService(Class<?> interfaceClazz) {
        return (ServiceDescriptor)this.services.computeIfAbsent(interfaceClazz.getName(), (_k) -> {
            return new ServiceDescriptor(interfaceClazz);
        });
    }
code7.png

2.2)在ServiceRepository注冊服務(wù)提供者的詳細信息

code8.png

在ServiceRepository中有一個ConcurrentHashMap,key為服務(wù)接口名+":"+group+":"+":"+version組成的,value為ProviderModel,主要包含接口的實現(xiàn)實例,上述的serviceDescriptor,serviceConfig和seviceMetadata

2.3)獲取當前服務(wù)對應(yīng)的注冊中心實例,本案例中只設(shè)置了Zookeeper,其中最重要的一點是會判斷是否開啟了服務(wù)自省,最終的URL的協(xié)議頭是不一樣的,在開啟時,添加一個變量service-discovery-registry=service

private static String extractRegistryType(URL url) {
        return UrlUtils.isServiceDiscoveryRegistryType(url) ? "service-discovery-registry" : "registry";
    }
public static boolean isServiceDiscoveryRegistryType(Map<String, String> parameters) {
        return parameters != null && !parameters.isEmpty() ? "service".equals(parameters.get("registry-type")) : false;
    }

開啟了服務(wù)自省后,對應(yīng)的URL樣例:

service-discovery-registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-zookeeper-service-introspection-provider-sample&dubbo=2.0.2&metadata-type=composite&pid=14508&qos.enable=false&registry=zookeeper&registry-type=service&release=2.7.8&timestamp=1612096358401

如果是原先普通的話,開頭 的協(xié)議應(yīng)該為registry

2.4)獲取ProtocolConfig,調(diào)用doExportUrlsFor1Protocol方法進行暴露。

code10.png

2.5)獲取元數(shù)據(jù)注冊中心

2.6)封裝URL進行本地服務(wù)暴露,通過動態(tài)代理轉(zhuǎn)換成Invoker,這幾步和之前的相同

2.7)調(diào)用protocol.export方法進行服務(wù)暴露,開啟了服務(wù)自省,協(xié)議為service-discovery-registry

具體調(diào)用RegistryProtocol.export方法,里面有一步要將服務(wù)信息注冊到注冊中心,這里有一些區(qū)別。

服務(wù)自省是獲取ServiceDiscoveryRegistry類,而普通的是ZookeeperRegistry

在這里主要簡單講下服務(wù)自省下的服務(wù)信息注冊。

注冊.png

2.8)根據(jù)serviceKey找到對應(yīng)的ProviderModel,將url添加進去

2.9)獲取WritableMetadataService,默認是(InMemoryWritableMetadataService),調(diào)用publishServiceDefinition方法,將url中的pid,timestamp,bind.ip,bind.port等參數(shù)字段移除掉,讀取side字段,判斷是provider端還是consumer端,根據(jù)url生成ServiceDefinition,并生成json字符串,并存放到InMemoryWritableMetadataService的serviceDefinitions(ConcurrentSkipListMap)中。

2.10)發(fā)布ServiceConfigExportedEvent事件和ServiceBeanExportedEvent事件

在上面介紹服務(wù)自省下的Zookeeper樹型圖時存在一個/dubbo/mapping/接口名/應(yīng)用名的路徑配置,進行接口名和應(yīng)用名的映射,那它是在什么時候產(chǎn)生的呢?

這時就要借助上述的ServiceConfigExportedEvent事件。

ServiceNameMappingListener監(jiān)聽該事件,調(diào)用onEvent方法

code12.png

然后調(diào)用DynamicConfigurationServiceNameMapping的map方法。
其中,MetadataService服務(wù)信息不發(fā)布到配置中心,該類在服務(wù)自省中發(fā)揮著重要的作用,具體的解釋在以后的篇幅解釋。

private static final List<String> IGNORED_SERVICE_INTERFACES = Arrays.asList(MetadataService.class.getName());
public void map(URL exportedURL) {
        String serviceInterface = exportedURL.getServiceInterface();
        // MetadataService服務(wù)信息不發(fā)布到配置中心
        if (!IGNORED_SERVICE_INTERFACES.contains(serviceInterface)) {
            String group = exportedURL.getParameter("group");
            String version = exportedURL.getParameter("version");
            String protocol = exportedURL.getProtocol();
            String key = ApplicationModel.getName();
            String content = String.valueOf(System.currentTimeMillis());
            this.execute(() -> {
 // 將服務(wù)信息發(fā)布到配置中心,可能有多個配置中心              DynamicConfiguration.getDynamicConfiguration().publishConfig(key, buildGroup(serviceInterface, group, version, protocol), content);
                }

            });
        }
    }

上述基本為服務(wù)暴露的過程,繼續(xù)講解Bootstrap.start()方法的接下來的步驟。

4.registerServiceInstance方法

code13.png

當開啟服務(wù)自省后,MetadataService中會存儲暴露的url信息,

此時,會調(diào)用內(nèi)部的exportMetadataService()和registerServiceInstance()方法。

在exportMetadataService方法中,會暴露相關(guān)的metadataServiceExporter,主要有兩個,即ConfigurableMetadataServiceExporter和RemoteMetadataServiceExporter。

在樣例的配置信息設(shè)置了dubbo.application.metadata-type=composite信息,默認值為local,

當為local時,暴露ConfigurableMetadataServiceExporter類,具體的暴露過程與上述的暴露過程一致,具體的暴露方法如下所示:


code15.png

當為composite時,除了暴露上面的exporter,另外也暴露RemoteMetadataServiceExporter類,

在暴露該類時,不會將信息注冊到配置中心中,這是與普通的服務(wù)的重要區(qū)別之一。

之后,執(zhí)行registerServiceInstance方法。

在上述的Zookeeper樹形圖中存在一個類似/service/應(yīng)用名/host:port的路徑,通過該路徑,我們可以把應(yīng)用相關(guān)的信息記錄下來,進而可以面向應(yīng)用調(diào)用,而不是往常的面向接口調(diào)用,存儲容量大大降低。

在該方法中主要是獲取服務(wù)端發(fā)布的任一一個服務(wù)URL,從中提取出host和port,然后與應(yīng)用名等信息創(chuàng)建一個ServiceInstance,具體結(jié)構(gòu)如下所示:

code14.png

然后遍歷具體的ServiceDiscovery.register方法,樣例配置的是ZookeeperServiceDiscovery,將ServiceInstace信息注冊到上述路徑中。

至此,在服務(wù)端的啟動部分就介紹結(jié)束了,在另一篇中,將會介紹在消費端的服務(wù)自省部分。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容