上篇文章,介绍了Dubbo增强SPI与动态增强原理,用来当作学习Dubbo源码的“开胃菜”。作为RPC框架,如何实现服务的发布和调用才是重中之重,本篇文章将会带大家一起学习下远程服务发布的过程。
一、Demo
public class Application {
public static void main(String[] args) throws Exception {
ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
bootstrap
.application(new ApplicationConfig("dubbo-demo-api-provider"))
.registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
.service(service)
.start()
.await();
}
}
Dubbo提供了多种服务暴露方式,本文以API的方式着手分析,更简单明了一些。
二、时序图
以上,则是服务注册到注册中心的全过程。
三、步骤详解
1.步骤1-start
首先通过DubboBootstrap.start方法,触发服务初始化,可以看到其内部是调用了exportServices方法来进行服务的暴露
public DubboBootstrap start() {
if (started.compareAndSet(false, true)) {
initialize();
// 1. export Dubbo Services
exportServices();
// Not only provider register, TODO
if (!isOnlyRegisterProvider() || hasExportedServices()) {
// 2. export MetadataService
exportMetadataService();
//3. Register the local ServiceInstance if required
registerServiceInstance();
}
// 步骤17完成服务引用,留到客户端流程讲解时再分析
referServices();
}
return this;
}
2.步骤2-exportServices
exportServices方法,从configManager里获取到需要暴露的服务,循环处理每一个ServiceConfig,然后调用其export方法,同时判断当前是否需要异步export,进而选择不同的处理方式。
private void exportServices() {
configManager.getServices().forEach(sc -> {
// TODO, compatible with ServiceConfig.export()
ServiceConfig serviceConfig = (ServiceConfig) sc;
serviceConfig.setBootstrap(this);
if (exportAsync) {
ExecutorService executor = executorRepository.getServiceExporterExecutor();
Future<?> future = executor.submit(() -> {
sc.export();
});
asyncExportingFutures.add(future);
} else {
sc.export();
exportedServices.add(sc);
}
});
}
3.步骤3-export
ServiceConfig的export方法支持延迟暴露rpc服务,个人理解有的rpc服务可能依赖一些前置的数据处理,比如要load一些数据到内存里,需要一定的准备时间,服务启动的时候,不能立刻对外提供服务,就需要延迟去暴露自己的rpc服务。可以通过ServiceConfig的setDelay方法设置延迟发布时间。没有设置延迟时间,则直接调用doExport
public synchronized void export() {
... ...
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
// 执行export动作
doExport();
}
}
4.步骤4-doExport
doExport方法内部,调用了doExportUrls进行服务的发布。发布完成后,调用dispatch发布了export事件。
protected synchronized void doExport() {
... ...
doExportUrls();
// dispatch a ServiceConfigExportedEvent since 2.7.4
// 步骤15
dispatch(new ServiceConfigExportedEvent(this));
}
该事件,最终被ServiceNameMappingListener处理,调用ServiceNameMapping.map方法。
public class ServiceNameMappingListener implements EventListener<ServiceConfigExportedEvent> {
private final ServiceNameMapping serviceNameMapping = getDefaultExtension();
@Override
public void onEvent(ServiceConfigExportedEvent event) {
ServiceConfig serviceConfig = event.getServiceConfig();
List<URL> exportedURLs = serviceConfig.getExportedUrls();
exportedURLs.forEach(url -> {
... ...
// 步骤16
serviceNameMapping.map(serviceInterface, group, version, protocol);
});
}
}
查看ServiceNameMapping默认实现DynamicConfigurationServiceNameMapping发现,其内部是调用DynamicConfiguration的publishConfig方法,实现config的发布。DynamicConfiguration实现如下
可以看到,不同的注册中心对应不同的实现类。
5.步骤5-doExportUrls
loadRegistries方法用来加载所有的服务注册中心对象,在Dubbo中,一个服务可以被注册到多个注册中心。遍历所有协议,进行服务发布
private void doExportUrls() {
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);
repository.registerService(pathKey, interfaceClass);
serviceMetadata.setServiceKey(pathKey);
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
6.步骤6-doExportUrlsFor1Protocol
代码有点长,就不贴了,简单总结下流程:
- 解析MethodConfig配置
- 如果为泛型调用,则设置为泛型类型
- 不是泛型调用,则获取拼接URL的参数
- 拼接URl对象
- 导出服务,Dubbo导出分为本地导出和远程导出,本地导出使用了inJvm协议,不开启端口,不发起远程调用,只在jvm内部进行关联。
最终调用protocol.export方法进行服务的暴露,前文讲解SPI的时候分析过,此时的protocol实际为Protocol$Adaptive类,内部会根据服务的类型选择使用InJvmProtocol还是DubboProtocol,但由于Dubbo扩展点都使用了Wrapper增强,因此需要经历层层调用才会调用到RegistryProtocol的export方法。
7.步骤13-export
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
... ...
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
register(registryUrl, registeredProviderUrl);
}
// Deprecated! Subscribe to override rules in 2.6.x or before.
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}
doLocalExport方法最终启动了NettyServer进行服务监听。服务启动后,调用register方法,把服务相关信息注册到对应的注册中心。
四、小节
由于Dubbo服务暴露的流程比较长,本文主要分析了Dubbo服务启动到Netty启动前的主要流程,由于篇幅限制,很多细节都没展开讲,如步骤17refreshServce方法、Dubbo的event处理机制、注册中心实现等等,心急吃不了热豆腐,后续会逐渐为大家一一详解。大家如果有什么疑问也可以在评论区进行讨论交流。