dubbo源码解析
① 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-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逻辑;
③ 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源码】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的逻辑一样,同样是