dubbo源码分析
1. 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逻辑;
2. 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
3. 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 方法,其他的拓展框架流程也会明白。只不过关联的知识点太多了,还是需要时间的积累才能一点一点的搞懂。
如果本篇有描述不清,或者描述有误的地方,还望在下方留言,大家一起交流,一起学习,一起进步~
4. 【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的属性上