Dubbo内核
前面在分布式架构学习的过程中已经大致学习了Dubbo的核心(Ioc+Aop+动态编译+自适应扩展(SPI、Active、Adaptive、wrapper组成的微内核)以及Dubbo的分层,下面将深入了解其实现
SPI机制实现
Dubbo的内核的SPI机制主要包括:自定义类加载、通过url控制和动态编译;
- Dubbo的SPI的类加载:
- Dubbo通过ExtensionLoader加载SPI注入的类
- 前面说过Dubbo使用微内核(Ioc、Aop)的思想,通过下面两种方式最小化服务成本:
- 按需加载:
- 需要时加载:说明其不会自动到相关目录读取,只有调用到相关扩展点才会通过class去进行类加载和实例化;
- 自适应扩展:通过解析出的URL获取一个扩展点中指定的实例;
- 单例模式运行
- Dubbo会保存扩展点和实例到ConcurrentHashMap避免重复加载和实例化;
- 按需加载:
- Dubbo的动态编译
Wrapper、@SPI、@Acitvate @Adaptive
实现- 其中Wrapper机制类似静态代理
类加载
- 扩展点:所谓的扩展点就是通过
@SPI
标记的接口及其实现类;- 扩展点注册:
- 在下面目录下建立全限定扩展点接口名,默认按下面顺序查找扩展点
- META-INF/dubbo/internal
- META-INF/dubbo/
- META-INF/services/
- 文件内通过key=value注入,其中key一般是扩展类的前缀名、value是扩展点实现类的全限定类名;
- 在下面目录下建立全限定扩展点接口名,默认按下面顺序查找扩展点
- 扩展点注册:
- ExtensionLoader:加载扩展点、保存扩展点以及其实例的类加载器
- 多实现扩展点的自适应(实例化和选择扩展点的策略)(后面实例介绍)
-
首先使用
@Adaptive
标注扩展点接口实现类; -
没有则将按照
URL
参数和选择@Adaptive
标注的方法的进行选择;- 参数格式为:扩展点类名的驼峰命名的点分形式:例如
AliDubboTest
接口,参数命名为ali.dubbo.test=实现类前缀
- 参数格式为:扩展点类名的驼峰命名的点分形式:例如
-
没有则选择默认的即
@SPI
指定的实现类;
-
ExtensionLoader加载过程
- 扩展点加载主要包括:自动激活扩展点加载、扩展点实例加载、自适应扩展点加载
- 分布式架构学习中已经介绍过Dubbo的SPI的三个注解
@SPI @Acitvate @Adaptive

ExtensionLoader类
- createExtension:加载扩展点(返回扩展点的类加载ExtensionLoader实例)
- getExtension:获取扩展点实现类的实例
- getActiveExtension:获取自动激活的扩展点实现类
- getAdaptiveExtension:获取自适应扩展点的实现类
总结
- ExtensionLoader实现类似Spring的单例bean实例化的三级缓存机制
- 主ExtensionLoader(类似第三级缓存):可以通过上面的三个目录加载扩展点的ExtensionLoader
- 三个扩展点注册文件就像BeanDefinition
- 各个扩展点ExtensionLoader的实例(类似二级缓存):可以根据扩展点注册名加载扩展点,
- 类似在Spring中通过通过注入属性获得实际的Bean实例
- 扩展点实现类实例:完整的bean,保存在ConcurrentHashMap中,通过实例名获取;
- 主ExtensionLoader(类似第三级缓存):可以通过上面的三个目录加载扩展点的ExtensionLoader
@Adaptive自适应扩展机制实现
@Adaptive
的基本规则和使用(详细见分布式架构)略过,重点研究期实现思路;
- 重点
- 自适应扩展用于选择一个扩展点,
- 其重点在于:自适应扩展方法、选择条件、选择的实现
- 选择条件
- 可以说是基于URL进行选择,除非用户通过
@Adaptive
标记的类不符合修饰器模式规范
- 可以说是基于URL进行选择,除非用户通过
- 选择的实现
- 修饰器模式:dubbo自动为我们创建修饰器类
- 对于
@Adaptive
方法,如果没有通过@Adaptive
指定实现类,dubbo将为我们自动创建修饰器实现类 - 可以通过``@Adaptive`自定义修饰器实现类
- 对于
- 自动编译+动态代理是其实现的基本原理
- 修饰器模式:dubbo自动为我们创建修饰器类
@Active自动激活机制机制
- 前面自适应扩展是通过修饰器模式选择一个扩展点的扩展方法的实现类,而**
@Active
则是选择多个扩展点的实现类**,一般用于如Filter扩展等需要多个同时激活,因为每个Filter实现的是不同的功能。
自动激活机制特点:
- 顺序性:允许对扩展点实现类的执行顺序进行指定
- 成组服务:一般将一组有顺序的互相补充的同一扩展点实现类的服务分为一组
- 可单点服务:允许只选择一个扩展点实现类来屏蔽分组属性
包装机制Wrapper规范
- Wrapper 机制:对扩展类中的 SPI 接口方法进行增强,基于AOP思想对扩展点的增强,其最大特点是类似SpringAOP,允许对于一个扩展点进行多重包装(代理)。
包装类规范(类似静态代理)
- 类需要实现SPI接口
- 类中需要有SPI接口的引用
- 类中的SPI实例需要是通过一个包含一个SIP接口参数的带参构造器来传的
- 在接口实现方法中要调用SPI接口引用对象的相应方法
- 类名的后缀需要时Wrapper
适用就是静态代理的使用方法,只不过也要注册到Dubbo中,通过Dubbo调用;
使用参考:https://zhuanlan.zhihu.com/p/338624286
启动过程
注册和发现
- Dubbo有一套自己的Ioc和Aop但是Dubbo完美的嵌入Spring(或者说基于Spring),是由于在Dubbo各层中都是通过
DubboBeanDefinitionParser
进行解析处理的,DubboBeanDefinitionParser
则注册到Spring中,通过Spring解析注解;- DubboNamespaceHandler实现了NamespaceHandler用于注册各层的DubboBeanDefinitionParser
- DubboBeanDefinitionParser继承BeanDefinitionParser,使得其可以借用Spring解析Bean的注解等,或Spring的xml、yaml配置的信息;然后将信息注入该层的Config;例如
dubbo.provider
保存到ProviderConfig
- 在
Dubbo
中,Config就相当于Spring的BeanDefinition,Dubbo各层相当于一个Bean; - Dubbo在Config保存了该层运行需要的所有数据;
DubboNamespaceHandler#2.6;3.0后增加了Ssl等Config,但是不是重点;
@Override
public void init() {
//Application上下文环境配置
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
//各层在yaml、xml的配置
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
//服务注册与发现
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
//注解解析器
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
ServiceBean
实现了ApplicationListener<ContextRefreshedEvent>
;通过Spring的ContextRefreshedEvent
事件通知开启Dubbo的服务注册;- 如果非延迟暴露则在属性注入到bean后,触发
afterPropertiesSet
事件,则在这时暴露服务; - 延时暴露则会在
onApplicationEvent
中被延时调用暴露 - 所谓的暴露就是调用
ServerConfig
的export
方法;
- 如果非延迟暴露则在属性注入到bean后,触发
ReferenceBean
同样如此,实现ApplicationListener<ContextRefreshedEvent>
;通过事件通知机制进行Dubbo的服务发现;- 显然消费者没有延迟这一说法,消费者在
afterPropertiesSet
调用getObject
方法调用ReferenceConfig
的get
方法,核心就是生成代理类对象(createProxy),进行服务调用;
- 显然消费者没有延迟这一说法,消费者在
RpcContext
- RpcContext是调用的线程上下文,每个Rpc调用有一个,可以用来传递隐式参数;由于进行Rpc时,ConsumerContextFilter
- 首先RpcContext内部有一个
ThreadLocal
变量(高版本用的InternalThreadLocal
本质上也是ThreadLocal),它是作为ThreadLocalMap的key,表明每个线程有一个RpcContext。
默认Filter链
- ConsumerContextFilter
- ContextFilter
Dubbo2.6-3.0
- 路由和路由组装
- 服务粒度:由原来的接口级服务粒度变为应用级服务粒度
- 服务自省:
- 异步编程模型
- 基于 HTTP/2 的 Triple 协议
- 云原生
RPC实现
自动注册和发现
- 在Dubbo中,只有两个层次没有SPI机制,一个是用户定义的服务接口,一个就是用于服务注册与发现的Config层
服务注册
- ServerConfig,类似于Spring的RootBeanDefinition,是运行时的BeanDefinition
- ServerConfig保存了使用服务和服务运行期间需要的一切数据;
- RootBeanDefinition保存了实例化一个Bean需要的一切数据;
//RPC协议
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
//透明调用代理工厂
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
//随机端口
private static final Map<String, Integer> RANDOM_PORT_MAP = new HashMap<String, Integer>();
//延时启动,定时任务
private static final ScheduledExecutorService delayExportExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboServiceDelayExporter", true));
//URL和服务暴露的方法
private final List<URL> urls = new ArrayList<URL>();
private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
// 接口名和接口Class
private String interfaceName;
private Class<?> interfaceClass;
// 服务实现类
private T ref;
// 服务限定名
private String path;
// 方法配置
private List<MethodConfig> methods;
private ProviderConfig provider;
//是否已经暴露
private transient volatile boolean exported;
//是否需要暴露
private transient volatile boolean unexported;
//泛化服务
private volatile String generic;
- 在前面介绍Dubbo启动的时候介绍过Dubbo怎么将一系列的Config配置完成的,然后进入``ServerConfig的export`方法进行服务暴露
注册的要点
- 可以认为dubbo的注册是本地注册和远程注册俩个过程
- 本地保存在AbstractProtocol的exporterMap、远程通过相关协议实现的
doexport
向注册中心注册
- 本地保存在AbstractProtocol的exporterMap、远程通过相关协议实现的
- dubbo是基于url进行服务,所以注册也是围绕url生成
- 根据协议生成相关协议格式的url
- dubbo完全透明化调用,注册重点是实现透明调用服务类的注册(代理类)
- 通过jdk或者javassist生成动态代理类
export服务暴露
- export会调用doExport进行服务暴露,和Spring的createBean几乎一样的逻辑;
- 在doExport中,首先进行属性注入(注入一系列ProviderConfig、ModuleConfig等到ServerConfig中)
- Spring同样会将一系列父BeanDefinition合并到RootBeanDefinition中
- 然后检查泛化服务之类的跳过
- 然后就是获取注册中心列表;
- 这里在Spring中没啥类似的,如果非要比较的话就是
BeanPostProcessor接口
的实现类列表;
- 这里在Spring中没啥类似的,如果非要比较的话就是
- 生成服务注册的Url进行服务注册:
- 有注册中心:
registry://IP:PORT/org.apache.dubbo.registry.RegistryService&参数
,参数包括:- 通讯协议:默认dubbo协议
- 服务接口:服务的接口全限定名
- 实现:实现类的全限定名
- 服务:提供的所有服务(接口的方法)
- 无注册中心:没用过·····
- 有注册中心:
- 然后根据协议进行服务暴露
- 如果**不需要透明的暴露的话,到这里服务注册就很简单,只需要直接调用
register
进行注册即可;**显然Dubbo没这么简单,**Dubbo在真正注册前会通过ProxyFactory进行一次AOP增强使得注册实例变为一个Invoker对象;**然后通过Invoker作为一个插件源,使得其能实现各种拔插;
- 如果**不需要透明的暴露的话,到这里服务注册就很简单,只需要直接调用
服务发现
- 服务发现相对于服务注册相对简单,服务发现本质是:到注册中心找到服务然后生成代理并在本地缓存;
Invoker
- **Invoker是Dubbo中的实体域,也就是真实存在的。其他模型都向它靠拢或转换成它;**换句话说就是通过所有层的模型都向Invoker靠拢,框架通过Invoker透明调用返回结果;

ProxyFactory
- 略
透明调用
- 透明调用:框架通过代理模式代理实现RPC调用、差错处理;