当前位置:首页 » 操作系统 » 深度解剖dubbo源码

深度解剖dubbo源码

发布时间: 2023-01-10 06:28:50

㈠ Dubbo(一)——Dubbo 集成于 Spring 的原理

最近一直在看bbo的源码部分。在阅读的时候,需要有一个入手点,才能一点一点的进行下去。自己在研究的时候,发现思绪比较乱,于是就以 芋道源码 为基础,一点一点的啃食。芋道源码是直接从bbo的配置和一些核心的API开始讲起,是从bbo已经启动的过程作为开始节点,而这些核心 API 与 Spring 的之间的关系被省略了,这些东西对我来说属于前置的知识点,所以花了比较长的时间又从 Dubbo 的核心 API 倒着往前看。

在阅读 Dubbo 时,发现前置知识越来越多,如:Spring 的 refresh 中的一些核心点,Spring 中 bean 的生命周期,BeanFactory 与 FactoryBean 的区别等。所以这些前置知识花了特别多的时间去补。所幸,虽然补前置知识虽然时间长,但是性价比还是可以的。Dubbo 是依赖于Spring 的上下文环境的框架,其他依赖于 Spring 的框架也是相同的道理。Spring 的一些对外的扩展点,读过之后也会心中有数。

1、本篇主要是描述了 Dubbo 在 Spring 创建上下文的时候,是如何从创建,到能完整提供一个RPC调用能力的一些相关点。
2、由于源码比较多,直接贴断点也太过臃肿,所以仅仅贴一些关键点来概括整个流程。
3、本文是依赖于前面的 bbo 项目进行断点分析,项目结构可以参照这里。项目中 bbo 的配置方式是 xml 文件,所以本篇主要说 xml 配置方式。其他方式道理相同,并不是问题的关键点。

4、项目启动的是 bbo-user 服务,所以 UserService 为 bbo:service,OrderService 为 bbo:reference。

下图为Spring 启动时是如何加载 Dubbo 的,其中省略了大量过程,只保留了一些关键节点,省略的部分可以略微脑补一下。

整个流程的入口是 Spring 的 refresh 方法。每个方法都有比较深的调用栈。与 Dubbo 有关的入口是 refresh 中的 方法

这个方法是执行 beanFactory 的一些后处理操作,其核心流程为在Spring容器中找出实现了BeanFactoryPostProcessor接口的processor并执行。Spring容器会委托给的方法执行。
是比较核心的类,在这里我们关注一下这个类。它的作用是对项目中配置的类进行处理。具体处理可以分为几步:

在加载类信息时,spring 会去用各种方式扫到注册的 bean 信息。我们在 spring 中注册的 bean,逃不出这个方法的扫描方式。 核心方法是:

扫描之后,会将扫描到的 bean 注册到 beanDefinitionMap 中

首先是此处 org.springframework.beans.factory.xml.#parseBeanDefinitions,可以看出方法会以配置文件根节点起,遍历所有子节点。

其次是这里 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition), 此方法会通过解析出来的节点,获取对应的 Spring 的 namespaceUri ,进而获取对应的配置文件处理器。
此处 ele 参数实际值为 <bbo:service ... />,namespaceUri 为 http://code.alibabatech.com/schema/bbo

我们看一下 resolve 方法中的细节。因为这个方法内部才是 Dubbo 依赖于 Spring 的关键点。

此处的 NamespaceHandler 为 DubboNamespaceHandler,再创建结束之后,进行 init 初始化。

可以看到,DubboNamespaceHandler 在初始化的时候,会创建所有 bbo 标签对应的Config 类的 DubboBeanDefinitionParser。并将 DubboBeanDefinitionParser 和 对应的 bbo 标签类注册到 NamespaceHandlerSupport 的 parsers 中。

最后,会在 com.alibaba.bbo.config.spring.schema.DubboBeanDefinitionParser#parse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, java.lang.Class<?>, boolean) 方法中进行处理

Dubbo 服务比较特殊,beanDefinition 跟普通的 bean 不太一样。在向 beanDefinitionMap 注册时,普通的 beanDefinition 的 beanName 与 beanClass 是对应的;而 bbo 服务的 beanDefinition 的 beanName 是bbo 服务的名称,beanClass 为 bbo 对应的 Bean。

普通的 beanDefinition:

bbo 引用的服务的 beanDefinition:

这一步的核心流程是从 beanFactory 中获取所有的 ApplicationListener,然后注册到监听器集合中。它的关键点其实是 ServiceBean。因为 ServiceBean 是 ApplicationListener 的实现。

所以 beanFactory 中 ServiceBean 也会被注册到监听器集合中。项目中的 ServiceBean 的 beanClass 实际是 UserService。

这一步的核心点,主要是创建剩余的各类对象,并将其保存到 singletonObjects 中。其中关联的前置知识为 Spring 中 bean 的生命周期 。它的核心方法是:
org.springframework.beans.factory.support.#doCreateBean

它的具体流程为:

ps:此处并不是只有这一步才会跟 bean 生命周期相关,bean 生命周期贯穿在 refresh 的很多流程中,只要执行doGetBean 方法,都会走这个流程。此处仅仅借楼关联一下。

这一步的核心点,是通知所有的监听器上下文刷新结束的事件。在这一步执行时,会通知到 ServiceBean。

此处暴露的是 UserService。

Dubbo 的启动条件是完全依赖于 Spring 的启动流程,Spring 的启动流程中核心的点是 refresh 方法。所以只要搞懂 refresh 方法,其他的拓展框架流程也会明白。只不过关联的知识点太多了,还是需要时间的积累才能一点一点的搞懂。
如果本篇有描述不清,或者描述有误的地方,还望在下方留言,大家一起交流,一起学习,一起进步~

㈡ Dubbo之限流分析

在前面的一篇中分析了Dubbo是如何降级的,除了降级,有时限流也是一种很有效的解决高并发的性能问题,那在本篇中开始分析Dubbo是如何限流的。我们知道限流主要是通过控制连接数来实现的,防止某一片段内请求处理过大,导致重要服务的失效。

服务端连接控制

限制当前提供者在使用bbo协议最多接受10个消费者链接

或者

并发控制
限制 com.foo.BarService 的每个方法,服务端并发执行(或占用线程池线程数)不能超过10个:

限制 com.foo.BarService 的 sayHello 方法,服务器并发执行(或占用线程池线程数)不能超过10个。

actives限流

该限流方式与前两种不同,其可以设置在提供端,也可以设置在消费者端。可以设置为接口级别,也可以设置为方法级别。
根据消费者与提供者建立的连接类型,其意义也不同。

长连接 : 表示当前的长连接最多可以处理的请求个数。与长连接的数量没有问题。
短连接 :表示当前服务可以同时处理的短连接数量。
类级别

方法级别

connections限流

可以设置在提供端,也可以设置在消费者端。限定连接的个数。对于短连接,和actives相同。但对于长连接,表示长连接的个数。
一般情况下,会使connections与actives联用,让connections限制长连接的个数,让actives限制长连接中可以处理的请求个数。
限制客户端服务使用连接不能超过10个

如果 <bbo:service> 和 <bbo:reference> 都配置了connections, <bbo:reference> 优先。

延迟连接

延迟连接仅可以设置在消费者端,并且不能设置为方法级别。仅作用于Dubbo服务暴露协议。将长连接的建立推迟到消费者真正调用提供者时。 可以减少长连接的数量。

我们已经讲解了如何设置控制链接数的,那么它们底层是如何实现的呢?

实际上上面的逻辑都是一个个Filter,所有的Filter会连接成一个过滤器链,每次请求都会经过整个链路中的每一个Filter。那它是在什么时候构造成一个过滤器链的呢。

在服务暴露的时候会调用 buildInvokerChain , 将真正执行的 invoker 放到过滤链的尾部,再执行 protocol.expert(buildInvokerChain(invoker, ...)) 方法来进行服务暴露。

在服务引用的时候会调用 protocol.refer() 方法先生成 Invoker ,再调用 buildInvokerChain(protocol.refer(type, url), ...) 来生成消费类型的调用链。

ExecuteLimitFilter

它用于限制每个服务中每个方法的最大并发数,有接口级别和方法级别的配置方式。

其基本原理:在框架中使用一个ConcurrentMap缓存了并发数的计数器,为每个请求URL生成一个IdentityString,并以此为key;再将每个IdentityString生成一个RpcStatus对象,将此作为value。RpcStatus对象用于记录对应的并发数。在调用开始之前,会通过URL获得RpcStatus对象,把对象中的并发数计数器原子+1,在finally中再将原子减1。只要在计数器+1的时候,发现当前计数器比设置的并发数大时,就会抛出异常。

TpsLimitFilter

TpsLimitFilter的限流是基于令牌的,即一段时间内只分配N个令牌,每次请求都会消耗一个令牌,耗完为止,后面再来的请求都会被拒绝。
具体的逻辑是在 DefaultTPSLimiter#isAllowable ,会用这个方法判断是否触发限流。
在DefaultTPSLimiter内部用一个ConcurrentHashMap缓存每个接口的令牌数,key是interface+group+version,value是一个StatItem对象,它包装了令牌刷新时间间隔、每次发放的令牌数等。首先判断当前时间减去上次发放令牌的时间是否超过了时间间隔,超过了就重新发放令牌,之前剩余的令牌会被直接覆盖掉。然后,通过CAS的方式减去1令牌,减掉后小于0就会触发限流。

ActiveLimitFilter

和服务提供者的 ExecuteLimitFilter 相似,它是消费者端的过滤器,限制的是客户端的并发量。

但是它与 ExecuteLimitFilter 有所不同,它不会直接抛出异常。而是当到达阈值的时候,会先加锁抢占当前接口的RpcStatus对象,然后通过wait方法进行等待,等待是有时间的,因为请求是有 timeout 属性的。然后如果某个Invoker在调用结束后,并发把计数器减-1并触发一个notify,此时会有一个在wait状态的线程被唤醒并继续执行,判断现在是否超时,如果超时则抛出异常。如果当前并发数仍然超出阈值,则继续执行wait方法;如果没有超出阈值在,则跳出循环,CAS+1,并调用invoke方法,调用结束后CAS-1,最后通过notify唤醒另外一个线程。

参考文章:
Dubbo之限流TpsLimitFilter源码分析
Dubbo服务限流
Dubbo源码分析----过滤器之ActiveLimitFilter

㈢ 【bbo源码】13. 服务消费方之@Reference依赖注入原理

用法 :

当某个主要注册到spring容器中的bean 的属性上有@Reference注解时,并且 注解的injvm = false时(默认),表明该属性会被注入一个远程接口实例,用作rpc远程调用。

@Reference不是派生自spring默认支持的@Resource和@Autowired,那么说明spring是不支持该注解用于依赖注入的,bbo对此进行了支持该注册的拓展。

在入口@EnableDubbo配置了扫描的包路径
用于扫描类上含有bbo@Service 和类属性上含有 @Reference 的bean

@DubboComponentScan 导入了DubboComponentScanRegistrar

DubboComponentScanRegistrar实现Spring的ImportBeanDefinitionRegistrar接口,被调用到实现的registerBeanDefinitions()时,会注册两个beanPostProcessor类

实现了和spring中支持@Autowired注册的一模一样的接口 :,继承了。两者的工作逻辑几乎一模一样。

在bean实例化之后,对 bean进行依赖注入

postProcessPropertyValues()

这个方法每个bean实例化都会调到,用的是父类的

判断方法和类上是否有@Reference注解,并将其包包装成.AnnotatedInjectionMetadata 对象,进行缓存

判断属性是否可以获取到@Reference,获取到返回

收集到需要依赖注入的属性之后,下一步获取到被依赖的bean实例,进行反射赋值

调用内部类AnnotatedFieldElement中的inject方法

获取到bbo创建的代理实例,反射设置有@Reference的属性上

㈣ 【bbo源码】5.配置信息解析-注解版

用于把bbo.properties读到spring的environment中,

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

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

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

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

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

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

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

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

类上有@EnableDubboConfigBindings,值为@EnableDubboConfigBinding数组
通过绑定,将有对应前缀的配置信息赋值到对应的配置类中

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

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

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

registrar.registerBeanDefinitions :

注册的过程中,需要从environment对象中拿bbo相关的配置,比如ApplicationConfig只拿
bbo.application.*相关的配置,然后创建ApplicationConfig的BeanDefinition.

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

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

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

.

利用 bboConfigBinder 对象来绑定前缀为bbo.application的配置信息到配置Bean中

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

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

@import进来了DubboComponentScanRegistrar类

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

跟xml的逻辑一样,同样是

㈤ 14. bbo源码-集群容错之MergeableCluster

在bbo官方的用户手册中,提到了使用 MergeableCluster 的场景--分组聚合:

功能示意图如下:

定义菜单接口方式:

Provider暴露服务--一个服务属于 group-hot ,一个服务属于 group-cold

笔者测试时启动了两个Provider,所以总计有四个服务,bbo-monitor监控显示如下:

Consumer调用服务:

几个重要的配置说明:

com.alibaba.bbo.rpc.cluster.Merger 文件内容如下:

核心源码在 MergeableClusterInvoker.java 中,源码如下所示:

在条件分支 if ( merger.startsWith(".") ) {} 中,有一段逻辑: method = returnType.getMethod( merger, returnType ); ,即从bbo服务接口方法返回类型即 java.util.List 中查找merger配置的方法,例如 .addAll ,我们先看一下debug过程各变量的值:

bbo源码中 method = returnType.getMethod( merger, returnType ); 调用 Method method = getMethod0(name, parameterTypes, true); ,再调用 Method res = privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates); ,最后调用 searchMethods(privateGetDeclaredMethods(true), name, parameterTypes)) ,得到最后方法匹配的核心逻辑如下:

从searchMethods()源码可知,方法匹配需要满足几个条件:

由上面的分析可知,如果要merger=".addAll"能够正常工作,那么只需要将bbo服务的返回类型改成 Collection 即可,例如:

如果 com.alibaba.bbo.rpc.cluster.Merger 文件集中方法无法满足需求,需要自定义实现,那么还是和bbo其他扩展实现一样,依赖SPI。只需要一下几步实现即可:

㈥ Dubbo之服务导出源码分析

要了解服务导出做了什么,需要了解导出的目的是什么?
bbo是一款面向接口代理的高性能RPC调用,说白了就是提供远程服务。

服务导出需要做的:简单说就是根据服务参数、服务协议构建服务URL,注册到注册中心,并启动Server。其中服务参数可以动态配置也需要监听。

1、onApplicationEvent
org.apache.bbo.config.spring.ServiceBean#onApplicationEvent
发布ContextRefreshedEvent,进行导出服务

2、export
org.apache.bbo.config.spring.ServiceBean#export
调用父类ServiceConfig导出,之后发布一个ServiceBeanExportedEvent

②、shouldExport检查服务是否需要导出
org.apache.bbo.config.ServiceConfig#shouldExport

org.apache.bbo.config.ServiceConfig#checkAndUpdateSubConfigs

1、completeCompoundConfigs
ServiceConfig中的某些属性如果是空的,那么就从ProviderConfig、MoleConfig、ApplicationConfig中获取
①、如果配置了ProviderConfig provider,如果application、mole、registries、monitor、protocols、configCenter这些属性为空的情况下,可以从provider中获取信息并赋值

③、如果ApplicationConfig application不为空,可以为registries、monitor为空的赋值
2、startConfigCenter
从配置中心获取配置,包括应用配置和全局配置

SystemConfiguration:是系统环境变量,可以在服务启动时通过-D指定参数
AbstractConfig:是通过@Service注解配置的参数
PropertiesConfiguration:是bbo.properties文件配置的
AppExternalConfiguration和ExternalConfiguration:是在配置中心Dubbo-Admin中配置的,AppXxx是应用级别
配置优先级是:SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
如果放到第二个位置优先级是:SystemConfiguration -->AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> -> PropertiesConfiguration
c、根据setXX()方法和setParameters()方法进行参数值覆盖

②、prepareEnvironment
管理台上的动态配置中心,如果是zookeeper,获取的就是/bbo/config/bbo/bbo.properties节点中的内容。如果是应用级别的则获取的就是/bbo/config/bbo-demo-consumer-application/bbo.properties节点中的内容(bbo-demo-consumer-application应用名,这里是以bbo-demo为例)

4、checkProtocol 从配置中心设置Protocol配置

10、Local、Stub、Mock
local和stub一样,不建议使用了。如果Local存根为true,则存根类为interfaceName + "Local"。如果Stub本地存根为true,则存根类为interfaceName + "Stub"

org.apache.bbo.config.ServiceConfig#doExport

unexported表示:当前服务已经被取消了,就不需要再导出了
exported表示:已经导出了,就不再重复导出了
1、doExportUrls
org.apache.bbo.config.ServiceConfig#doExportUrls

②、遍历protocols协议
pathKey = group/应用名/path服务名:version
例如:mygroup/bbo-demo/org.apache.bbo.demo.DemoService:1.0.1
ProviderMethodModel表示某一个方法、方法名所属的服务的,包含实现类,接口,以及接口中的各个方法

ApplicationModel表示应用中有哪些服务提供者和引用了哪些服务
每种协议都会导出一个单独的服务,并注册到各个注册中心
2、doExportUrlsFor1Protocol
org.apache.bbo.config.ServiceConfig#doExportUrlsFor1Protocol

③、methods方法参数处理

⑦、scope导出方式(scope=null进行远程导出)

生成的注册服务URL是在registryURL和export参数是服务url的拼装
b、也表示服务提供者,包括了Invoker和服务的配置。是对Invoker的包裹
c、protocol.export(wrapperInvoker)这是导出的核心,使用了SPI。
使用特定的协议来对服务进行导出,这里的协议是registry,导出成功后得到一个Exporter
由于注册地址也是服务,所以会先使用RegistryProtocol进行服务注册(服务导出),然后注册(服务导出)完了之后,再使用DubboProtocol进行导出
自适应SPI会获取url
org.apache.bbo.config.invoker.#getUrl

⑩、当存在注册中心时,是先使用Registy协议注册服务,然后在使用Http协议导出服务。而没有注册中心时,是直接使用Http协议导出服务。根据服务url,讲服务的元信息存入元数据中心MetadataReportService

RegistryProtocol被ProtocolFilterWrapper包裹,ProtocolFilterWrapper被ProtocolListenerWrapper包裹(SPI原理)

1、export
org.apache.bbo.rpc.protocol.ProtocolListenerWrapper#export
如果协议是REGISTRY_PROTOCOL(registry)则会直接走到下一个protocol实现类ProtocolFilterWrapper。
如果不是则会使用ListenerExporterWrapper处理

②、获取providerUrl 服务提供者url

③、生成overrideSubscribeUrl,与监听有关
在服务提供者url的基础上,生成一个overrideSubscribeUrl,协议为provider://,增加参数category=configurators&check=false
overrideSubscribeUrl是用来对动态配置监听的,需要监听的服务和监听的类型(configurators, 老版本)
一个overrideSubscribeUrl对应一个OverrideListener,用来监听overrideSubscribeUrl变化事件

④、doLocalExport导出
此时服务协议是bbo,DubboProtocol被ProtocolFilterWrapper包裹,ProtocolFilterWrapper被ProtocolListenerWrapper包裹

1、export
org.apache.bbo.rpc.protocol.ProtocolListenerWrapper#export
返回结果被ListenerExporterWrapper包裹

2、export
org.apache.bbo.rpc.protocol.ProtocolFilterWrapper#export

EchoFilter、ClassLoaderFilter、GenericFilter、ContextFilter、TraceFilter、TimeoutFilter、MonitorFilter、ExceptionFilter
返回包裹了Filter的CallbackRegistrationInvoker
3、export
org.apache.bbo.rpc.protocol.bbo.DubboProtocol#export

②、register
org.apache.bbo.registry.support.FailbackRegistry#register

在服务导出过程中,需要对动态配置中心的数据进行监听。如果发生更改,可以及时做出反映。
这里用到了Zookeeper的Watcher机制。
1、配置中心的路径与动态配置路径是不相同的,这里需要区分开来
配置中心的路径是:
①、应用:/bbo/config/bbo/org.apache.bbo.demo.DemoService/bbo.properties节点的内容
②、全局:/bbo/config/bbo/bbo.properties节点的内容
2、对于动态配置的监听有版本的差别
在2.7之前,只可以对单个服务进行监听,不可以对应用监听
/bbo/org.apache.bbo.demo.DemoService/configurators/* 只对路径名字监听,不监听内容
在2.7之后,服务和应用都可以监听
服务: /bbo/config/bbo/org.apache.bbo.demo.DemoService.configurators 节点的内容
应用: /bbo/config/bbo/bbo-demo-provider-application.configurators 节点的内容
3、监听时机RegistryProtocol#export
org.apache.bbo.registry.integration.RegistryProtocol#export

㈦ Dubbo启动源码解析一

这次讲 bbo-spring-boot-starter 启动方式,所以入口就是Spring的SPI机制;
首先在META-INF/spring.factories配置下,配置了org.apache.bbo.spring.boot.autoconfigure.DubboAutoConfiguration类,在启动时,则会把DubboAutoConfiguration类注册到spring容器中;
我们来看下DubboAutoConfiguration
先看启动流程

我们先看下生产者端的启动流程,首先是在Spring中注册类

该类实现了接口,则在Spring容器初始化时,会调用方法

我们会看到,这个时候会去注册类,这个类我们等流程到了在分析,我们先按启动流程看过去;resolvePackagesToScan方法先获取到需要扫描的包 ,然后再调用registerServiceBeans去注册相关实例,我们重点来看下registerServiceBeans方法

接下来,我们主要去看下registerServiceBean方法

接下来,我们来看下buildServiceBeanDefinition方法

到这,ServiceBean注册成功,ServiceBean类很重要,每个Dubbo service实例都对应一个ServiceBean,相关配置都在ServiceBean中;我们再回到开始注册的类

类继承了,实现了ApplicationListener,主要监听了Spring容器生命周期,我们看下onApplicationContextEvent方法

我们可以看到,当Spring容器启动成功时,会调用bboBootstrap.start();

接下来,主要逻辑在ServiceBean中,这个export方法在其父类ServiceConfig中,我们下一篇主要讲ServiceConfig逻辑;

㈧ 如何更好地学习bbo源代码

一、Dubbo整体架构
1、Dubbo与Spring的整合
Dubbo在使用上可以做到非常简单,不管是Provider还是Consumer都可以通过Spring的配置文件进行配置,配置完之后,就可以像使用spring
bean一样进行服务暴露和调用了,完全看不到bbo
api的存在。这是因为bbo使用了spring提供的可扩展Schema自定义配置支持。在spring配置文件中,可以像、这样进行配置。META-INF下的spring.handlers文件中指定了bbo的xml解析类:DubboNamespaceHandler。像前面的被解析成ServiceConfig,被解析成ReferenceConfig等等。
2、jdk spi扩展
由于Dubbo是开源框架,必须要提供很多的可扩展点。Dubbo是通过扩展jdk
spi机制来实现可扩展的。具体来说,就是在META-INF目录下,放置文件名为接口全称,文件中为key、value键值对,value为具体实现类的全类名,key为标志值。由于bbo使用了url总线的设计,即很多参数通过URL对象来传递,在实际中,具体要用到哪个值,可以通过url中的参数值来指定。
Dubbo对spi的扩展是通过ExtensionLoader来实现的,查看ExtensionLoader的源码,可以看到Dubbo对jdk spi做了三个方面的扩展:

(1)jdk spi仅仅通过接口类名获取所有实现,而ExtensionLoader则通过接口类名和key值获取一个实现;

(2)Adaptive实现,就是生成一个代理类,这样就可以根据实际调用时的一些参数动态决定要调用的类了。

(3)自动包装实现,这种实现的类一般是自动激活的,常用于包装类,比如Protocol的两个实现类:ProtocolFilterWrapper、ProtocolListenerWrapper。
3、url总线设计
Dubbo为了使得各层解耦,采用了url总线的设计。我们通常的设计会把层与层之间的交互参数做成Model,这样层与层之间沟通成本比较大,扩展起来也比较麻烦。因此,Dubbo把各层之间的通信都采用url的形式。比如,注册中心启动时,参数的url为:
registry://0.0.0.0:9090?codec=registry&transporter=netty
这就表示当前是注册中心,绑定到所有ip,端口是9090,解析器类型是registry,使用的底层网络通信框架是netty。

二、Dubbo启动过程

Dubbo分为注册中心、服务提供者(provider)、服务消费者(consumer)三个部分。
1、注册中心启动过程
注册中心的启动过程,主要看两个类:RegistrySynchronizer、RegistryReceiver,两个类的初始化方法都是start。
RegistrySynchronizer的start方法:

(1)把所有配置信息load到内存;

(2)把当前注册中心信息保存到数据库

(3)启动5个定时器。
5个定时器的功能是:
(1)AutoRedirectTask,自动重定向定时器。默认1小时运行1次。如果当前注册中心的连接数高于平均值的1.2倍,则将多出来的连接数重定向到其他注册中心上,以达到注册中心集群的连接数均衡。
(2)DirtyCheckTask,脏数据检查定时器。作用是:分别检查缓存provider、数据库provider、缓存consumer、数据库consumer的数据,清除脏数据;清理不存活的provider和consumer数据;对于缓存中的存在的provider或consumer而数据库不存在,重新注册和订阅。
(3)ChangedClearTask,changes变更表的定时清理任务。作用是读取changes表,清除过期数据。
(4)AlivedCheckTask,注册中心存活状态定时检查,会定时更新registries表的expire字段,用以判断注册中心的存活状态。如果有新的注册中心,发送同步消息,将当前所有注册中心的地址通知到所有客户端。
(5)ChangedCheckTask,变更检查定时器。检查changes表的变更,检查类型包括:参数覆盖变更、路由变更、服务消费者变更、权重变更、负载均衡变更。
RegistryReceiver的start方法:启动注册中心服务。默认使用netty框架,绑定本机的9090端口。最后启动服务的过程是在NettyServer来完成的。接收消息时,抛开bbo协议的解码器,调用类的顺序是
NettyHandler-》NettyServer-》MultiMessageHandler-》HeartbeatHandler-》AllDispatcher-》
DecodeHandler-》HeaderExchangeHandler-》RegistryReceiver-》RegistryValidator-》RegistryFailover-》RegistryExecutor。

2、provider启动过程
provider的启动过程是从ServiceConfig的export方法开始进行的,具体步骤是:
(1)进行本地jvm的暴露,不开放任何端口,以提供injvm这种形式的调用,这种调用只是本地调用,不涉及进程间通信。
(2)调用RegistryProtocol的export。
(3)调用DubboProtocol的export,默认开启20880端口,用以提供接收consumer的远程调用服务。
(4)通过新建RemoteRegistry来建立与注册中心的连接。
(5)将服务地址注册到注册中心。
(6)去注册中心订阅自己的服务。
3、consumer启动过程
consumer的启动过程是通过ReferenceConfig的get方法进行的,具体步骤是:
(1)通过新建RemoteRegistry来建立与注册中心的连接。
(2)新建RegistryDirectory并向注册中心订阅服务,RegistryDirectory用以维护注册中心获取的服务相关信息。
(3)创建代理类,发起consumer远程调用时,实际调用的是InvokerInvocationHandler。

三、实际调用过程
consumer端发起调用时,实际调用经过的类是:
1、consumer:

InvokerInvocationHandler-》MockClusterInvoker(如果配置了Mock,则直接调用本地Mock类)-》FailoverClusterInvoker(负载均衡,容错机制,默认在发生错误的情况下,进行两次重试)-》RegistryDirectory$InvokerDelegete-》ConsumerContextFilter-》FutureFilter->DubboInvoker

2、provider:
NettyServer-》MultiMessageHandler-》HeartbeatHandler-》AllDispatcher-》DecodeHandler-》HeaderExchangeHandler-》DubboProtocol.requestHandler-》EchoFilter-》ClassLoaderFilter-》GenericFilter-》ContextFilter-》ExceptionFilter-》TimeoutFilter-》MonitorFilter-》TraceFilter-》实际service。

四、Dubbo使用的设计模式
1、工厂模式
ServiceConfig中有个字段,代码是这样的:
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

Dubbo里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了jdk

spi的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在classpath下增加个文件就可以了,代码零侵入。另外,像上面的Adaptive实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。
2、装饰器模式
Dubbo在启动和调用阶段都大量使用了装饰器模式。以Provider提供的调用链为例,具体的调用链代码是在ProtocolFilterWrapper的buildInvokerChain完成的,具体是将注解中含有group=provider的Filter实现,按照order排序,最后的调用顺序是
EchoFilter-》ClassLoaderFilter-》GenericFilter-》ContextFilter-》ExceptionFilter-》
TimeoutFilter-》MonitorFilter-》TraceFilter。

更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter则只是在主功能上添加了功能,更改当前线程的ClassLoader,这是典型的装饰器模式。
3、观察者模式
Dubbo的provider启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个listener。注册中心会每5秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个notify消息,provider接受到notify消息后,即运行NotifyListener的notify方法,执行监听器方法。
4、动态代理模式
Dubbo扩展jdk

spi的类ExtensionLoader的Adaptive实现是典型的动态代理实现。Dubbo需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是ExtensionLoader的方法。代理类的主要逻辑是,获取URL参数中指定参数的值作为获取实现类的key。

热点内容
php宏定义 发布:2025-05-15 02:32:54 浏览:269
咸鱼支付密码哪里改 发布:2025-05-15 02:32:53 浏览:519
存储机箱 发布:2025-05-15 02:31:31 浏览:836
编程很累吗 发布:2025-05-15 02:29:25 浏览:551
疫情期间访问国外网络 发布:2025-05-15 02:24:24 浏览:247
我的世界网易版游戏服务器 发布:2025-05-15 02:23:46 浏览:221
全球编程网站 发布:2025-05-15 02:22:55 浏览:334
编程猫知乎 发布:2025-05-15 02:18:00 浏览:631
服务器转接搭建 发布:2025-05-15 02:12:50 浏览:518
编译好的内核如何升级另一台主机 发布:2025-05-15 02:00:06 浏览:759