基于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:FRAMEWORK-dubbo框架级别、APPLICATION-应用级别、MODULE-模块级别、SELF-prototype原型;

public enum ExtensionScope {
    FRAMEWORK,
    APPLICATION,
    MODULE,
    SELF
}
复制代码

不同scope有不同ScopeModel负责管理。

ScopeModel实现ExtensionAccessor,提供获取SPI扩展点的能力。

dubbo中所有用到扩展点的地方,都会通过ScopeModel来获取对应scope下的扩展点。

而原来ExtensionLoader#getExtensionLoader(type)方法已经废弃,委派给全局单例ModuleModel获取扩展点。

每个ScopeModel实例对应一个ExtensionDirector,所有获取扩展点逻辑都委派给这个ExtensionDirector。

private volatile ExtensionDirector extensionDirector;
protected void initialize() {
    synchronized (instLock) {
        this.extensionDirector = new ExtensionDirector(parent != null ?
                                                       parent.getExtensionDirector() : 
                                                       null, scope, this);
        // ...
    }
}
复制代码

ExtensionDirector存储一个scope范围中扩展接口class对应ExtensionLoader,从而实现了不同scope管理自己的ExtensionLoader实例。

双亲委派?

ExtensionDirector#getExtensionLoader:获取或创建ExtensionLoader。

1)如果scope=SELF,每次创建一个ExtensionLoader;

2)优先走parent获取ExtensionLoader;

3)parent找不到,自己判断scope是否和扩展点SPI注解scope一致,一致才创建ExtensionLoader;

双亲关系:Framework最大,接着是Application,最后是Module。

如果scope=Framework,通过ModuleModel/ApplicationModel也能获取到,且module、application、framework具备父子关系的三个ScopeModel都会拿到同一个扩展点实现。

例如Codec2编解码器,一种Codec2编解码器在一个FrameworkModel中只有一个实例。

如果scope=Module,只能通过ModuleModel获取,ApplicationModel/FrameworkModel获取不到。

例如Filter过滤器,一种Filter过滤器在一个ModuleModel中只有一个实例。

依赖注入

2.x仅支持setter注入。

3.x增加了构造/aware注入,主要是为了方便接入ScopeModel。

ExtensionLoader#createExtension

普通扩展点支持构造/setter/aware注入。

构造注入

InstantiationStrategy#instantiate:选择构造方法实例化扩展点。

目前支持两种构造方法,一种无参构造,另一种是参数列表只有ScopeModel的构造方法。

优先会取ScopeModel注入构造方法,默认采用无参构造。

可以注入不同ScopeModel。

setter注入

和2.x相同,通过一个自适应扩展AdaptiveExtensionInjector,循环所有ExtensionInjector,获取setter方法的type+name对应实现实例。

SpiExtensionInjector

和2.x相同,通过setter方法可以注入自适应扩展实现。

区别在于需要通过关联ScopeModel获取对应scope下的扩展实例。

ScopeBeanExtensionInjector

3.x新支持从一个scope的BeanFactory中获取bean注入,这个BeanFactory后续再聊。

SpringExtensionInjector

3.x对于spring ioc容器注入更合理了,一个SpringExtensionInjector只能对应一个ioc容器。

ApplicationContext是有父子关系的,2.x通过多个ApplicationContext循环获取bean,不太合理。

Aware注入

ExtensionLoader#postProcessAfterInitialization:

ExtensionAccessorAware接口实现,通过setExtensionAccessor注入scope对应ExtensionDirector

ScopeModelAwareExtensionProcessor#postProcessAfterInitialization:

ScopeModelAware接口实现,注入当前scope持有的Model。

ScopeModelAwareExtensionProcessor#initialize:

不同scope能注入的Model不同。

Wrapper增强

2.7.8版本做了Wrapper增强(github.com/apache/dubb…)。

注:因为之前读的是2.7.6,说到Wrapper无序,这里澄清一下。

1)支持直接获取未包装扩展点实现

ExtensionLoader#getExtension支持传入wrap,决定是否获取被包装的扩展点实现。

ExtensionLoader会缓存被包装和未被包装的扩展点实现。

2)支持包装排序

3)支持针对部分扩展点实现做包装

注意:包装类仅支持Setter/Aware注入,不支持构造注入。

BeanFactory?

ScopeModel#initialize:每个ScopeModel都持有一个ScopeBeanFactory。

这个BeanFactory是干嘛用的,和Extension扩展点有什么关系,有什么区别?

ScopeBeanFactory用于统一管理ScopeModel范围内的非扩展点bean

注册bean

ScopeModelInitializer扩展点负责在ScopeModel构造时,执行一些初始化任务。

如FrameworkModel:

如ApplicationModel:

这些ScopeModelInitializer会向ScopeModel的beanFactory中注入一些自己需要用的bean。

比如dubbo-cluster模块需要用到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,支持模块级别隔离。

应用级别隔离

案例

通过DubboBootstrap#newInstance这个新api,可以构造多个DubboBootstrap实例。

ApplicationConfig applicationConfig = new ApplicationConfig("app1");
applicationConfig.setRegisterMode("instance"); // 应用实例级别注册
ServiceConfig service = new ServiceConfig<>();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
DubboBootstrap.newInstance().application(applicationConfig)
    .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
    .protocol(new ProtocolConfig(CommonConstants.DUBBO, 20880))
    .service(service)
    .start();
ApplicationConfig applicationConfig2 = new ApplicationConfig("app2");
applicationConfig2.setRegisterMode("instance"); // 应用实例级别注册
ServiceConfig service2 = new ServiceConfig<>();
service2.setInterface(DemoService.class);
service2.setRef(new DemoServiceImpl());
DubboBootstrap.newInstance().application(applicationConfig2)
    .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
    .protocol(new ProtocolConfig(CommonConstants.DUBBO, 20880))
    .service(service2)
    .start();
复制代码

注意:两个应用使用了同样的dubbo端口20880,但是并不会触发端口冲突。

原因是Protocol是framework级别的。

Protocol成员变量serverMap在framework级别只有一份。

DubboProtocol#openServer:发现已经存在20880端口上的ProtocolServer,跳过。

多Bootstrap

DubboBootstrap底层从2.7.5全局单例变为ApplicationModel纬度下单例

DubboBootstrap#newInstance:

1)FrameworkModel#defaultModel#newApplication:利用全局默认FrameworkModel创建一个新的ApplicationModel

2)DubboBootstrap#getInstance(ApplicationModel):创建DubboBootstrap放入全局缓存instanceMap

对于2.7的DubboBootstrap#getInstance api,用全局默认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.com/apache/dubb…

模块级别隔离

案例

在配置ReferenceConfig时,可以指定所属模块,不同模块中的ReferenceConfig都是相互独立的。

注:该案例即使不建立多个module,也没有问题,只是说明api的使用。

System.setProperty("dubbo.application.service-discovery.migration", "FORCE_APPLICATION");
ApplicationConfig consumerApp = new ApplicationConfig("multi-module-app-consumer");
ReferenceConfig directReference = new ReferenceConfig<>();
directReference.setInterface(DemoService.class);
directReference.setScope("remote"); // 不走injvm调用
ReferenceConfig cacheReference = new ReferenceConfig<>();
cacheReference.setInterface(DemoService.class);
cacheReference.setScope("remote"); // 不走injvm调用
cacheReference.setCache("lfu"); // 走dubbo自带本地缓存
ApplicationModel applicationModel = FrameworkModel.defaultModel().newApplication();
DubboBootstrap bootstrap = DubboBootstrap.getInstance(applicationModel).application(consumerApp)
    .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
    .protocol(new ProtocolConfig(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服务唯一标识interface+group+version。

很多地方存储了serviceKey对应的全局配置(map结构),而dubbo并没有强制约束只能有一个serviceKey,导致部分配置相互覆盖。

举个例子,下面这个issue在异步调用场景下,无法正确获取到Reference上配置的回调方法。

github.com/apache/dubb…。

2.x因为一个serviceKey只能有一个ConsumerModel,所以无法获取到正确的AsyncMethodInfo。

而在3.0,ModuleServiceRepository存储模块级别下的ConsumerModel,从根本上避免了类似问题。

同一个serviceKey在不同module下可以存在多个ConsumerModel。

三、Deployer

引用自dubbo官网:cn.dubbo.apache.org/zh-cn/overv…

当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:

dubbo3.0中提到一个新角色Deployer,自动部署服务的本地代理。

目前Deployer只是一个接口,存在于应用jvm进程中,也并非是一个独立的角色。

下面是一个案例,通过ModuleDeployer单独启动和停止一个DemoService服务。

ApplicationConfig providerApp = new ApplicationConfig("app1");
providerApp.setRegisterMode("instance");
DubboBootstrap provider = DubboBootstrap.newInstance().application(providerApp)
    .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
    .protocol(new ProtocolConfig(CommonConstants.DUBBO, 22880))
    // 启动GreetingService
    .service(t -> t.interfaceClass(GreetingService.class).ref((GreetingService) () -> "null")).start();
// 使用 ModuleDeployer 单独启动 DemoService
ModuleModel module1 = provider.getApplicationModel().newModule();
ServiceConfig demoService= new ServiceConfig<>();
demoService.setInterface(DemoService.class);
demoService.setRef(new DemoServiceImpl());
provider.service(demoService, module1);
module1.getDeployer().start();
Assert.assertTrue(module1.getDeployer().isStarted(), "deployer start fail");
// 使用 ModuleDeployer 单独停止 DemoService
module1.getDeployer().stop();
Assert.assertTrue(module1.getDeployer().isStopped(), "deployer stop fail");
复制代码

目前根据ScopeModel划分,支持两个纬度的Deployer,一个是应用,一个是模块。

DefaultApplicationDeployer

启动

ConfigScopeModelInitializer#initializeApplicationModel

在ApplicationModel构造期间,创建DefaultApplicationDeployer与当前ApplicationModel绑定。

DubboBootstrap#start:委派给底层ApplicationDeployer#start(2.7的启动流程都下放到Deployer)。

ApplicationDeployer#start:我们只考虑首次start的情况,分为两步

1)DefaultApplicationDeployer#initialize:应用Deployer自己初始化

2)DefaultApplicationDeployer#startModules:启动ApplicationModel下所有模块的ModuleDeployer

DefaultApplicationDeployer#initialize:分为以下几步

1)注册ShutdownHook

2)连接配置中心,创建动态配置

3)利用Environment中的kv配置,注入ConfigManager管理的AbstractConfig

4)初始化模块Deployer

5)连接元数据中心

DefaultApplicationDeployer#startModules:所有模块级别的Deployer启动

销毁

DubboBootstrap#destroy:委派给对应ApplicationModel#destroy

ApplicationModel#onDestroy:销毁应用model所有依赖

其中包含ApplicationDeployer和ModuleDeployer。

DefaultApplicationDeployer#preDestroy:从注册中心注销应用实例。

DefaultApplicationDeployer#postDestroy:当所有Module销毁后执行

1)销毁注册中心,取消订阅

2)销毁所有元数据中心

3)执行ShutdownHook

4)销毁线程池

DefaultModuleDeployer

启动

DefaultModuleDeployer#start:可以由上层ApplicationDeployer#start触发,也可以主动启动

1)模块级别AbstractConfig填充

2)暴露服务

3)引用服务

销毁

同样,模块Deployer可以由上层ApplicationModel销毁触发,也可以手动触发。

DefaultModuleDeployer#stop:走Deployer销毁,实际底层是销毁ModuleModel

ModuleModel#onDestroy:销毁ModuleModel

需要注意的是ModuleModel#onDestroy每次执行完成,都会执行ApplicationModel#tryDestroy。

ApplicationModel#tryDestroy:判断如果没有ModuleModel存在,会销毁自身,所以ModuleModel销毁可能会触发ApplicationModel销毁。

DefaultModuleDeployer#preDestroy:状态变更

DefaultModuleDeployer#postDestroy:

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,实现模块粒度的自动部署服务,负责模块级别的启动和停止。

比如暴露服务、引用服务。

展开阅读全文

更新时间:2024-08-18

标签:都会   线程   缓存   全局   容器   源码   实例   模块   级别   方法   中心

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top