本系列为个人Dubbo学习笔记,内容基于《深度剖析Apache Dubbo 核心技术内幕》, 过程参考官方源码分析文章,仅用于个人笔记记录。本文分析基于Dubbo2.7.5版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。
系列文章地址:Dubbo源码分析:全集整理
本文基于 Dubbo 2.7.5 版本。关于该部分逻辑,如有需要可参考:
在 Dubbo笔记㉕ : Spring 执行流程概述 一文中,我们介绍了 在 Spring 中 Dubbo 的执行流程,其中我们知道了 提供者在启动时的服务发布是在 DubboBootstrap 中完成的。本文我们来看一下 DubboBootstrap 的执行流程。
DubboBootstrap 是 Dubbo 服务启动的核心类,在其中完成了Dubbo服务暴露的过程。
DubboBootstrap 是单例的,其构造函数如下:
private DubboBootstrap() {
// 获取 配置管理
configManager = ApplicationModel.getConfigManager();
// 获取 环境
environment = ApplicationModel.getEnvironment();
DubboShutdownHook.getDubboShutdownHook().register();
// 设置回调,当服务关闭时触发该回调,调用 DubboBootstrap#destroy 来销毁服务。
ShutdownHookCallbacks.INSTANCE.addCallback(new ShutdownHookCallback() {
@Override
public void callback() throws Throwable {
DubboBootstrap.this.destroy();
}
});
}
DubboBootstrap#start 的实现如下:
public DubboBootstrap start() {
// cas 保证只启动一次
if (started.compareAndSet(false, true)) {
// 1. 服务配置初始化
initialize();
// 2. Dubbo服务导出
exportServices();
// Not only provider register
// 3. 元数据中心服务暴露,当使用服务自省模式时才会执行该部分逻辑
// 3.1 不仅仅提供者注册 || 存在导出的服务
if (!isOnlyRegisterProvider() || hasExportedServices()) {
// 3.2 导出元数据服务
exportMetadataService();
// 3.3 如果需要注册本地服务实例
registerServiceInstance();
}
// 4. 服务引用流程
referServices();
}
return this;
}
下面我们按照注释顺序来看:
在 DubboBootstrap#initialize 中完成了 Dubbo的配置检查和初始化,其具体实现如下:
private void initialize() {
// CAS 防止多次调用
if (!initialized.compareAndSet(false, true)) {
return;
}
// 1. 初始化 FrameworkExt 扩展类。 FrameworkExt 是 SPI接口,这里获取所有的 实现类并且调用FrameworkExt#initialized 来初始化
ApplicationModel.iniFrameworkExts();
// 2. 启用配置中心并刷新本地配置
startConfigCenter();
// 3. 在默认是zk作为注册中心时,如果没有配置配置中心,则使用注册中心作为配置中心
useRegistryAsConfigCenterIfNecessary();
// 4. 启动元数据中心配置
startMetadataReport();
// 5. 加载远程配置
loadRemoteConfigs();
// 6. 检查本地配置是否合法
checkGlobalConfigs();
// 7. 初始化元数据中心 Service
initMetadataService();
// 8. 初始化元数据中心导出类
initMetadataServiceExporter();
// 9. 初始化监听器
initEventListener();
}
在 Spring 中 Dubbo 的初始化工作和 Main 方法启动基本也是类似的,只不过在执行过程中稍微有些区别。下面我们按照注释来具体说明 :
ApplicationModel.iniFrameworkExts() 是执行框架扩展实现类的初始化方法,其实现如下:
public static void iniFrameworkExts() {
// 获取支持的框架扩展FrameworkExt 实现
Set<FrameworkExt> exts = ExtensionLoader.getExtensionLoader(FrameworkExt.class).getSupportedExtensionInstances();
for (FrameworkExt ext : exts) {
// 执行初始化方法
ext.initialize();
}
}
其中 FrameworkExt 是 Dubbo 提供的 框架扩展 SPI 接口,继承了 Lifecycle 接口,如下:
@SPI
public interface FrameworkExt extends Lifecycle {
}
...
// Lifecycle 接口实现如下
public interface Lifecycle {
void initialize() throws IllegalStateException;
void start() throws IllegalStateException;
void destroy() throws IllegalStateException;
}
Lifecycle 是 Dubbo 组件的声明周期接口,SPI 扩展实现类时在对应的生命周期会调用对应的方法。
Lifecycle#initialize :当一个 SPI 实现类实现 Lifecycle 接口时,在其创建时会调用该方法。
Lifecycle#start :当一个 SPI 实现类实现 Lifecycle 接口时,在容器刷新结束后会调用该方法。
Lifecycle#destroy :当一个 SPI 实现类实现 Lifecycle 接口时,在容器销毁时会调用该方法。
Dubbo 提供的FrameworkExt 有三个实现 : ConfigManager、Environment、ServiceRepository。我们这里先来看 initialize 方法。只有 Environment#initialize 有具体操作,如下:
// 加载了配置中心的配置并保存到了 org.apache.dubbo.common.config.Environment 的 map 中。
@Override
public void initialize() throws IllegalStateException {
// 获取 配置管理
ConfigManager configManager = ApplicationModel.getConfigManager();
// 获取默认的配置中心
Optional<Collection<ConfigCenterConfig>> defaultConfigs = configManager.getDefaultConfigCenter();
// 如果存在配置中心,则从配置中心获取配置,保存到本地。
defaultConfigs.ifPresent(configs -> {
for (ConfigCenterConfig config : configs) {
this.setExternalConfigMap(config.getExternalConfiguration());
this.setAppExternalConfigMap(config.getAppExternalConfiguration());
}
});
}
如果存在配置中心,则会在这里加载了配置中心的配置与本地配置合并,并保存到 environment 中。
private void startConfigCenter() {
Collection<ConfigCenterConfig> configCenters = configManager.getConfigCenters();
if (CollectionUtils.isNotEmpty(configCenters)) {
// 复合配置,保存了配置优先级的特性
CompositeDynamicConfiguration compositeDynamicConfiguration = new CompositeDynamicConfiguration();
for (ConfigCenterConfig configCenter : configCenters) {
// 刷新配置中心配置
configCenter.refresh();
ConfigValidationUtils.validateConfigCenterConfig(configCenter);
// 添加到 复合配置中
compositeDynamicConfiguration.addConfiguration(prepareEnvironment(configCenter));
}
environment.setDynamicConfiguration(compositeDynamicConfiguration);
}
// 刷新本地配置
configManager.refreshAll();
}
关于配置中心的内容,如有需要详参:Dubbo笔记 ㉒ :配置中心
这一步的注释很清楚,就不再贴出代码:出于兼容性考虑,当注册协议是zookeeper并且没有明确指定配置中心时,使用注册中心作为默认配置中心。
初始化元数据中心实例。服务如果配置了元数据中心, 则在此处进行配置初始化
private void startMetadataReport() {
ApplicationConfig applicationConfig = getApplication();
// 获取元数据中心类型,local 或 remote
String metadataType = applicationConfig.getMetadataType();
// FIXME, multiple metadata config support.
Collection<MetadataReportConfig> metadataReportConfigs = configManager.getMetadataConfigs();
if (CollectionUtils.isEmpty(metadataReportConfigs)) {
// 如果配置是 元数据中心是远程 (dubbo.application.metadata-type = remote) && 没配置元数据中心 则抛出异常
if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataType)) {
throw new IllegalStateException("No MetadataConfig found, you must specify the remote Metadata Center address when 'metadata=remote' is enabled.");
}
// 如果没有配置元数据中心则直接返回
return;
}
// 获取元数据中心配置
MetadataReportConfig metadataReportConfig = metadataReportConfigs.iterator().next();
// 校验配置
ConfigValidationUtils.validateMetadataConfig(metadataReportConfig);
if (!metadataReportConfig.isValid()) {
return;
}
// MetadataReportInstance 会根据元数据中心的配置,创建一个 MetadataReport 实例。
// MetadataReportInstance#MetadataReport 是静态的属性,所以对于MetadataReportInstance 来说 只会存在一个 MetadataReport
MetadataReportInstance.init(metadataReportConfig.toUrl());
}
这一步 对 RegistryConfig 和 ProtocolConfig 的 id 进行了处理。
private void loadRemoteConfigs() {
// registry ids to registry configs
List<RegistryConfig> tmpRegistries = new ArrayList<>();
// 获取 registry id
Set<String> registryIds = configManager.getRegistryIds();
registryIds.forEach(id -> {
if (tmpRegistries.stream().noneMatch(reg -> reg.getId().equals(id))) {
// 通过 registry id 获取到对应的 RegistryConfig 并刷新配置
tmpRegistries.add(configManager.getRegistry(id).orElseGet(() -> {
// 设置注册中心配置的id
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setId(id);
registryConfig.refresh();
return registryConfig;
}));
}
});
// 保存经过上述处理的注册中心
configManager.addRegistries(tmpRegistries);
// protocol ids 同上
// protocol ids to protocol configs
List<ProtocolConfig> tmpProtocols = new ArrayList<>();
Set<String> protocolIds = configManager.getProtocolIds();
protocolIds.forEach(id -> {
if (tmpProtocols.stream().noneMatch(prot -> prot.getId().equals(id))) {
tmpProtocols.add(configManager.getProtocol(id).orElseGet(() -> {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setId(id);
protocolConfig.refresh();
return protocolConfig;
}));
}
});
configManager.addProtocols(tmpProtocols);
}
这一步是检查各个组件的配置是否正确,逻辑比较常规,篇幅所限不再贴出代码。
这里会初始化元数据服务 MetadataService, 当使用服务自省时会注册该服务。
private void initMetadataService() {
this.metadataService = getExtension(getMetadataType());
}
这里会根据 dubbo.application.metadata-type 参数的不同,MetadataService 会选择不同的实现。
MetadataService 的子接口 WritableMetadataService 具有三个实现类 :
metadata-type = local
时加载该类作为元数据服务,服务的元数据信息会保存在本地。metadata-type = remote
时加载该类作为元数据服务初始化元数据中心服务导出器,MetadataServiceExporter 用于导出 MetadataService,其实现如下:
private void initMetadataServiceExporter() {
this.metadataServiceExporter = new ConfigurableMetadataServiceExporter(metadataService);
}
初始化事件监听方法,
// Dubbo 提供了 DirectEventDispatcher 和 ParallelEventDispatcher两种实现,默认是 DirectEventDispatcher
private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension();
private void initEventListener() {
// Add current instance into listeners
addEventListener(this);
}
public DubboBootstrap addEventListener(EventListener<?> listener) {
eventDispatcher.addEventListener(listener);
return this;
}
EventDispatcher 是 SPI 接口,Dubbo提供了两个实现 :
DubboBootstrap 是 GenericEventListener 子类,实现了泛化监听的功能。
DubboBootstrap#exportServices 开始实际导出服务,其实现如下:
private void exportServices() {
// configManager.getServices() 获取所有需要发布的Dubbo Service
configManager.getServices().forEach(sc -> {
// TODO, compatible with ServiceConfig.export()
ServiceConfig serviceConfig = (ServiceConfig) sc;
serviceConfig.setBootstrap(this);
// 如果是异步发布,则交由线程池来发布,并将 Future 保存到 asyncExportingFutures 中
if (exportAsync) {
ExecutorService executor = executorRepository.getServiceExporterExecutor();
Future<?> future = executor.submit(() -> {
sc.export();
});
asyncExportingFutures.add(future);
} else {
// 服务发出,导出的服务保存到exportedServices 中
sc.export();
exportedServices.add(sc);
}
});
}
这里需要注意 configManager.getServices() 获取到的是所有要发布的 dubbo 服务的 ServiceBean。
在 Dubbo笔记㉕ : Spring 执行流程概述 一文中我们说到,在 Spring 中需要暴露的服务会有一个对应的 ServiceBean 注入到容器中,而 ServiceBean 在创建时会调用父类方法AbstractConfig#addIntoConfigManager 将 当前 Bean 添加到 ConfigManager 中。而在这里,则是通过 configManager.getServices() 将所有 的 ServiceBean 拿出来暴露服务。
上面可以看到,服务实际导出过程交由了 ServiceConfig#export 来完成。而 ServiceConfig#export 的导出过程,我们在之前的文章中已经有过分析过 dubbo 2.7.0 版本,:Dubbo笔记 ③ : 服务发布流程 - ServiceConfig#export。
这部分的逻辑只有在 dubbo.application.register-consumer = true
或 启用服务自省时才会执行。关于该部分的详细逻辑,我们在服务自省的文章中详细分析。
// 3. 元数据中心服务暴露
// 3.1 dubbo.application.register-consumer = true || 通过 MetadataService 暴露了服务
if (!isOnlyRegisterProvider() || hasExportedServices()) {
// 3.2 暴露 MateService
exportMetadataService();
// 3.3 注册 应用 到注册中心
registerServiceInstance();
}
关于服务自省,详参:
1 . Dubbo笔记 ㉗ : 服务自省-提供者
2. Dubbo笔记 ㉘ : 服务自省-消费者
如果当前这个服务也引用了其他服务,则在这里完成其他服务的引用。
对于一个Dubbo服务,我们可以通过注解的形式来引用
通过 @Reference 注解引用
@Reference(group = "spring", version = "2.0.0")
private ProviderService providerService;
通过 XML 的形式引用
<dubbo:reference id="ProviderService" interface="com.kingfish.service.ProviderService" group="spring" version="2.0.0" />
通过 @Bean 注解方式引用
@Bean
public ReferenceBean<ProviderService> referenceConfig(){
ReferenceBean<ProviderService> referenceConfig = new ReferenceBean<>();
referenceConfig.setInterface(ProviderService.class);
referenceConfig.setVersion("2.0.0");
referenceConfig.setGroup("spring");
return referenceConfig;
}
对于第一种情况则直接通过 ReferenceAnnotationBeanPostProcessor 来完成引用,而第三种情况则是通过这里的代码完成。(第二种 XML 形式我没看逻辑 )
当 ReferenceBean 在 Spring容器中创建时会调用 AbstractConfig#addIntoConfigManager 将自身添加到 ConfigManager 的配置中。这里通过 configManager.getReferences() 获取到该 ReferenceBean 进行服务引用。具体代码如下:
private void referServices() {
// 获取 引用缓存
if (cache == null) {
cache = ReferenceConfigCache.getCache();
}
// 遍历当前应用引用的 dubbo 服务
configManager.getReferences().forEach(rc -> {
// TODO, compatible with ReferenceConfig.refer()
ReferenceConfig referenceConfig = (ReferenceConfig) rc;
referenceConfig.setBootstrap(this);
// 是否延迟初始化,默认 true,不需要延迟
if (rc.shouldInit()) {
// 异步 或 异步加载引用实例。
if (referAsync) {
CompletableFuture<Object> future = ScheduledCompletableFuture.submit(
executorRepository.getServiceExporterExecutor(),
() -> cache.get(rc)
);
asyncReferringFutures.add(future);
} else {
// 获取 Dubbo引用的 代理类,这里调用的是 ReferenceConfigCache#get(ReferenceConfigBase)
cache.get(rc);
}
}
});
}
其中 ReferenceConfigCache#get(ReferenceConfigBase
的实现如下:
public <T> T get(ReferenceConfigBase<T> referenceConfig) {
// 获取引用的服务 key
String key = generator.generateKey(referenceConfig);
Class<?> type = referenceConfig.getInterfaceClass();
proxies.computeIfAbsent(type, _t -> new ConcurrentHashMap());
ConcurrentMap<String, Object> proxiesOfType = proxies.get(type);
proxiesOfType.computeIfAbsent(key, _k -> {
// 获取引用服务的代理类
Object proxy = referenceConfig.get();
// 缓存引用实例
referredReferences.put(key, referenceConfig);
return proxy;
});
// 返回引用实例
return (T) proxiesOfType.get(key);
}
我们这里看到最终还是通过 Object proxy = referenceConfig.get();
来获取的 Dubbo引用的服务代理类,关于 ReferenceConfig#get 的内容,我们在之前分析 2.7.0 的文章中已经进行过详细解析,如有需要。详参 :Dubbo笔记 ⑧ : 消费者启动流程 - ReferenceConfig#get
当 Spring容器销毁时触发该方法,该方法则是完成一些销毁工作,具体不再赘述。
public DubboBootstrap stop() throws IllegalStateException {
destroy();
return this;
}
public void destroy() {
if (started.compareAndSet(true, false)
&& destroyed.compareAndSet(false, true)) {
unregisterServiceInstance();
unexportMetadataService();
unexportServices();
unreferServices();
destroyRegistries();
destroyProtocols();
destroyServiceDiscoveries();
clear();
shutdown();
release();
}
}
通过我们上面的分析可以知道,提供者的服务导出是通过 ServiceConfig#export 方法实现。 消费者的服务引用是通过 ReferenceConfig#get 方法实现。关于这两个方法的执行流程我们在之前的文章中分析过 Dubbo2.7.0 版本的逻辑 (Main 方法执行版本),2.7.5 版本 (Spring 执行版本)的执行逻辑与之相差不大,下面简要介绍一下 Spring 2.7.5 版本下的执行过程。
这里仅仅是展示了一点流程,如果需要详细阅读,可参考 Dubbo源码分析:全集整理
public synchronized void export() {
// 不应暴露直接返回服务
if (!shouldExport()) {
return;
}
// bootstrap 没有初始化则先初始化
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
bootstrap.init();
}
// 检查并更新配置
checkAndUpdateSubConfigs();
//init serviceMetadata
// 初始化 service 元数据信息,暴露版本、分组、接口、暴露方法及入参等信息
serviceMetadata.setVersion(version);
serviceMetadata.setGroup(group);
serviceMetadata.setDefaultGroup(group);
serviceMetadata.setServiceType(getInterfaceClass());
serviceMetadata.setServiceInterfaceName(getInterface());
serviceMetadata.setTarget(getRef());
// 进行服务发布
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
这一步主要增加了 DubboBootstrap 的初始化验证 和 ServiceMetadata 的赋值
protected synchronized void doExport() {
....
// 进行服务暴露
doExportUrls();
// dispatch a ServiceConfigExportedEvent since 2.7.4
// 分发暴露事件,ServiceNameMappingListener 对 ServiceConfigExportedEvent 事件进行监听
dispatch(new ServiceConfigExportedEvent(this));
}
注: dispatch(new ServiceConfigExportedEvent(this));
会分发 ServiceConfigExportedEvent 事件。而 ServiceNameMappingListener 对该事件进行了监听。ServiceNameMappingListener#onEvent 实现如下:
@Override
public void onEvent(ServiceConfigExportedEvent event) {
ServiceConfig serviceConfig = event.getServiceConfig();
List<URL> exportedURLs = serviceConfig.getExportedUrls();
exportedURLs.forEach(url -> {
String serviceInterface = url.getServiceInterface();
String group = url.getParameter(GROUP_KEY);
String version = url.getParameter(VERSION_KEY);
String protocol = url.getProtocol();
// 在配置中心上映射服务,以 zk 为例,其映射的节点为 dubbo/config/mapping/com.kingfish.service.ProviderService/simple-provider
// 即 dubbo/config/mapping/{接口全路径}/{应用名称}
serviceNameMapping.map(serviceInterface, group, version, protocol);
});
}
这里 serviceNameMapping.map(serviceInterface, group, version, protocol);
将当前 接口和应用映射到了 zk 上,如下图:
private void doExportUrls() {
ServiceRepository repository = ApplicationModel.getServiceRepository();
ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
// 注册服务提供者
repository.registerProvider(
getUniqueServiceName(),
ref,
serviceDescriptor,
this,
serviceMetadata
);
// 获取注册中心地址
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
// In case user specified path, register service one more time to map it to path.
// 注册 Dubbo Service
repository.registerService(pathKey, interfaceClass);
// TODO, uncomment this line once service key is unified
serviceMetadata.setServiceKey(pathKey);
// 进行服务暴露
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
关于 ServiceConfig#doExportUrlsFor1Protocol 的内容基本和 2.7.0 没有差异,详参 Dubbo笔记 ④ : 服务发布流程 - doExportUrlsFor1Protocol
下面内容来源 : dubbo解析-ServiceRepository功能及属性详解
ServiceRepository是存储了所有服务端发布的服务、客户端需要访问的服务,通过ServiceRepository可以获取所有本dubbo实例发布的服务和引用的服务。
ServiceRepository是通过ApplicationModel.getServiceRepository方法创建或者获取的。
里面包括三个字段:services、consumers、providers。作用分别是:
services:类型是ConcurrentMap
consumers:类型是ConcurrentMap
providers:类型是ConcurrentMap
public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
}
if (ref == null) {
init();
}
return ref;
}
public synchronized void init() {
if (initialized) {
return;
}
// bootstrap 初始化
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
bootstrap.init();
}
checkAndUpdateSubConfigs();
//init serivceMetadata
// 初始化 serviceMetadata信息
serviceMetadata.setVersion(version);
serviceMetadata.setGroup(group);
serviceMetadata.setDefaultGroup(group);
serviceMetadata.setServiceType(getActualInterface());
serviceMetadata.setServiceInterfaceName(interfaceName);
// TODO, uncomment this line once service key is unified
serviceMetadata.setServiceKey(URL.buildKey(interfaceName, group, version));
checkStubAndLocal(interfaceClass);
ConfigValidationUtils.checkMock(interfaceClass, this);
Map<String, String> map = new HashMap<String, String>();
... 服务参数解析,保存到 map 中
// 将参数信息保存到 serviceMetadata#attachments 中
serviceMetadata.getAttachments().putAll(map);
// 注册消费者
ServiceRepository repository = ApplicationModel.getServiceRepository();
ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass);
repository.registerConsumer(
serviceMetadata.getServiceKey(),
attributes,
serviceDescriptor,
this,
null,
serviceMetadata);
// 创建提供者代理类
ref = createProxy(map);
serviceMetadata.setTarget(ref);
serviceMetadata.addAttribute(PROXY_CLASS_REF, ref);
// 保存提供者代理类的引用关系
repository.lookupReferredService(serviceMetadata.getServiceKey()).setProxyObject(ref);
initialized = true;
// dispatch a ReferenceConfigInitializedEvent since 2.7.4
// 分发 ReferenceConfigInitializedEvent 事件
dispatch(new ReferenceConfigInitializedEvent(this, invoker));
}
以上:内容部分参考
https://blog.csdn.net/weixin_38308374/article/details/105938319
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正