Dubbo3.0服务暴露过程
聊到服务暴露,首先要谈的就是Dubbo一个重要的领域模型 - URL
。
对于 dubbo 中的 URL,有人理解为配置总线,有人理解为统一配置模型,说法虽然不同,但都是在表达一个意思,这样的 URL 在 dubbo 中被当做是公共契约,所有扩展点参数都包含 URL 参数,URL 作为上下文信息贯穿整个扩展点设计体系
使用统一的URL模型好处也是显而易见的
首先URL可以贯穿所有模块的扩展点,各个模块都可以将它作为参数的表达式,简化并且统一了概念,降低了代码的理解成本,其次URL的扩展性强,因为URL可以理解为一个参数的集合(类似于Map),当我们在扩展代码的时候,可以自由的追加参数到URL中,而不需要改变方法的入参、反参的结构
URL
组成此 URL 对象的具体参数如下:
- protocol:一般是 dubbo 中的各种协议 如:dubbo thrift http zk
- username/password:用户名/密码
- host/port:主机/端口
- path:接口名称
- parameters:参数键值对
模块发布器
服务扫描加载过程在 服务扫描 已经解析过了
可以知道一个服务最终会被加载成ServiceBean进行暴露,其暴露服务的主要方法是export()
,那什么时候调用他呢?
这里就要讲到DefaultModuleDeployer
,他负责了务的暴露和服务的引用
,实现ApplicationListener
接口,监听着 Spring 的容器刷新完成事件,当整个Spring IOC容器refersh完成之后,会触发服务的暴露和服务的引用。
当监听到容器刷新完成的事件后,会调用DefaultModuleDeployer
的start()
方法,来看下这个方法
public Future start() throws IllegalStateException {
// 初始化 applicationDeployer
applicationDeployer.initialize();
return startSync();
}
applicationDeployer初始化包括以下
- 注册JVM钩子方法,用于应用关闭时做dubbo相关的销毁行为
- 载dubbo相关的配置文件
- 启动dubbo三大中心的其中两个,分别是 配置中心、元数据中心
- 加载dubbo相关的配置文件
来看下startSync方法
private synchronized Future startSync() throws IllegalStateException {
//模块发布器已经停止或者启动失败则直接抛出异常返回
if (isStopping() || isStopped() || isFailed()) {
throw new IllegalStateException(getIdentifier() + " is stopping or stopped, can not start again");
}
try {
if (isStarting() || isStarted()) {
return startFuture;
}
// 切换模块为启动模式,并发送一些启动事件
onModuleStarting();
//模块发布器进行初始化
initialize();
// 暴露服务
exportServices();
// prepare application instance
// exclude internal module to avoid wait itself
if (moduleModel != moduleModel.getApplicationModel().getInternalModule()) {
applicationDeployer.prepareInternalModule();
}
// 引用服务(饿汉模式)
referServices();
// 非异步启动直接标记为已启动
if (asyncExportingFutures.isEmpty() && asyncReferringFutures.isEmpty()) {
onModuleStarted();
} else {
// 异步启动则等待服务暴露和服务引用完成
frameworkExecutorRepository.getSharedExecutor().submit(() -> {
try {
// wait for export finish
waitExportFinish();
// wait for refer finish
waitReferFinish();
} catch (Throwable e) {
logger.warn("wait for export/refer services occurred an exception", e);
} finally {
onModuleStarted();
}
});
}
} catch (Throwable e) {
onModuleFailed(getIdentifier() + " start failed: " + e, e);
throw e;
}
return startFuture;
}
服务引用可以参考如下链接 服务引用
接下来看看服务暴露的启动代码
private void exportServices() {
// 遍历所有需要暴露的服务,然后进行引用
for (ServiceConfigBase sc : configManager.getServices()) {
exportServiceInternal(sc);
}
}
private void exportServiceInternal(ServiceConfigBase sc) {
ServiceConfig<?> serviceConfig = (ServiceConfig<?>) sc;
if (!serviceConfig.isRefreshed()) {
serviceConfig.refresh();
}
// 服务已经暴露的话直接跳过
if (sc.isExported()) {
return;
}
// 是否允许异步发布服务
if (exportAsync || sc.shouldExportAsync()) {
// 使用线程池发布服务
ExecutorService executor = executorRepository.getServiceExportExecutor();
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
if (!sc.isExported()) {
// 服务暴露
sc.export();
exportedServices.add(sc);
}
} catch (Throwable t) {
logger.error(CONFIG_FAILED_EXPORT_SERVICE, "", "", "Failed to async export service config: " + getIdentifier() + " , catch error : " + t.getMessage(), t);
}
}, executor);
asyncExportingFutures.add(future);
} else {
if (!sc.isExported()) {
// 非异步发布则直接暴露服务
sc.export();
exportedServices.add(sc);
}
}
}
需要暴露的所有服务的来源即是
ServiceAnnotationPostProcessor
里扫描的生成的所有的ServiceConfig
服务暴露源码解析
从图中可以看到整个ServiceBean的继承关系,每一层的大致作用如下
- AbstractConfig: 封装了很多配置(service,application,reference)的基础信息,提供了公共的配置参数解析以及公共的方法行为,是dubbo中所有配置的最终基类
- AbstractMethodConfig: 封装了方法级别的相关属性
- AbstarctInterfaceConfig: 封装了接口级别的相关属性,主要属性包括注册中心、接口名称、接口版本、接口分组等service和reference都必须得相关接口信息
- AbstractServiceConfig、ServiceConfigBase、ServiceConfig: 封装了服务暴露的相关属性以及方法
- ServiceBean: 作为服务暴露的真正被创建的对象,是一个基于Spring的FactoryBean,所有通过@DubboService定义的服务都将被封装成一个个ServiceBean对象然后进行暴露
服务导出的入口方法是ServiceConfig
中的exported方法,该方法被定义在ServiceConfigBase
中,是一个抽象方法,实现主要是由ServiceConfig
来完成
服务暴露步骤简介
服务暴露的流程可以简化为以下几点
- 服务配置检查,由AbstarctConfig触发,经过AbstactMethod、AbstractInterfaceConfig、ServiceConfig,主要作用是在服务暴露之前确保所有相关的配置已经全部都被加载完成
- 加载注册中心,如果没有在服务上配置要发布的配置中心,则会加载使用默认的配置中心,确保进行remote发布时有对应的服务的注册地址
- 根据注册中心和服务协议构建URL
- 如果配置的不是remote,则在本地导出服务
- 如果配置的是remote,则根据对应的协议进行服务导出
- 不管是不是remote,都会先包装成Invoker然后进行服务的导出,导出完成后会生成对应的exporter
- 服务导出完成后将服务信息注册到注册中心
服务配置导出服务模板方法
先来看下ServiceConfig中的export()
模版方法
public void export() {
if (this.exported) {
return;
}
// ensure start module, compatible with old api usage
getScopeModel().getDeployer().start();
synchronized (this) {
// 检查服务是否导出过,导出过则跳过
if (this.exported) {
return;
}
// 刷新服务配置
if (!this.isRefreshed()) {
this.refresh();
}
if (this.shouldExport()) {
this.init();
if (shouldDelay()) {
// 延迟发布
doDelayExport();
} else {
// 发布服务
doExport();
}
}
}
}
服务暴露前的初始化方法
public void init() {
if (this.initialized.compareAndSet(false, true)) {
// 加载服务监听器
ExtensionLoader<ServiceListener> extensionLoader = this.getExtensionLoader(ServiceListener.class);
this.serviceListeners.addAll(extensionLoader.getSupportedExtensionInstances());
}
// 初始化元数据,例如版本号,服务分组信息等
initServiceMetadata(provider);
// 设置服务接口类
serviceMetadata.setServiceType(getInterfaceClass());
// 设置服务接口实例
serviceMetadata.setTarget(getRef());
serviceMetadata.generateServiceKey();
}
执行服务导出
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
if (exported) {
return;
}
//服务路径 为空则设置为接口的全限定名
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
// 执行服务的导出
doExportUrls();
// 导出完成后进行相关监听器的回调
exported();
}
服务导出URL配置
private void doExportUrls() {
// 模块服务存储库
ModuleServiceRepository repository = getScopeModel().getServiceRepository();
ServiceDescriptor serviceDescriptor;
//ref为服务实现类型
final boolean serverService = ref instanceof ServerService;
if (serverService) {
serviceDescriptor = ((ServerService) ref).getServiceDescriptor();
repository.registerService(serviceDescriptor);
} else {
// 大部分服务暴露的都是走这里的逻辑,即自己定义的服务
// 这个注册不是向注册中心注册 这个是解析服务接口将服务方法等描述信息存放在了服务存储ModuleServiceRepository类型对象的成员变量services中
serviceDescriptor = repository.registerService(getInterfaceClass());
}
// 生成服务提供者模型,型 封装了一些提供者需要的就基本属性同时内部解析封装方法信息 ProviderMethodModel 列表 , 服务标识符 格式group/服务接:版本号
providerModel = new ProviderModel(serviceMetadata.getServiceKey(),
ref,
serviceDescriptor,
getScopeModel(),
serviceMetadata, interfaceClassLoader);
// Compatible with dependencies on ServiceModel#getServiceConfig(), and will be removed in a future version
providerModel.setConfig(this);
providerModel.setDestroyRunner(getDestroyRunner());
//模块服务存储库存储提供者模型对象ModuleServiceRepository
repository.registerProvider(providerModel);
//获取配置的注册中心列表 ,同时将注册中心配置转URL (在Dubbo中URL就是配置信息的一种形式)
//这里会获取到两个 由dubbo.application.register-mode 双注册配置决定,默认是双注册
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);
// stub service will use generated service name
if (!serverService) {
//模块服务存储库ModuleServiceRepository存储服务接口信息
repository.registerService(pathKey, interfaceClass);
}
//根据协议导出配置到注册中心
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
providerModel.setServiceUrls(urls);
}
应用级和接口级服务注册地址获取
这里看一下服务的注册中心获取,涉及到双注册
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
来看下loadRegistries()方法
public static List<URL> loadRegistries(AbstractInterfaceConfig interfaceConfig, boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<>();
// 获取应用级配置信息
ApplicationConfig application = interfaceConfig.getApplication();
// 获取所有注册中心
List<RegistryConfig> registries = interfaceConfig.getRegistries();
if (CollectionUtils.isNotEmpty(registries)) {
// 遍历所有注册中心生成注册中心URL
for (RegistryConfig config : registries) {
// try to refresh registry in case it is set directly by user using config.setRegistries()
if (!config.isRefreshed()) {
config.refresh();
}
// 获取注册中心ip地址
String address = config.getAddress();
if (StringUtils.isEmpty(address)) {
address = ANYHOST_VALUE;
}
// 检查注册中心是否有效
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
// 这里就是生成注册中心URL的整个方法了,生成的具体的url可以看下面的注册中心URL参数解析
Map<String, String> map = new HashMap<String, String>();
AbstractConfig.appendParameters(map, application);
AbstractConfig.appendParameters(map, config);
map.put(PATH_KEY, RegistryService.class.getName());
AbstractInterfaceConfig.appendRuntimeParameters(map);
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(extractRegistryType(url))
.setScopeModel(interfaceConfig.getScopeModel())
.build();
// provider delay register state will be checked in RegistryProtocol#export
if (provider || url.getParameter(SUBSCRIBE_KEY, true)) {
registryList.add(url);
}
}
}
}
}
// 兼容接口级的发布方式的入口,即应用级和接口发布的兼容
return genCompatibleRegistries(interfaceConfig.getScopeModel(), registryList, provider);
}
来看下genCompatibleRegistries()方法
private static List<URL> genCompatibleRegistries(ScopeModel scopeModel, List<URL> registryList, boolean provider) {
List<URL> result = new ArrayList<>(registryList.size());
registryList.forEach(registryURL -> {
if (provider) {
// 配置了service_registry_protocal协议的走这个逻辑
String registerMode;
if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) {
// 获取
registerMode = registryURL.getParameter(REGISTER_MODE_KEY, ConfigurationUtils.getCachedDynamicProperty(scopeModel, DUBBO_REGISTER_MODE_DEFAULT_KEY, DEFAULT_REGISTER_MODE_INSTANCE));
if (!isValidRegisterMode(registerMode)) {
registerMode = DEFAULT_REGISTER_MODE_INSTANCE;
}
result.add(registryURL);
//这里配置的就是应用级配置 则先添加应用级地址,再根据条件判断是否添加接口级注册中心地址
if (DEFAULT_REGISTER_MODE_ALL.equalsIgnoreCase(registerMode)
&& registryNotExists(registryURL, registryList, REGISTRY_PROTOCOL)) {
URL interfaceCompatibleRegistryURL = URLBuilder.from(registryURL)
.setProtocol(REGISTRY_PROTOCOL)
.removeParameter(REGISTRY_TYPE_KEY)
.build();
result.add(interfaceCompatibleRegistryURL);
}
} else {
// 正常情况下我们会走这段逻辑
registerMode = registryURL.getParameter(REGISTER_MODE_KEY, ConfigurationUtils.getCachedDynamicProperty(scopeModel, DUBBO_REGISTER_MODE_DEFAULT_KEY, DEFAULT_REGISTER_MODE_ALL));
if (!isValidRegisterMode(registerMode)) {
registerMode = DEFAULT_REGISTER_MODE_INTERFACE;
}
// 上面的registryUrl是接口级注册URL,如果dubbo.application.registy-mode=all | instance时,添加应用级注册URL
if ((DEFAULT_REGISTER_MODE_INSTANCE.equalsIgnoreCase(registerMode) || DEFAULT_REGISTER_MODE_ALL.equalsIgnoreCase(registerMode))
&& registryNotExists(registryURL, registryList, SERVICE_REGISTRY_PROTOCOL)) {
URL serviceDiscoveryRegistryURL = URLBuilder.from(registryURL)
.setProtocol(SERVICE_REGISTRY_PROTOCOL)
.removeParameter(REGISTRY_TYPE_KEY)
.build();
result.add(serviceDiscoveryRegistryURL);
}
// 如果时接口级的注册或者dubbo.application.registy-mode=al,添加接口注册URL
if (DEFAULT_REGISTER_MODE_INTERFACE.equalsIgnoreCase(registerMode) || DEFAULT_REGISTER_MODE_ALL.equalsIgnoreCase(registerMode)) {
result.add(registryURL);
}
}
FrameworkStatusReportService reportService = ScopeModelUtil.getApplicationModel(scopeModel).getBeanFactory().getBean(FrameworkStatusReportService.class);
reportService.reportRegistrationStatus(reportService.createRegistrationReport(registerMode));
} else {
result.add(registryURL);
}
});
return result;
}
这个方法是根据服务注册模式来判断使用接口级注册地址还是应用级注册地址分别如下所示: 配置信息: dubbo.application.register-mode 配置值:
- instance - 应用级注册
- interface - 接口级注册
- all - 双注册
注册中心URL参数解析
组装注册中心RegistryURL比较简单,就是将注册中心转成转换成通用的模型URL,我们先来看下RegistryURL长什么样子
- 接口级注册URL
registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=admin&client=curator&dubbo=2.0.2&metadata-service-protocol=tri&pid=75068&qos.enable=false®istry=zookeeper&release=3.1.1×tamp=1672654920215
- 应用级注册URL
service-discovery-registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=admin&client=curator&dubbo=2.0.2&metadata-service-protocol=tri&pid=75068&qos.enable=false®istry=zookeeper&release=3.1.1×tamp=1672654920215
URL中展现的内容主要是:
- 第一部分是协议,分为registry和service-discovery-registry
- 第二部分是host+port,表示注册中心的地址
- 第三部分是path,为服务接口的全限定名称
- 最后是整个URL的参数集合,里面主要包含了应用名称、连接注册中心所需要使用的客户端、注册中心类型等
导出服务至本地或注册中心
先来看下服务导出的入口
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 获取协议参数部分数据
Map<String, String> map = buildAttributes(protocolConfig);
// 移除空值,简化配置
map.keySet().removeIf(key -> StringUtils.isEmpty(key) || StringUtils.isEmpty(map.get(key)));
//协议配置放到元数据对象中
serviceMetadata.getAttachments().putAll(map);
// 议配置 + 默认协议配置转URL类型的配置存储,即真正暴露的服务协议URL,参考下方的URL信息
URL url = buildUrl(protocolConfig, map);
// 导出服务
exportUrl(url, registryURLs);
}
服务URL的组装和注册中心的类似,都是生成统一模型URL,我们来看下ServiceURL长什么样子
tri://169.254.10.111:50051/cn.amberdata.admin.facade.UnitFacade?anyhost=true&application=admin&background=false&bind.ip=169.254.10.111&bind.port=50051&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=dev-donghua-dm&interface=cn.amberdata.admin.facade.UnitFacade&loadbalance=random&metadata-service-protocol=tri&methods=getParentArchiveUnit,getUnitListByParentId,getUnitListLikeName,getStatistics,listChildUnitByCode,getUnitByCode,getUnitCount,updateUnitFonds,findAllArchives,getUnitByTag,getAllUnitByType,listAllUnit,getUnitsByTagAndUserId,getUnitDetail,listParentUnitByCode&pid=75221&qos.enable=false&release=3.1.1&revision=3.1.2&side=provider&timeout=50000×tamp=1672655522052
URL中展现的内容主要是:
- 第一部分是服务的协议,表示最终以什么形式暴露出去
- 第二部分是服务的地址,表示服务暴露地址,本质上是解析本机的ip+默认的协议端口
- 第三部分是path,表示当前服务的接口
- 最后是整个URL的参数集合,里面主要包含了服务的地址信息、服务接口信息、服务中包含的所有方法等
服务导出
上面介绍了URL的组装,当我们有了注册中心的URL和服务的URL之后,我们就可以进行服务的导出了。
服务导出的主要步骤可以简化为:
- 判断服务导出方式是本地导出还是远程导出
- 不管是本地导出还是远程导出,最终都会根据url获取对应的Invoker
- 获取完Invoker后会转换成Exporter然后缓存起来
private void exportUrl(URL url, List<URL> registryURLs) {
// 获取URL的scope,scope是服务暴露范围,用于判断以何种形式暴露服务
String scope = url.getParameter(SCOPE_KEY);
// don't export when none is configured
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// 如果scope不等于remote,则进行本地服务暴露
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
exportLocal(url);
}
// 如果scope不等于local,则进行远程服务暴露
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
// 获取额外的协议,也就是同一个URL多种协议配置,例如主协议为tri,ext.potocol为dubbo,则既暴露tri,也暴露dubbo
String extProtocol = url.getParameter("ext.protocol", "");
List<String> protocols = new ArrayList<>();
if (StringUtils.isNotBlank(extProtocol)) {
// export original url
url = URLBuilder.from(url).
addParameter(IS_PU_SERVER_KEY, Boolean.TRUE.toString()).
removeParameter("ext.protocol").
build();
}
// 进行远程服务导出
url = exportRemote(url, registryURLs);
if (!isGeneric(generic) && !getScopeModel().isInternal()) {
// 推送服务信息至注册中心
MetadataUtils.publishServiceDefinition(url, providerModel.getServiceModel(), getApplicationModel());
}
// 将额外的协议配置分隔组装成数组
if (StringUtils.isNotBlank(extProtocol)) {
String[] extProtocols = extProtocol.split(",", -1);
protocols.addAll(Arrays.asList(extProtocols));
}
// 暴露远程服务
for(String protocol : protocols) {
if(StringUtils.isNotBlank(protocol)){
URL localUrl = URLBuilder.from(url).
setProtocol(protocol).
build();
localUrl = exportRemote(localUrl, registryURLs);
if (!isGeneric(generic) && !getScopeModel().isInternal()) {
MetadataUtils.publishServiceDefinition(localUrl, providerModel.getServiceModel(), getApplicationModel());
}
this.urls.add(localUrl);
}
}
}
}
// 缓存已暴露过的协议
this.urls.add(url);
}
本地服务导出
判断URL中的Scope是否等于Remote,如果不是则进行本地服务导出。导出服务前会重新设置一遍URL,更改URL的Potocol为Local,Host为127.0.0.1,Port为0
本地服务永远是以injvm形式暴露
本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Dubbo 的 Filter 链。
private void exportLocal(URL url) {
// 重新组装url,设置injvm的必须参数
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
local = local.setScopeModel(getScopeModel())
.setServiceModel(providerModel);
// 导出服务
doExportUrl(local, false);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
远程服务导出
远程服务导出的基本流程:
- 遍历所有注册中心
- 检查是否有配置监控服务,有则设置到url中
- 检查是否有配置过代理invoker
- 导出服务
private URL exportRemote(URL url, List<URL> registryURLs) {
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) {
url = url.addParameterIfAbsent(SERVICE_NAME_MAPPING_KEY, "true");
}
//if protocol is only injvm ,not register
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
// 检查是否需要暴露监控服务
URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
if (monitorUrl != null) {
url = url.putAttribute(MONITOR_KEY, monitorUrl);
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
if (logger.isInfoEnabled()) {
if (url.getParameter(REGISTER_KEY, true)) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL.getAddress());
} else {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
}
// 服务导出
doExportUrl(registryURL.putAttribute(EXPORT_KEY, url), true);
}
} else {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
// 服务导出
doExportUrl(url, true);
}
return url;
}
为什么Dubbo要做本地服务暴露?
因为服务之间可能出现内部调用的情况,例如多模块之间是同级的,无法在依赖上相互引用,那就可以利用Dubbo进行调用。这个时候使用本地服务的话,就避免了调用时就可以利用JVM避免产生网络层的消耗
获取Invoker并进行服务暴露生成Exporter
private void doExportUrl(URL url, boolean withMetaData) {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
if (withMetaData) {
// 包装成DelegateProviderMetaDataInvoker,是一个装饰类
invoker = new DelegateProviderMetaDataInvoker(invoker, this);
}
Exporter<?> exporter = protocolSPI.export(invoker);
exporters.add(exporter);
}
获取invker是由两个工厂来完成的,分别是JdkProxyFactory和JavassistProxyFacotry
服务暴露完成后会生成对应的Exporter并维护到exporters列表中,用于服务的卸载时能找到已暴露的服务个数并进行卸载
JavassistProxyFactory类型的getInvoker方法
我们直接看默认的类型javassist 对应JavassistProxyFactory代理工厂 获取调用对象
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
try {
// 创建实际服务提供者的代理类型,代理类型后缀为DubboWrap
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
//创建一个匿名内部类对象 继承自AbstractProxyInvoker的Invoker对象
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
} catch (Throwable fromJavassist) {
// try fall back to JDK proxy factory
try {
Invoker<T> invoker = jdkProxyFactory.getInvoker(proxy, type, url);
logger.error("Failed to generate invoker by Javassist failed. Fallback to use JDK proxy success. " +
"Interfaces: " + type, fromJavassist);
// log out error
return invoker;
} catch (Throwable fromJdk) {
logger.error("Failed to generate invoker by Javassist failed. Fallback to use JDK proxy is also failed. " +
"Interfaces: " + type + " Javassist Error.", fromJavassist);
logger.error("Failed to generate invoker by Javassist failed. Fallback to use JDK proxy is also failed. " +
"Interfaces: " + type + " JDK Error.", fromJdk);
throw fromJavassist;
}
}
}
使用协议导出调用对象
Exporter<?> exporter = protocolSPI.export(invoker);
调用链路如下图所示:
协议序列化机制ProtocolSerializationWrapper
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 将服务提供者url注册到存储仓库中
getFrameworkModel(invoker.getUrl().getScopeModel()).getServiceRepository().registerProviderUrl(invoker.getUrl());
return protocol.export(invoker);
}
协议过滤器ProtocolFilterWrapper
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 判断是否是注册中心地址,remote模式为registry或者service-registry-diconvery,即为true,直接走这里的逻辑
if (UrlUtils.isRegistry(invoker.getUrl())) {
return protocol.export(invoker);
}
// 过滤器调用链FilterChainBuilder的扩展对象查询
FilterChainBuilder builder = getFilterChainBuilder(invoker.getUrl());
// 为服务配置对应的过滤器
return protocol.export(builder.buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}
这里的逻辑大家可能有点疑惑,如果是remote模式,那进来URL,UrlUtils.isRegistry(invoker.getUrl())
的判断结果不都是true吗?那什么时候会使用到下面的过滤器呢?
-
injvm模式的服务会直接配置对应的过滤器
-
remote模式的服务
所有需要被暴露的服务在remote模式下都会生成两个URL,一个是注册中心URL,即protocol协议是registry或service-registry-disconvery,另外一个是具体暴露协议的URL,即protocol为dubbo或triple等真正需要暴露的服务的URL
第一部分注册中心URL最终的export调用会走到RegistryProrocol中完成和注册相关的服务的加载,完成之后会获取真正需要本地导出的服务的协议URL,进行本地的服务导出,走的依旧是Protocol#Adaptive#export方法。这个时候就会进入到配置对应服务过滤器的代码中了。
详情可以看下方的RegistryPotocol
生成的FilterChainBuilder对应的类为 DefaultFilterChainBuilder
来看下DefaultFilterChainBuilder#buildInvokerChain方法
@Override
public <T> Invoker<T> buildInvokerChain(final Invoker<T> originalInvoker, String key, String group) {
Invoker<T> last = originalInvoker;
URL url = originalInvoker.getUrl();
List<ModuleModel> moduleModels = getModuleModelsFromUrl(url);
List<Filter> filters;
if (moduleModels != null && moduleModels.size() == 1) {
// 获取所有group为provider的过滤器
filters = ScopeModelUtil.getExtensionLoader(Filter.class, moduleModels.get(0)).getActivateExtension(url, key, group);
} else if (moduleModels != null && moduleModels.size() > 1) {
filters = new ArrayList<>();
List<ExtensionDirector> directors = new ArrayList<>();
for (ModuleModel moduleModel : moduleModels) {
List<Filter> tempFilters = ScopeModelUtil.getExtensionLoader(Filter.class, moduleModel).getActivateExtension(url, key, group);
filters.addAll(tempFilters);
directors.add(moduleModel.getExtensionDirector());
}
filters = sortingAndDeduplication(filters, directors);
} else {
filters = ScopeModelUtil.getExtensionLoader(Filter.class, null).getActivateExtension(url, key, group);
}
// 为invoker依次创建对应的责任链装饰对象
// 倒序拼接,将过滤器的调用对象添加到链表中 最后倒序遍历之后 last节点指向了调用链路链表头节点的对象
if (!CollectionUtils.isEmpty(filters)) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new CopyOfFilterChainNode<>(originalInvoker, next, filter);
}
return new CallbackRegistrationInvoker<>(last, filters);
}
// 返回最终装饰完的Invoker对象
return last;
}
过滤器采用的是责任链模式,服务调用时,会依次调用对应的过滤器
服务质量监控QosProtocolWrapper
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 开启qos服务
startQosServer(invoker.getUrl());
return protocol.export(invoker);
}
private void startQosServer(URL url) {
try {
if (!hasStarted.compareAndSet(false, true)) {
return;
}
boolean qosEnable = url.getParameter(QOS_ENABLE, true);
WireProtocol qosWireProtocol = frameworkModel.getExtensionLoader(WireProtocol.class).getExtension("qos");
if(qosWireProtocol != null) {
((QosWireProtocol) qosWireProtocol).setQosEnable(qosEnable);
}
// 检查当前url是否需要开启qos
if (!qosEnable) {
logger.info("qos won't be started because it is disabled. " +
"Please check dubbo.application.qos.enable is configured either in system property, " +
"dubbo.properties or XML/spring-boot configuration.");
return;
}
String host = url.getParameter(QOS_HOST);
int port = url.getParameter(QOS_PORT, QosConstants.DEFAULT_PORT);
boolean acceptForeignIp = Boolean.parseBoolean(url.getParameter(ACCEPT_FOREIGN_IP, "false"));
Server server = frameworkModel.getBeanFactory().getBean(Server.class);
if (server.isStarted()) {
return;
}
server.setHost(host);
server.setPort(port);
server.setAcceptForeignIp(acceptForeignIp);
// 开启qos服务
server.start();
} catch (Throwable throwable) {
logger.warn("Fail to start qos server: ", throwable);
}
}
协议监听器ProtocolListenerWrapper
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 如果是注册中心url则直接调用
if (UrlUtils.isRegistry(invoker.getUrl())) {
return protocol.export(invoker);
}
// 先导出对象 再创建过滤器包装对象 执行监听器逻辑
return new ListenerExporterWrapper<T>(protocol.export(invoker),
Collections.unmodifiableList(ScopeModelUtil.getExtensionLoader(ExporterListener.class, invoker.getUrl().getScopeModel())
.getActivateExtension(invoker.getUrl(), EXPORTER_LISTENER_KEY)));
}
remote与inJvm调用上的区别
有三个区别,分别如下:
- 在经过协议过滤器ProtocolFilterWrapper时,inJvm会为其配置对应的过滤器,remote则直接导出对象
- 再经过协议监听器ProtocolListenerWrapper,导出完成后,inJvm会为其配置监听器,remote则不会
- 最终调用的协议不同,inJvm为InJvmProtocol,remote为RegistryProtocol
本地暴露协议InjvmProtocol
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 生成inJvmExporter
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
注册中心协议RegistryProtocol
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 获取注册中心URL
URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally
// 获取服务URL,服务URL需要在本地导出
URL providerUrl = getProviderUrl(originInvoker);
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
// the same service. Because the subscribed is cached key with the name of the service, it causes the
// subscription information to cover.
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
Map<URL, NotifyListener> overrideListeners = getProviderConfigurationListener(providerUrl).getOverrideListeners();
overrideListeners.put(registryUrl, overrideSubscribeListener);
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// 在本地导出服务
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// 根据注册中心URL获取注册中心视力
final Registry registry = getRegistry(registryUrl);
// 向注册中心作地址转换
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
// decide if we need to delay publish (provider itself and registry should both need to register)
boolean register = providerUrl.getParameter(REGISTER_KEY, true) && registryUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 向注册中心注册服务
register(registry, registeredProviderUrl);
}
// 标记服务已注册
registerStatedUrl(registryUrl, registeredProviderUrl, register);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
if (!registry.isServiceDiscovery()) {
// Deprecated! Subscribe to override rules in 2.6.x or before.
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
}
//内置监听器通知 这个不是通知消费者的
notifyExport(exporter);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}
RegistryProtocol服务在本地导出
来看下 doLocalExport 方法
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
String key = getCacheKey(originInvoker);
return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
//代码中用的这个protoco对象是dubbo自动生成的适配器对象protocol$Adaptive 适配器对象会根据当前协议的参数来查询具体的协议扩展对象
return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
});
}
上面这个protocol$Adaptive 协议的export导出方法与之前的一样也会经历下面几个过程,具体细节可以参考remote协议的导出:
- ProtocolSerializationWrapper
- ProtocolFilterWrapper
- ProtocolListenerWrapper
- QosProtocolWrapper
- 唯一不同的是我们这里对应的协议扩展类型为TripleProtocol、 接下来来看下TripleProtocol的导出服务export方法实现:
来看下 TripleProtocol
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 获取服务URL
URL url = invoker.getUrl();
checkProtobufVersion(url);
String key = serviceKey(url);
final AbstractExporter<T> exporter = new AbstractExporter<T>(invoker) {
@Override
public void afterUnExport() {
pathResolver.remove(url.getServiceKey());
pathResolver.remove(url.getServiceModel().getServiceModel().getInterfaceName());
// set service status
triBuiltinService.getHealthStatusManager()
.setStatus(url.getServiceKey(), ServingStatus.NOT_SERVING);
triBuiltinService.getHealthStatusManager()
.setStatus(url.getServiceInterface(), ServingStatus.NOT_SERVING);
exporterMap.remove(key);
}
};
exporterMap.put(key, exporter);
invokers.add(invoker);
pathResolver.add(url.getServiceKey(), invoker);
pathResolver.add(url.getServiceModel().getServiceModel().getInterfaceName(), invoker);
// 设置服务健康状态
triBuiltinService.getHealthStatusManager()
.setStatus(url.getServiceKey(), HealthCheckResponse.ServingStatus.SERVING);
triBuiltinService.getHealthStatusManager()
.setStatus(url.getServiceInterface(), HealthCheckResponse.ServingStatus.SERVING);
// init
url.getOrDefaultApplicationModel().getExtensionLoader(ExecutorRepository.class)
.getDefaultExtension()
.createExecutorIfAbsent(url);
// 使用netty为服务开启对应的端口
PortUnificationExchanger.bind(url, new DefaultPuHandler());
optimizeSerialization(url);
return exporter;
}
向注册中心注册服务
这里涉及到接口级注册和应用级注册,双注册原理,参考链接 Dubbo双注册原理
最后
整体来看,服务的暴露由DefaultModuleDeployer
来完成的,DefaultModuleDeployer
包含了ApplicationModuleDeployer
的启动引导,服务的导出以及引入
服务暴露会经历如下几个阶段:
- 组装注册中心URL以及服务本导出URL
- 根据URL上定义的协议进行服务导出
- 将服务注册到对应的注册中心
- 检查服务注册结果
- 发布服务导出完成事件