小白也能看懂的dubbo3应用级服务发现详解

小白也能看懂的dubbo3应用级服务发现详解,第1张

dubbo 是一款开源的 RPC 框架,主要有3个角色: 提供者(provider) 消费者(consumer) 注册中心(registry)

提供者启动时向注册中心注册服务地址,消费者启动时订阅服务,并通过获取到的提供者地址发起调用,当提供者地址变更时,通过注册中心向消费者推送变更。这就是 dubbo 主要的工作流程。

在275之前,dubbo 只支持接口级服务发现模型,>=275的版本提供了接口级与应用级两种服务发现模型,30之后的版本应用级服务发现更是非常重要的一个功能。

本文将从为什么需要引入应用级服务发现,dubbo 实现应用级服务发现的难点以及dubbo3 是如何解决这些问题这三个部分进行讲解。

开始前,我们先了解下 dubbo 最初提供的接口级服务发现是怎样的。

dubbo 服务的注册发现是以 接口 为最小粒度的,在 dubbo 中将其抽象为一个 URL ,大概长这样:

看着很乱?捋一捋:

无论是存储还是变更推送压力都可能遇到瓶颈,数据多表现在这两个方面:

这个问题好解决: 拆!

dubbo 在 27 之后的版本支持了 元数据中心 配置中心 ,对于URL的参数进行分类存储。持久不变的(如application、method等)参数存储到元数据中心中,可能在运行时变化(timeout、tag)的存储到配置中心中

无论是增加一台机器还是增加一个接口,其增长都是线性的,这个问题比单条数据大更严重。

当抹去注册信息中的 interface 信息,这样数据量就大大减少

只用过 dubbo 的同学可能觉得这很主流。

但从服务发现的角度来看:

无论是用的最多的服务注册发现系统 DNS ,又或者是 SpringCloud 体系、 K8S 体系,都是以应用为维度进行服务注册发现的,只有和这些体系对齐,才能更好地与之进行打通。

在我了解的范围里,目前只有 dubbo SOFARPC HSF 三个阿里系的 RPC 框架支持了接口级的服务发现。

provider端暴露服务:

consumer端引用服务:

本地调用远程的方法时,只需要配置一个 reference ,然后直接使用 interface 来调用,我们不必去实现这个 interaface,dubbo 自动帮我们生成了一个代理进行 RPC 调用,屏蔽了通信的细节,让我们有种 像调用本地方法一样调用远程方法的感觉 ,这也是 dubbo 的优势。

从这里我们能看出为什么 dubbo 要设计成接口级服务发现,因为要为每一个 interface 生成一个代理,就必须定位到该 interface 对应服务暴露的服务地址,为了方便,dubbo 就这么设计了。

如果让我来设计应用级服务发现,注册不必多说,按应用名注册即可。

至于订阅,在目前 dubbo 机制下,必须得告诉消费者消费的每个接口是属于哪个应用,这样才能定位到接口部署在哪里。

实现 dubbo 应用级服务发现,难点在于

保留接口级服务发现,且默认采取双注册方式,可配置使用哪种服务发现模型,如下配置使用应用级服务发现

名词有点高大上,但道理很简单,让 dubbo 自己去匹配,提供者注册的时候把接口和应用名的映射关系存储起来,消费者消费时根据接口名获取到部署的应用名,再去做服务发现。

数据存储在哪里?显然元数据中心非常合适。该方案用户使用起来和之前接口级没有任何不同,但需要增加一个元数据中心,架构变得复杂。

且有一个问题是,如果接口在多个应用下部署了,dubbo 查找的策略是都去订阅,这可能在某些场景下不太合适。

本文从接口级服务发现讲到应用级服务发现,包含了为什么 dubbo 设计成接口级服务发现,接口级服务发现有什么痛点?基于 dubbo 现状如何设计应用级服务发现,应用级服务发现实现有什么难点等等问题进行解答,相信看完的小伙伴一定有所收获。

画外音:目前Dubbo在开源中国举办的2019年度最受欢迎中国开源软件中排名第3名,支持Dubbo的朋友可以去投票哇。 2019年度最受欢迎中国开源软件

Consumer端正常调用Dubbo服务时,一般都需要服务提供方提供一个jar包,只有在项目中引入该jar包,才能调用相关服务;能不能向>

用于把dubboproperties读到spring的environment中,

这个工作是由Spring的ConfigurationClassPostProcessor类来完成的检测到某个需要注册的Bean上有@PropertySource注解,就会读该文件的配置信息,弄到environment对象的MutablePropertySources对象中。

后期会把配置信息弄到dubbo 配置类中

该注解上还有@DubboComponentScan,@EnableDubboConfig,这两个注解是dubbo用注解与spring集成的核心了

该注解用@import导入了DubboConfigConfigurationRegistrar这个类

DubboConfigConfigurationRegistrar 实现了ImportBeanDefinitionRegistrar接口,那么spring在实例化的时候会调用DubboConfigConfigurationRegistrar重写ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法,并且将用@Import导入DubboConfigConfigurationRegistrar的类的元数据包装成importingClassMetadata对象。

其实就是为了获取入口类AnnoBean上的@EnableDubboConfig注解里的multiple属性配置的值,默认是true

然后注册了两个DubboConfigConfiguration的内部类

通过读Class对象注册到ioc容器

类上有@EnableDubboConfigBindings,值为@EnableDubboConfigBinding数组

通过绑定,将有对应前缀的配置信息赋值到对应的配置类中

又用@Import导入DubboConfigBindingsRegistrar类,DubboConfigBindingsRegistrar这个类又实现了ImportBeanDefinitionRegistrar,EnvironmentAware接口

实现ImportBeanDefinitionRegistrar肯定是为了另外导入一些类,并且拿到导入的源类,获取源类上配置的信息

实现EnvironmentAware是为了拿到spring的environment对象,因为 dubboproperties 已经被@PropertySource注解机制加载到了environmentMutablePropertySources中,在这里只对beanName的创建有作用。

registrarregisterBeanDefinitions :

注册的过程中,需要从environment对象中拿dubbo相关的配置,比如ApplicationConfig只拿

dubboapplication相关的配置,然后创建ApplicationConfig的BeanDefinition

如果 @EnableDubboConfigBinding配置的multiple为true(默认为false),并且在配置文件中配置了同样前缀的属性,如:

这样会为同一种配置类型,生成两个BDbeanName不同的配置Bean,名称规则如下所示, #0表示的是''在配置的key中出现的位置

之后还会注册一个BeanPostProcessor类型的DubboConfigBindingBeanPostProcessor类的beanDefinition,BeanPostProcessor类型 会在每一个Bean实例化的过程中,根据配置的前缀,从environment拿出所需的配置,根据beanName来处理beanName相同的这一个配置Bean,把配置信息绑定到配置类的属性中。

DubboConfigBindingBeanPostProcessorpostProcessBeforeInitialization

利用 dubboConfigBinder 对象来绑定前缀为dubboapplication的配置信息到配置Bean中

这里dubboConfigBinder对象是DubboConfigBindingBeanPostProcessor中的一个属性,是在因为这个类实现了InitializingBean这个接口的afterPropertiesSet方法,dubboConfigBinder对象就是在这里初始化的

最后用的DataBinder的api把一个MutablePropertyValues绑定到Bean的属性

@import进来了DubboComponentScanRegistrar类

DubboComponentScanRegistrar又实现了ImportBeanDefinitionRegistrar接口,实现registerBeanDefinitions方法

跟xml的逻辑一样,同样是

1、InvokerInvocationHandler jdk动态代理

5、RegistryDirector返回Invokers

Router分为:Script 脚本路由、Condition 条件路由

6、通过MockInvokersSelector的route方法(getNormalInvokers)拿到能正常执行的invokers

8、当回到AbstractClusterInvoker后,执行(默认FailoverClusterInvoker,根据配置的是,Failfast Cluster(快速失败) , Failsafe Cluster(失败安全) , Failback Cluster(失败自动恢复) , Forking Cluster(并行调用多个服务器,只要一个成功即返回) , Broadcast Cluster(广播调用所有提供者,逐个调用,任意一台报错则报错))doInvoker方法

9、FailoverClusterInvoker调用AbstractClusterInvoker的select方法

10、执行doSelect方法

11、调用AbstractLoadbalance的select方法

12、根据配置的负载均衡策略调用对应的(如RoundRobinLoadBalance)类的doSelect方法

13、返回invokersget()方法

14、调用FailoverClusterInvoker的invoke方法

均继承自抽象类AbstractDirectory

Directory 获取 invoker 是从 methodInvokerMap 中获取的,主要都是读 *** 作,那它的写 *** 作是在什么时候写的呢就是在回调方法 notify 的时候 *** 作的,也就是注册中心有变化,则更新 methodInvokerMap 和 urlInvokerMap 的值

根据dubbo-admin配置的路由规则来过滤相关的invoker,当我们对路由规则点击启用,就会触发 RegistryDirectory 类的 notify 方法。

notify方法调用refreshInvoker方法。

route方法的实现类为ConditionRoute 根据条件进行过滤

1、调用mathThen方法

2、调用matchCondition方法

3、调用isMatch判断

4、调用isMatchGlobPattern方法

集群模块是服务提供者和服务消费者的中间层,为服务消费者屏蔽了服务提供者的情况,这样服务消费者就可以专心处理远程调用相关事宜。比如发请求,接受服务提供者返回的数据等。这就是Dubbo Cluster集群的作用。

通过cluster来指定集群容错方式

其实就是应对出错情况采取的策略

用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非提供者挂了,再连另一台,自动开启延迟链接,以减少长接数

​ 启动时服务提供者将当前进程启动时间注册到ZK;服务消费者发现该节点后计算服务启动时间(相对当前时间),在默认预热时间的前20%时间内,该节点权重始终固定为2,这样客户端的负载均衡器只会分发极少的请求至节点。

​ 在预热时间之后的80%时间内,该节点权重将随着时间的推移而线性增长;待预热时间到期后,权重自动恢复为默认值100;负载均衡器的内核是一个标准的WLC算法模块,即加权最少连接算法;

​ 如果某个节点Hang住或宕机,其权重会迅速自动调节降低,避免持续性影响;当节点下线时,服务端提前触发权重调节,重载默认权重至1并发布到注册中心,服务消费者将迅速感知到该事件;

服务提供者优雅下线步骤(注意这套逻辑仅在服务端执行)在okhtmdown=true对应的controller中加入下列逻辑,注意要判断down是否为true,因为正常来说false表示启动验证而不是关机

服务者消费者配置

dubbo服务支持参数动态调整,例如动态调整权重,但dubbo实现方式较为特殊,并不是常规思路。

​ ServiceConfig类拿到对外提供服务的实际类ref,然后通过ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转换(javassistProxyFacory、JdkProxyFactory),接着要做Invoker转换到Export的过程

​ 服务发布:本地暴露、远程暴露

​ 为什么会有 本地暴露 和 远程暴露 呢不从场景考虑讨论技术的没有意义是在dubbo中我们一个服务可能既是 Provider ,又是 Consumer ,因此就存在他自己调用自己服务的情况,如果再通过网络去访问,那自然是舍近求远,因此他是有 本地暴露 服务的这个设计从这里我们就知道这个两者的区别

1、spring启动,解析配置文件

2、创建dubbo标签解析器

3、解析dubbo标签

4、ServiceBean解析

5、容器创建完成,触发ContextRefrestEvent

6、export暴露服务

7、duExportUrls

8、doExportUrlsFor1Protocol

9、getInvoker

10、protocolexport

11、开启服务器 openServer()如nettyServer

12、注册服务到注册中心 registerProvider

Filter 在服务暴露前,做拦截器初始化,在加载所有拦截器时会过滤支队provider生效的数据。

可以。zookeeper的信息会缓存到本地作为一个缓存文件,并且转换成 properties 对象方便使用。建立线程池,定时检测并连接注册中心,失败了就重连。

注册服务到zk其实就是在zk上创建临时节点,当节点下线或者down掉时,即会删除临时节点,从而使服务从可用列表中剔除。

持久节点

临时节点

1、export的时候进行zk订阅

2、设置监听回调的地址,回调给FailbackRegistry的notify

3、创建持久节点

4、设置对该节点的监听

5、更新新的服务信息,服务启动和节点更新回调,都会调用到这里

6、更新缓存文件

7、对比新旧信息是否有变化,有则重新暴露服务

高并发大业务量情况下,暂时屏蔽边缘业务

MockClusterInvoker

​ SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。

本文内容并非原创,使用资料均来自互联网。 dubbo使用了zkClient而不是使用zookeeper本身的客户端与zookeeper进行交互,为什么呢? 先看看zookeeper本身自带的客户端的问题。 1 ) ZooKeeper的Watcher是一次性的,用过了需要再注册;

以上就是关于小白也能看懂的dubbo3应用级服务发现详解全部的内容,包括:小白也能看懂的dubbo3应用级服务发现详解、Dubbo配置参数详解-generic、【dubbo源码】5.配置信息解析-注解版等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9713174.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-01
下一篇 2023-05-01

发表评论

登录后才能评论

评论列表(0条)

保存