服务自省,Dubbo面向了应用级

微信公众号:九点半的马拉
路途虽遥远,将来更美好
学海无涯,大家一起加油!

Dubbo是一款很优秀的RPC框架,目前Github的Star数已经达到34.6k,有效的反映出它的受欢迎程度。Dubbo提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节。Dubbo设计的稳定架构为数万服务的稳定运行提供了坚实的基础。

Dubbo的传统架构

对于传统架构,Dubbo主要可以分为3个组件:ConsumerProviderRegistryMonitor组件不是较为重要的组件,主要负责服务相关数据的统计与查询,可以忽略掉。

1.png

Provider为服务提供者,在启动时会根据设置的协议(如:Dubbo协议),将服务进行服务暴露,生成对应的Invoker(AbstractProxyInvoker),并存储在HashMap中,key为端口、接口名、接口版本和接口分组组成的字符串。最后将服务元数据信息注册到注册中心。

Consumer为服务消费者,从注册中心订阅要引用的服务元数据信息,封装成DubboInvoker,最后通过动态代理转换成一个代理对象。

在服务消费时,它会通过上述的代理对象进行调用,在调用之前会通过Directory获取所有可以调用的远程服务Invoker列表,然后通过负载均衡策略选择出一个进行调用,在具体的调用之前,它会经过一系列的Filter调用链,可以进行处理上下文,限流,回声检测,超时日志打印等。

传统结构的不足

从上面的分析中我们可以看出,传统的Dubbo架构过多依赖注册中心,当注册的服务元数据信息发生变化时,Consumer通过订阅感知信息变化后,会从注册中心重新拉取信息,如果变化频繁,对其网络也造成了一定的压力。同时,注册中心存储的是接口级别的服务信息,容易造成数据存储容量骤增,较多的信息存在冗余。

现在云原生技术兴起,Spring Cloud等提供了面向应用的服务注册与发现。从Dubbo2.7.5开始,Dubbo开始提出一种服务自省的概念,开始提供面向应用级别的服务注册与发现。此时,服务的维度从接口级别降到了应用级别,注册中心存储应用级别的信息,数据容量大幅度减少。

Dubbo2.7.5.png

假设定义了两个服务DemoService和RoadService,传统架构下的节点信息和服务自省下的节点信息如下所示(选用Zookeeper作为注册中心)

传统节点.png

上图是传统架构下的注册中心节点。它主要是一种树形结构,该结构分为四层。

/dubbo作为服务信息的根节点

org.apache.dubbo.demo.DemoServiceorg.apache.dubbo.demo.RoadService作为第二级节点,表示服务的全限定接口名,

comsumersconfigcuratorsprovidersroutors为第三级节点,其中:

consumers下的子节点表示多个消费者URL元数据信息;

configcurators下的子节点包含多个用于提供者动态配置URL元数据信息;

providers下的子节点包含多个服务提供者URL元数据信息

routors下的子节点包含用于多个消费者路由策略URL元数据信息。

服务自省节点.png

上图是服务自省机制下的注册中心节点信息。(注意,为了方便,我们将Zookeeper也作为配置中心使用)

在最外层节点中,有两个重要的节点dubboservice

对于service节点

它主要存储的不同应用的元数据信息。比如,dubbo-zookeeper-service-introspection-provider-sample表示一个应用名,它的子节点名是该应用的ip地址,节点值存储的是有关元数据信息。

对于dubbo节点

最重要的是config节点,该节点是配置中心的节点,下面的子节点org.apache.dubbo.spring.boot.sample.consumer.DemoServiceorg.apache.dubbo.spring.boot.sample.consumer.RoadServce表示Dubbo服务接口名,下面的子节点表示对应的提供对应服务的应用名。这样就做到了Dubbo服务接口与应用的映射。

服务自省在服务端和消费端都有体现,出于篇幅的考虑,将服务自省拆分为服务端和消费端,各自用部分篇幅介绍。

服务端的服务自省部分

DubboBootstrap主要处理dubbo所有的配置信息,当调用start方法时,表示dubbo进行启动,服务暴露和服务自省等均在该方法中执行。

执行时机

我们可以使用注解@EnableDubbo开启Dubbo,其中包含了@DubboComponentScan注解。

通过@DubboComponentScan引入类DubboComponentScanRegistrar,后续创建了ServiceAnnotationBeanPostProcessor后置处理器,它实现了BeanDefinitionRegistryPostProcessor接口,Spring容器中的所有Bean注册之后会回调postProcessBeanDefinitionRegistry方法,将@Service注解的服务BeanDefinition注册到spring容器中,Spring容器启动完成后会发布ContextRefreshEvent事件,DubboBootstrapApplicationListener类会监听到该事件,之后调用dubboBootstrap.start方法,此时开启了Dubbo相关逻辑。

code1.png

我们对其中的几个重要方法进行解释。

1. exportServices方法

在exportServices方法中进行服务暴露,这是一个很重要的额工作。

我们对定义的一个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 : 实现该接口的具体类

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

code5.png
code3.png

获取到具体的ServiceConfig对象后,开始执行它的export方法,进行服务暴露。

在具体的服务暴露之前,会更新一些相关的serviceMetadata,然后调用doExportUrls方法进行暴露。

code6.png

2. doExportUrls方法

2.1)获取ServiceRepository对象,(该对象记录发布的服务信息,客户端需要访问的服务),从中获取 ServiceDescriptor对象存储暴露的服务

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注册服务提供者的详细信息

code8.png

在ServiceRepository中有一个ConcurrentHashMap,key为服务接口名+":"+group+":"+":"+version组成的,value为ProviderModel,主要包含接口的实现实例,上述的serviceDescriptor,serviceConfig和seviceMetadata

2.3)获取当前服务对应的注册中心实例,本案例中只设置了Zookeeper,其中最重要的一点是会判断是否开启了服务自省,最终的URL的协议头是不一样的,在开启时,添加一个变量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;
    }

开启了服务自省后,对应的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

如果是原先普通的话,开头 的协议应该为registry

2.4)获取ProtocolConfig,调用doExportUrlsFor1Protocol方法进行暴露。

code10.png

2.5)获取元数据注册中心

2.6)封装URL进行本地服务暴露,通过动态代理转换成Invoker,这几步和之前的相同

2.7)调用protocol.export方法进行服务暴露,开启了服务自省,协议为service-discovery-registry

具体调用RegistryProtocol.export方法,里面有一步要将服务信息注册到注册中心,这里有一些区别。

服务自省是获取ServiceDiscoveryRegistry类,而普通的是ZookeeperRegistry

在这里主要简单讲下服务自省下的服务信息注册。

注册.png

2.8)根据serviceKey找到对应的ProviderModel,将url添加进去

2.9)获取WritableMetadataService,默认是(InMemoryWritableMetadataService),调用publishServiceDefinition方法,将url中的pid,timestamp,bind.ip,bind.port等参数字段移除掉,读取side字段,判断是provider端还是consumer端,根据url生成ServiceDefinition,并生成json字符串,并存放到InMemoryWritableMetadataService的serviceDefinitions(ConcurrentSkipListMap)中。

2.10)发布ServiceConfigExportedEvent事件和ServiceBeanExportedEvent事件

在上面介绍服务自省下的Zookeeper树型图时存在一个/dubbo/mapping/接口名/应用名的路径配置,进行接口名和应用名的映射,那它是在什么时候产生的呢?

这时就要借助上述的ServiceConfigExportedEvent事件。

ServiceNameMappingListener监听该事件,调用onEvent方法

code12.png

然后调用DynamicConfigurationServiceNameMapping的map方法。
其中,MetadataService服务信息不发布到配置中心,该类在服务自省中发挥着重要的作用,具体的解释在以后的篇幅解释。

private static final List<String> IGNORED_SERVICE_INTERFACES = Arrays.asList(MetadataService.class.getName());
public void map(URL exportedURL) {
        String serviceInterface = exportedURL.getServiceInterface();
        // MetadataService服务信息不发布到配置中心
        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(() -> {
 // 将服务信息发布到配置中心,可能有多个配置中心              DynamicConfiguration.getDynamicConfiguration().publishConfig(key, buildGroup(serviceInterface, group, version, protocol), content);
                }

            });
        }
    }

上述基本为服务暴露的过程,继续讲解Bootstrap.start()方法的接下来的步骤。

4.registerServiceInstance方法

code13.png

当开启服务自省后,MetadataService中会存储暴露的url信息,

此时,会调用内部的exportMetadataService()和registerServiceInstance()方法。

在exportMetadataService方法中,会暴露相关的metadataServiceExporter,主要有两个,即ConfigurableMetadataServiceExporter和RemoteMetadataServiceExporter。

在样例的配置信息设置了dubbo.application.metadata-type=composite信息,默认值为local,

当为local时,暴露ConfigurableMetadataServiceExporter类,具体的暴露过程与上述的暴露过程一致,具体的暴露方法如下所示:


code15.png

当为composite时,除了暴露上面的exporter,另外也暴露RemoteMetadataServiceExporter类,

在暴露该类时,不会将信息注册到配置中心中,这是与普通的服务的重要区别之一

之后,执行registerServiceInstance方法。

在上述的Zookeeper树形图中存在一个类似/service/应用名/host:port的路径,通过该路径,我们可以把应用相关的信息记录下来,进而可以面向应用调用,而不是往常的面向接口调用,存储容量大大降低。

在该方法中主要是获取服务端发布的任一一个服务URL,从中提取出host和port,然后与应用名等信息创建一个ServiceInstance,具体结构如下所示:

code14.png

然后遍历具体的ServiceDiscovery.register方法,样例配置的是ZookeeperServiceDiscovery,将ServiceInstace信息注册到上述路径中。

至此,在服务端的启动部分就介绍结束了,在另一篇中,将会介绍在消费端的服务自省部分。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容