基于Dubbo3。1。8分析Dubbo3源码
前言
从本章开始基于Dubbo3。1。8分析Dubbo3源码。
由于Dubbo3中到处都会出现各种xxxModel,所以必须先搞清楚这些东西的作用。
本章主要分析:
1)DubboSPI的变更;
2)多实例特性及其作用;
3)Deployer新角色及其作用;一、SPI?Scope
在3。x之前,ExtensionLoader针对于一个扩展点接口只有一个实例,针对一个扩展点实现Class也只有一个扩展点实现实例。
2。xExtensionLoader全局缓存:
在第一章的时候提到过,单例往往会针对一个scope来说,而在Dubbo3。0引入了Scope概念。
ExtensionScope列举了目前支持的四种scope:FRAMEWORKdubbo框架级别、APPLICATION应用级别、MODULE模块级别、SELFprototype原型;publicenumExtensionScope{FRAMEWORK,APPLICATION,MODULE,SELF}复制代码
不同scope有不同ScopeModel负责管理。
ScopeModel实现ExtensionAccessor,提供获取SPI扩展点的能力。
dubbo中所有用到扩展点的地方,都会通过ScopeModel来获取对应scope下的扩展点。
而原来ExtensionLoadergetExtensionLoader(type)方法已经废弃,委派给全局单例ModuleModel获取扩展点。
每个ScopeModel实例对应一个ExtensionDirector,所有获取扩展点逻辑都委派给这个ExtensionDirector。privatevolatileExtensionDirectorextensionDirector;protectedvoidinitialize(){synchronized(instLock){this。extensionDirectornewExtensionDirector(parent!null?parent。getExtensionDirector():null,scope,this);。。。}}复制代码
ExtensionDirector存储一个scope范围中扩展接口class对应ExtensionLoader,从而实现了不同scope管理自己的ExtensionLoader实例。
双亲委派?
ExtensionDirectorgetExtensionLoader:获取或创建ExtensionLoader。
1)如果scopeSELF,每次创建一个ExtensionLoader;
2)优先走parent获取ExtensionLoader;
3)parent找不到,自己判断scope是否和扩展点SPI注解scope一致,一致才创建ExtensionLoader;
双亲关系:Framework最大,接着是Application,最后是Module。
如果scopeFramework,通过ModuleModelApplicationModel也能获取到,且module、application、framework具备父子关系的三个ScopeModel都会拿到同一个扩展点实现。
例如Codec2编解码器,一种Codec2编解码器在一个FrameworkModel中只有一个实例。
如果scopeModule,只能通过ModuleModel获取,ApplicationModelFrameworkModel获取不到。
例如Filter过滤器,一种Filter过滤器在一个ModuleModel中只有一个实例。
依赖注入
2。x仅支持setter注入。
3。x增加了构造aware注入,主要是为了方便接入ScopeModel。
ExtensionLoadercreateExtension:
普通扩展点支持构造setteraware注入。
构造注入
InstantiationStrategyinstantiate:选择构造方法实例化扩展点。
目前支持两种构造方法,一种无参构造,另一种是参数列表只有ScopeModel的构造方法。
优先会取ScopeModel注入构造方法,默认采用无参构造。
可以注入不同ScopeModel。
setter注入
和2。x相同,通过一个自适应扩展AdaptiveExtensionInjector,循环所有ExtensionInjector,获取setter方法的typename对应实现实例。
SpiExtensionInjector
和2。x相同,通过setter方法可以注入自适应扩展实现。
区别在于需要通过关联ScopeModel获取对应scope下的扩展实例。
ScopeBeanExtensionInjector
3。x新支持从一个scope的BeanFactory中获取bean注入,这个BeanFactory后续再聊。
SpringExtensionInjector
3。x对于springioc容器注入更合理了,一个SpringExtensionInjector只能对应一个ioc容器。
ApplicationContext是有父子关系的,2。x通过多个ApplicationContext循环获取bean,不太合理。
Aware注入
ExtensionLoaderpostProcessAfterInitialization:
ExtensionAccessorAware接口实现,通过setExtensionAccessor注入scope对应ExtensionDirector。
ScopeModelAwareExtensionProcessorpostProcessAfterInitialization:
ScopeModelAware接口实现,注入当前scope持有的Model。
ScopeModelAwareExtensionProcessorinitialize:
不同scope能注入的Model不同。
Wrapper增强
2。7。8版本做了Wrapper增强(github。comapachedubb)。
注:因为之前读的是2。7。6,说到Wrapper无序,这里澄清一下。
1)支持直接获取未包装扩展点实现
ExtensionLoadergetExtension支持传入wrap,决定是否获取被包装的扩展点实现。
ExtensionLoader会缓存被包装和未被包装的扩展点实现。
2)支持包装排序
3)支持针对部分扩展点实现做包装
注意:包装类仅支持SetterAware注入,不支持构造注入。BeanFactory?
ScopeModelinitialize:每个ScopeModel都持有一个ScopeBeanFactory。
这个BeanFactory是干嘛用的,和Extension扩展点有什么关系,有什么区别?
ScopeBeanFactory用于统一管理ScopeModel范围内的非扩展点bean。注册bean
ScopeModelInitializer扩展点负责在ScopeModel构造时,执行一些初始化任务。
如FrameworkModel:
如ApplicationModel:
这些ScopeModelInitializer会向ScopeModel的beanFactory中注入一些自己需要用的bean。
比如dubbocluster模块需要用到bean都会在ClusterScopeModelInitializer中注入,bean可以选择在某个scope下单例。
无论是基于Class注册还是基于instance直接注册,最终都会执行初始化Aware注入,放入beanFactory缓存。
获取bean
获取bean,先走当前scopeBeanFactory获取,如果获取不到,走父scopeBeanFactory获取。
注意:这里和SPI扩展点Scope双亲委派是相反的,但是和SpringIOC是一样的,子容器能感知到父容器,父容器对子容器无感知。
底层优先name匹配,其次type匹配(匹配多个抛出异常),如果都匹配不到返回null。
二、多实例
基于ScopeModel非单例,3。0提供了多实例的新特性。
FrameworkModel,支持同一个jvm内部dubbo框架级别隔离。
ApplicationModel,支持应用级别隔离。
ModuleModel,支持模块级别隔离。
应用级别隔离案例
通过DubboBootstrapnewInstance这个新api,可以构造多个DubboBootstrap实例。ApplicationConfigapplicationConfignewApplicationConfig(app1);applicationConfig。setRegisterMode(instance);应用实例级别注册ServiceConfigDemoServiceImplservicenewServiceConfig();service。setInterface(DemoService。class);service。setRef(newDemoServiceImpl());DubboBootstrap。newInstance()。application(applicationConfig)。registry(newRegistryConfig(zookeeper:127。0。0。1:2181))。protocol(newProtocolConfig(CommonConstants。DUBBO,20880))。service(service)。start();ApplicationConfigapplicationConfig2newApplicationConfig(app2);applicationConfig2。setRegisterMode(instance);应用实例级别注册ServiceConfigDemoServiceImplservice2newServiceConfig();service2。setInterface(DemoService。class);service2。setRef(newDemoServiceImpl());DubboBootstrap。newInstance()。application(applicationConfig2)。registry(newRegistryConfig(zookeeper:127。0。0。1:2181))。protocol(newProtocolConfig(CommonConstants。DUBBO,20880))。service(service2)。start();复制代码
注意:两个应用使用了同样的dubbo端口20880,但是并不会触发端口冲突。
原因是Protocol是framework级别的。
Protocol成员变量serverMap在framework级别只有一份。
DubboProtocolopenServer:发现已经存在20880端口上的ProtocolServer,跳过。
多Bootstrap
DubboBootstrap底层从2。7。5全局单例变为ApplicationModel纬度下单例。
DubboBootstrapnewInstance:
1)FrameworkModeldefaultModelnewApplication:利用全局默认FrameworkModel创建一个新的ApplicationModel
2)DubboBootstrapgetInstance(ApplicationModel):创建DubboBootstrap放入全局缓存instanceMap
对于2。7的DubboBootstrapgetInstanceapi,用全局默认ApplicationModel创建DubboBootstrap。
应用级别对象
2。xConfigManager管理所有AbstractConfig,到3。x这里只管理应用级别AbstractConfig。
比如应用配置ApplicationConfig、配置中心配置ConfigCenterConfig、注册中心配置RegistryConfig等等。
其中大部分配置对应实际实例都同样是应用级别对象。
比如元数据中心。
比如基于rpc服务级别的注册中心。
比如基于应用级别的注册中心。
比如配置中心。
应用级别kv配置Environment。
但是Protocol协议是框架级别。
服务端处理rpc请求的线程池(DubboServerHandler线程)是独立的,默认200固定线程池。
注意,线程池创建策略ThreadPool是框架级别的,但是线程池实例ExecutorService是应用级别的。
有什么用
DubboBootstrap多实例支持后,可以在一个jvm进程中部署多个dubbo应用。
举个例子,下面这个issue因为多ioc容器的情况下,DubboBootstrap只能启动一次,导致部分服务没有暴露。
github。comapachedubb模块级别隔离案例
在配置ReferenceConfig时,可以指定所属模块,不同模块中的ReferenceConfig都是相互独立的。
注:该案例即使不建立多个module,也没有问题,只是说明api的使用。System。setProperty(dubbo。application。servicediscovery。migration,FORCEAPPLICATION);ApplicationConfigconsumerAppnewApplicationConfig(multimoduleappconsumer);ReferenceConfigDemoServicedirectReferencenewReferenceConfig();directReference。setInterface(DemoService。class);directReference。setScope(remote);不走injvm调用ReferenceConfigDemoServicecacheReferencenewReferenceConfig();cacheReference。setInterface(DemoService。class);cacheReference。setScope(remote);不走injvm调用cacheReference。setCache(lfu);走dubbo自带本地缓存ApplicationModelapplicationModelFrameworkModel。defaultModel()。newApplication();DubboBootstrapbootstrapDubboBootstrap。getInstance(applicationModel)。application(consumerApp)。registry(newRegistryConfig(zookeeper:127。0。0。1:2181))。protocol(newProtocolConfig(CommonConstants。DUBBO,1))。reference(directReference,applicationModel。newModule())focus。reference(cacheReference,applicationModel。newModule())focus。start();复制代码多Module
每个ReferenceConfig会关联一个ModuleModel。
每个ModuleModel都有一个模块级别的ModuleConfigManager(类比ConfigManager),管理部分AbstractConfig,比如ReferenceConfig。
模块级别对象
ModuleConfigManger管理部分AbstractConfig,包括ServiceConfig、ReferenceConfig等等。
这些配置都是从原来ConfigManager中拆分出来的,现在是模块级别配置。
Module级别也有自己的ModuleEnvironment。
Filter是模块级别的。
即便如此,很多Filter内部依赖的还是应用级别甚至全局变量,比如CacheFilter管理本地缓存。
CacheFactory走应用级别,所以本地缓存实际上是应用级别的。
ClusterFilter是3。0引入的,用于代替ClusterInterceptor(废弃)的扩展点,也是模块级别的。
Filter的javadoc,说明3。0在负载均衡前会经过ClusterFilter。
有什么用
2。x,dubbo在很多地方都用到了serviceKey,即rpc服务唯一标识interfacegroupversion。
很多地方存储了serviceKey对应的全局配置(map结构),而dubbo并没有强制约束只能有一个serviceKey,导致部分配置相互覆盖。
举个例子,下面这个issue在异步调用场景下,无法正确获取到Reference上配置的回调方法。
github。comapachedubb。
2。x因为一个serviceKey只能有一个ConsumerModel,所以无法获取到正确的AsyncMethodInfo。
而在3。0,ModuleServiceRepository存储模块级别下的ConsumerModel,从根本上避免了类似问题。
同一个serviceKey在不同module下可以存在多个ConsumerModel。
三、Deployer
引用自dubbo官网:cn。dubbo。apache。orgzhcnoverv
当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:
dubbo3。0中提到一个新角色Deployer,自动部署服务的本地代理。
目前Deployer只是一个接口,存在于应用jvm进程中,也并非是一个独立的角色。
下面是一个案例,通过ModuleDeployer单独启动和停止一个DemoService服务。ApplicationConfigproviderAppnewApplicationConfig(app1);providerApp。setRegisterMode(instance);DubboBootstrapproviderDubboBootstrap。newInstance()。application(providerApp)。registry(newRegistryConfig(zookeeper:127。0。0。1:2181))。protocol(newProtocolConfig(CommonConstants。DUBBO,22880))启动GreetingService。service(tt。interfaceClass(GreetingService。class)。ref((GreetingService)()null))。start();使用ModuleDeployer单独启动DemoServiceModuleModelmodule1provider。getApplicationModel()。newModule();ServiceConfigDemoServiceImpldemoServicenewServiceConfig();demoService。setInterface(DemoService。class);demoService。setRef(newDemoServiceImpl());provider。service(demoService,module1);module1。getDeployer()。start();Assert。assertTrue(module1。getDeployer()。isStarted(),deployerstartfail);使用ModuleDeployer单独停止DemoServicemodule1。getDeployer()。stop();Assert。assertTrue(module1。getDeployer()。isStopped(),deployerstopfail);复制代码
目前根据ScopeModel划分,支持两个纬度的Deployer,一个是应用,一个是模块。
DefaultApplicationDeployer启动
ConfigScopeModelInitializerinitializeApplicationModel
在ApplicationModel构造期间,创建DefaultApplicationDeployer与当前ApplicationModel绑定。
DubboBootstrapstart:委派给底层ApplicationDeployerstart(2。7的启动流程都下放到Deployer)。
ApplicationDeployerstart:我们只考虑首次start的情况,分为两步
1)DefaultApplicationDeployerinitialize:应用Deployer自己初始化
2)DefaultApplicationDeployerstartModules:启动ApplicationModel下所有模块的ModuleDeployer
DefaultApplicationDeployerinitialize:分为以下几步
1)注册ShutdownHook
2)连接配置中心,创建动态配置
3)利用Environment中的kv配置,注入ConfigManager管理的AbstractConfig
4)初始化模块Deployer
5)连接元数据中心
DefaultApplicationDeployerstartModules:所有模块级别的Deployer启动
销毁
DubboBootstrapdestroy:委派给对应ApplicationModeldestroy
ApplicationModelonDestroy:销毁应用model所有依赖
其中包含ApplicationDeployer和ModuleDeployer。
DefaultApplicationDeployerpreDestroy:从注册中心注销应用实例。
DefaultApplicationDeployerpostDestroy:当所有Module销毁后执行
1)销毁注册中心,取消订阅
2)销毁所有元数据中心
3)执行ShutdownHook
4)销毁线程池
DefaultModuleDeployer启动
DefaultModuleDeployerstart:可以由上层ApplicationDeployerstart触发,也可以主动启动
1)模块级别AbstractConfig填充
2)暴露服务
3)引用服务
销毁
同样,模块Deployer可以由上层ApplicationModel销毁触发,也可以手动触发。
DefaultModuleDeployerstop:走Deployer销毁,实际底层是销毁ModuleModel
ModuleModelonDestroy:销毁ModuleModel
需要注意的是ModuleModelonDestroy每次执行完成,都会执行ApplicationModeltryDestroy。
ApplicationModeltryDestroy:判断如果没有ModuleModel存在,会销毁自身,所以ModuleModel销毁可能会触发ApplicationModel销毁。
DefaultModuleDeployerpreDestroy:状态变更
DefaultModuleDeployerpostDestroy:
1)取消暴露
2)取消引用
3)销毁ModuleServiceRepository
总结SPI?
支持scope,每个ScopeModel管理自己scope下的扩展点。
通过ScopeModel查找扩展点,走双亲委派模式,优先从父Scope找,父Scope找不到才从自身Scope找。
每个ScopeModel持有一个BeanFactory,管理非扩展点bean。
通过BeanFactory查找bean,走ioc父子容器模式,子Scope先从自身找,自身找不到从父Scope找。
为了方便获取ScopeModel,ExtensionLoader支持构造注入、Aware注入ScopeModel。
此外,Wrapper包装扩展点在2。7。8版本做了增强,支持排序,支持条件包装。多实例
3。0支持DubboBootstrap存在多个,本质上是一个ApplicationModel对应一个DubboBoostrap。
此外,同一个DubboBoostrap中还支持模块级别隔离,本质上是多个ModuleModel。Deployer
Deployer是3。0提出的新角色。
官网的解释是自动部署服务的本地代理。
目前在代码中是以Deployer接口的形式出现。
原来2。7中DubboBootstrap的启动和停止逻辑都下沉到Deployer中。
ApplicationDeployer,借助ApplicationModel,实现应用粒度的自动部署服务,负责应用级别的启动和停止。
比如连接配置中心、元数据中心。
ModuleDeployer,借助ModuleModel,实现模块粒度的自动部署服务,负责模块级别的启动和停止。
比如暴露服务、引用服务。
银河系违章哥是谁?银河系违章哥说了什么?违章很多,但是一位来自银行系皇家的公子公然违章,与警察讲起了法律,你听过吗?这件事情确有其事,银河系违章哥的语录也爆红网络。银河系违章哥是谁?银河系违章哥说了什么?银河系……
一季度彩电市场开门不大吉但有三大特点值得关注据奥维云网(AVC)预测数据显示,2022年第一季度中国彩电市场零售量规模为903万台,同比下降8。8;零售额规模为280亿元,同比下降10。1。可以看到,今年一季度量额都继续……
梅花可以放卧室吗?梅花放在卧室风水好吗?梅花是冬天开的一种花,象征着一种气节。因此,一些比较追求雅致的人会养殖这种花。那么,梅花可以放卧室吗?梅花放在卧室风水好吗?梅花可以放卧室吗家里可以摆放梅花。……
10月份可以种什么花?10月份种花好吗10月份正值秋季,很多人喜欢种花,那么10月份是否可以种花呢?10月份种什么花好呢?下面我们来为大家一起介绍下吧!10月份种花好吗可以啊,适合秋播秋种的花草很多呀,……
胖哥俩在执法人员检查前丢弃食材吃死螃蟹有什么危害最近胖哥俩对于用死螃蟹的新闻是非常火爆的,很多人都多少有些了解,而这也引起了市监局的注意,但胖哥俩在执法人员到来之前将有问题的食材全部丢弃,我们接下来便一起了解一下。胖哥俩在执……
碎碎念35哥又邀请我们去参观他们家的别墅,犹豫很久还是去了到达和顺古镇的第二天,一早醒来,草草吃过早饭,我们计划去国殇墓园,却被客栈老板告知,当天闭馆。于是,粉墙黛瓦的和顺古镇成为腾冲之行的第一印记。客栈老板陪我们两人,随意而悠闲漫无……
柴胡注射剂说明书柴胡注射剂使用说明柴胡注射剂是一款很不错的退烧药品,但是这个是打针用的,很多人都想多了解一下这款药品,下面介绍柴胡注射剂说明书柴胡注射剂使用说明。柴胡注射剂说明书【药品名称】柴胡注射液……
小柴胡颗粒产妇能吃吗?小柴胡颗粒产后能吃吗?产妇吃药也是需要好好注意的,很多药不能吃,下面5号网的小编为你们介绍小柴胡颗粒产妇能吃吗?小柴胡颗粒产后能吃吗?小柴胡颗粒产妇能吃吗产妇感冒是可以用药物的,一般的中成药都……
北京密云大集一览表赶集这个词听起来不新鲜,但是随着城市的发展,农村大集越来越少。在北京的密云郊区的部分村镇,还保留着很多农村山货大集,每到开集的当天,村子里的一家老小结伴去赶集,人流涌动,……
小柴胡颗粒可以和头孢一起吃吗可以解酒吗?服用头孢这种药品是有着很多限制的,不能与很多药一起吃,下面5号网的小编为你们介绍小柴胡颗粒可以和头孢一起吃吗可以解酒吗?小柴胡颗粒可以和头孢一起吃吗建议服用风寒感冒颗粒,……
女网红徒步西藏直播时遇难徒步旅行需要准备什么现在越来越多的人都喜欢旅行,而同时现在很多的独自徒步也是非常火热的,引来了很多人的效仿,那么我们便来了解一下女网红徒步西藏直播时遇难?徒步旅行需要准备什么?女网红徒步西藏直播时……
青藏高原,和南极北极齐名,你又了解多少?地球是这样划分的,南极、北极、第三极和其他地方。提起青藏高原,你想到的是什么?是皑皑雪山,是高耸入云,是冰天雪地,还是生命禁区?很久很久以前,青藏高原还是一片汹涌的海域,……