前言:
本文主要讲解dubbo集成spring进行的服务暴露过程。大致流程我从处理标签开始讲解处理,从spring解析我们dubbo的xml文件到暴露服务的一个大致过程,(注:对spring的扩展点有一点了解。)后续流程发布细节讲持续更新发布。文中如有错误点请各位大佬指出,当前文章也只是本人在阅读源码当中的一个记录点。下面我们进入正文。
服务导出简要流程如下:
- 读取配置信息生成
ServiceBean ->
ServiceConfig.export()- 服务注册根据配置信息将服务以及相关信息注册到注册中心->RegistryProtocol.export()
- 根据配置的协议不同,启动Netty或Tomcat
- Dubbo服务提供者启动监听动态配置修改
- 服务导出完成,发布导出完成事件
DubboApplicationListenerRegistrar集成于spring的ApplicationContextAware接口。这一步注册监听器。着重讲解下
DubboBootstrapApplicationListener
监听器,内部内部初始化了dubbo的启动类
DubboBootstrap
。
其中
DubboBootstrapApplicationListener
的监听器会由spring来进行对应的回调,在回调的时候进行了对应的服务注册。其中使用到了启动类
DubboBootstrap
启动类的主要作用如下:
- 初始化
configManager
和
environment
对象。- 注册
DubboShutdownHook
钩子,实现优雅停机。- 更新配置中心
ConfigCenterBean
对象对应的值。- 加载并更新远程配置。
- 检查
configManager
对象各个配置对象是否合法。- 加载并初始化元数据中心。
- Dubbo服务导出。
启动类
DubboBootstrap
内置2个关键配置类
ConfigManager
,
Environment
这2个类都是于整个dubbo服务的配置信息。核心暴露方法为
DubboBootstrap.start()
public DubboBootstrap start() {
if (started.compareAndSet(false, true)) {
// ......省略代码
initialize();
// ......省略代码
exportServices();
}
return this;
}
当前我们主要关注的是
start()
方法内部的
initialize()和exportServices()方法,该2个方法和我们暴露服务的过程息息相关,具体细致的源码各位可以GitHub拉取对应源码进行阅读。
initialize()该方法为Dubbo初始化配置、环境、元数据等,具体方法如下:
public void initialize() {
if (!initialized.compareAndSet(false, true)) {
return;
}
// 初始化环境,就是我们读取到的配置文件信息,ConfigManager类和Environment
ApplicationModel.initFrameworkExts();
// 开启配置中心
startConfigCenter();
// 加载远程配置详细
loadRemoteConfigs();
// 验证全局配置是否合法
checkGlobalConfigs();
// @since 2.7.8,开启元数据中心
startMetadataCenter();
// 初始化元数据中心
initMetadataService();
if (logger.isInfoEnabled()) {
logger.info(NAME + " has been initialized!");
}
}
上面也有介绍到我们对应的
configManager类,其中该类就保存着我们解析过后的配置信息,不管是标签也好还是注解也好,所有的配置信息都会存储在该管理器内部,顾名思义。内部由一个map进行存储着所有的配置信息。
final Map<String, Map<String, AbstractConfig>> configsCache = newMap();
完成上述步骤我们整个的dubbo所需要的环境是已经准备好了,下面就是我们注册服务和暴露服务的一个部分了。
exportServices()该方法主要用于暴露服务,其中注册,导出等一系列核心逻辑都在该方法完成。
private void exportServices() {
// 从配置管理器中获取所有关于暴露服务的配置信息类
// <dubbo:service interface="org.apache.dubbo.demo.DemoService" timeout="3000" ref="demoService" registry="myRegistry, ztoRegistry"/>
configManager.getServices().forEach(sc -> {
// 服务配置类,顾名思义存储我们服务暴露的配置信息,其实该类为ServiceBean->ServiceConfig类
ServiceConfig serviceConfig = (ServiceConfig) sc;
serviceConfig.setBootstrap(this);
// 是否开启异步导出服务,其实都是调用exportService来导出我们的服务
if (exportAsync) {
ExecutorService executor = executorRepository.getServiceExporterExecutor();
Future<?> future = executor.submit(() -> {
try {
exportService(serviceConfig);
} catch (Throwable t) {
logger.error("export async catch error : " + t.getMessage(), t);
}
});
asyncExportingFutures.add(future);
} else {
// 导出服务(暴露服务)
exportService(serviceConfig);
}
});
}
private void exportService(ServiceConfig sc) {
if (exportedServices.containsKey(sc.getServiceName())) {
throw new IllegalStateException("There are multiple ServiceBean instances with the same service name: [" +
sc.getServiceName() + "], instances: [" +
exportedServices.get(sc.getServiceName()).toString() + ", " +
sc.toString() + "]. Only one service can be exported for the same triple (group, interface, version), " +
"please modify the group or version if you really need to export multiple services of the same interface.");
}
// 导出暴露服务
sc.export();
exportedServices.put(sc.getServiceName(), sc);
}
ServiceConfig.export()进行服务响应的暴露,在暴露前针对默认的配置进行对应的检查。检查服务
checkAndUpdateSubConfigs()
,元数据的处理
initServiceMetadata()
,和争对服务的导出
doExport()
方法。源码如下:
@Override
public synchronized void export() {
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
// compatible with api call.
if (null != this.getRegistry()) {
bootstrap.registries(this.getRegistries());
}
bootstrap.initialize();
}
// 对默认配置进行检查,某些配置没有提供时,提供缺省值
checkAndUpdateSubConfigs();
// 初始化元数据的一些信息
initServiceMetadata(provider);
// 设置元数据信息,服务类型,服务引用
serviceMetadata.setServiceType(getInterfaceClass());
serviceMetadata.setTarget(getRef());
serviceMetadata.generateServiceKey();
if (!shouldExport()) {
return;
}
// 是否异步导出
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(() -> {
try {
// Delay export server should print stack trace if there are exception occur.
this.doExport();
} catch (Exception e) {
logger.error("delay export server occur exception, please check it.", e);
}
}, getDelay(), TimeUnit.MILLISECONDS);
} else {
// 导出服务
doExport();
}
exported();
}
这里我们重点讲解
doExport()
内部的
doExportUrls()
方法,该方法才是真正的导出服务的核心。
private void doExportUrls() {
// 所有的注册中心地址信息
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
int protocolConfigNum = protocols.size();
// 所有的协议,这里涉及到多协议多注册的情况
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.
repository.registerService(pathKey, interfaceClass);
// 暴露服务,这里会核心会创建Invoker和Exporter
doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum);
}
}
这里我们需要着重讲解
doExportUrlsFor1Protocol()
方法。该方法内部核心就是创建一个可执行体
Invoker
对象和发布服务创建出
Exporter
对象。其中内部主要作用就是为一下几点:
1.本地服务导出:其内部根据URL 中Protocol类型为 injvm,会选择
InjvmProtocol
方法为
exportLocal(url);
2.远程服务导出 & 有注册中心:其内部根据URL 中 Protocol 类型为 registry,会选择
RegistryProtocol
3.远程服务导出 和没有注册中心:根据服务协议头类型判断,我们这里假设是 dubbo ,则会选择
DubboProtocol
核心的部分也就是 RegistryProtocol#export 、 InjvmProtocol#export 、DubboProtocol#export 三个方法的执行过程。后续文章我将更加的细致的讲解其三个的暴露过程。
RegistryProtocol#export
服务注册
DubboProtocol#export
服务暴露,比如启动 了Netty 服务,让提供者获得网络通信的能力也在内部进行处理
InjvmProtocol#export
本地服务暴露
doExportUrlsFor1Protocol()
方法内部的核心代码部分:
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass,
registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
首先我们来讲解下Invoker,其实在得到Invoker对象,已经能够调用到我们对应的服务,它个人被我们理解成一个可执行体,最终不管rpc框架如何封装和远程调用发起到服务端,都是围绕Invoker对象来处理。因为他内部存储着我们真正的引用对象。值得讲解的一下是,上述代码获取到的invoker对象是经历过重重封装的。
扩展知识:其内部涉及到的设计模式有代理模式,责任链模式,装饰者模式,其中内部的拦截器也是在内部封装存储着,就使用到了责任链模式,内部多个拦截器形成了一个链条各司其职的拦截。当然装饰者模式在dubbo源码中随处可见,比如spi机制获取到的装载对象。
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
其次我们讲解下Exporter对象,当前在执行
PROTOCOL.export(wrapperInvoker)
方法是整个dubbo的核心可以这样理解,当前方法把我们的服务暴露到了我们的注册中心,和建立了网络通信,监听端口,注册监听器,处理服务调用端的请求。到了这里就完成了一个服务的暴露的完整过程,其实内部的实现细节相当多。这里我们只讲服务的整体暴露流程。
扩展知识:在阅读
PROTOCOL.export(wrapperInvoker)
方法时候一定要先理解dubbo的SPI机制,不然在这里会找不到头脑,我也是在读这部分的源码途中被绕来绕去,所以在熟悉SPI机制之后的运行原理才来读该部分源码,才能如鱼得水读懂该源码。
该文章是我对自己看整个dubbo源码的一个梳理,如有什么有误的地方还请各位大佬指出。谢谢啦。整体执行流程如下: