元数据中心
元数据介绍
服务治理中的元数据(Metadata)指的是服务分组、服务版本、服务名、方法列表、方法参数列表、超时时间等,这些信息将会存储在元数据中心之中。与元数据平起平坐的一个概念是服务的注册信息,即:服务分组、服务版本、服务名、地址列表等,这些信息将会存储在注册中心中。稍微一对比可以发现,元数据中心和注册中心存储了一部分共同的服务信息,例如服务名。两者也有差异性,元数据中心还会存储方法列表即参数列表,注册中心存储了服务地址。上述的概述,体现出了元数据中心和注册中心在服务治理过程中,担任着不同的角色
总结,元数据就是对正常的服务调用过程不造成影响,但是起到描述服务的作用,对我们调试,排查问题起到很大作用
下面我会对每个对比点进行单独分析,以加深对元数据中心的理解。
职责
在 Dubbo 2.7 版本之前,并没有元数据中心的概念,那时候注册信息和元数据都耦合在一起。Dubbo Provider 的服务配置有接近 30 个配置项,排除一部分注册中心服务治理需要的参数,很大一部分配置项仅仅是 Provider 自己使用,不需要透传给消费者;Dubbo Consumer 也有 20 多个配置项。在注册中心之中,服务消费者列表中只需要关注 application,version,group,ip,dubbo 版本等少量配置。这部分数据不需要进入注册中心,而只需要以 key-value 形式持久化存储在元数据中心即可。从职责来看,将不同职责的数据存储在对应的组件中,会使得逻辑更加清晰。
变化频繁度
注册信息和元数据耦合在一起会导致注册中心数据量的膨胀,进而增大注册中心的网络开销,直接造成了服务地址推送慢等负面影响。服务上下线会随时发生,变化的其实是注册信息,元数据是相对不变的。
数据量
由于元数据包含了服务的方法列表以及参数列表,这部分数据会导致元数据要比注册信息大很多。注册信息被设计得精简会直接直接影响到服务推送的 SLA。
数据交互/存储模型
注册中心采用的是 PubSub 模型,这属于大家的共识,所以注册中心组件的选型一般都会要求其有 notify 的机制。而元数据中心并没有 notify 的诉求,一般只需要组件能够提供 key-value 的存储结构即可。
主要使用场景
在服务治理中,注册中心充当了通讯录的职责,在复杂的分布式场景下,让消费者能找到提供者。而元数据中心存储的元数据,主要适用于服务测试、服务 MOCK 等场景,这些场景都对方法列表、参数列表有所诉求。在下面的小节中,我也会对使用场景进行更加详细的介绍。
可用性要求
注册中心宕机或者网络不通会直接影响到服务的可用性,它影响了服务调用的主路径。但一般而言,元数据中心出现问题,不会影响到服务调用,它提供的能力是可被降级的。这也阐释了一点,为什么很多用户在 Dubbo 2.7 中没有配置元数据中心,也没有影响到正常的使用。元数据中心在服务治理中扮演的是锦上添花的一个角色。在组件选型时,我们一般也会对注册中心的可用性要求比较高,元数据中心则可以放宽要求。
元数据得价值
降低地址推送的时延
由于注册中心采用的是 PubSub 模型,数据量的大小会直接影响到服务地址推送时间,不知道你有没有遇到过 No provider available 的报错呢?明明提供者已经启动了,但由于注册中心推送慢会导致很多问题,一方面会影响到服务的可用性,一方面也会增加排查问题的难度。
服务测试 & 服务 MOCK
我在dubbo-admin上一直未能测试成功,一直显示的都是
据说只有使用dubbo版本为2.7.3以前的才能够正常使用。看来dubbo-admin在服务测试这方面做的还不够完善
在 Dubbo 2.7 之前,虽然注册中心耦合存储了不少本应属于元数据的数据,但也漏掉了一部分元数据,例如服务的方法列表,参数列表。这些是服务测试和服务 MOCK 必备的数据,想要使用这些能力,就必须引入元数据中心。例如开源的 Dubbo Admin 就实现了服务测试功能,用户可以在控制台上对已经发布的服务提供者进行功能测试。可能你之前有过这样的疑惑:为什么只有 Dubbo 2.7 才支持了服务测试呢?啊哈,原因就是 Dubbo 2.7 才有了元数据中心的概念。当然,服务 MOCK 也是如此。
其他场景
可以这么理解,任何依赖元数据的功能,都需要元数据中心的支持。其他场景还包括了网关应用获取元数据来进行泛化调用、服务自动化测试等等。再描述一个可能的场景,抛砖引玉。在一次南京 Dubbo Meetup 上,dubbo.js 的作者提及的一个场景,希望根据元数据自动生成 NodeJs 代码,以简化前端的开发量,也是元数据的作用之一。这里就需要发挥各位的想象力了。
元数据存储格式剖析
前面我们介绍了元数据中心的由来以及价值,还是飘在天上的概念,这一节将会让概念落地。元数据是以怎么样一个格式存储的呢?
以 DemoService 服务为例:
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" executes="4500" retries="7" owner="kirito"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
首先观察在 Dubbo 2.6.x 中,注册中心如何存储这个服务的信息:
dubbo://30.5.120.185:20880/com.alibaba.dubbo.demo.DemoService?
anyhost=true&
application=demo-provider&
interface=com.alibaba.dubbo.demo.DemoService&
methods=sayHello&
bean.name=com.alibaba.dubbo.demo.DemoService&
dubbo=2.0.2&executes=4500&
generic=false&owner=kirito&
pid=84228&retries=7&side=provider×tamp=1552965771067
例如 bean.name 和 owner 这些属性,肯定是没必要注册上来的。
接着,我们在 Dubbo 2.7 中使用最佳实践,为 registry 配置 simplified=true:
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" executes="4500" retries="7" owner="kirito"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181" simplified="true" />
<dubbo:metadata-report address="nacos://127.0.0.1:8848"/>
dubbo://30.5.120.185:20880/org.apache.dubbo.demo.api.DemoService?
application=demo-provider&
dubbo=2.0.2&
release=2.7.0&
timestamp=1552975501873
被精简省略的数据不代表没有用了,而是转移到了元数据中心之中,我们观察一下此时元数据中心中的数据:
总结
元数据中心其实就是存储生产者或者消费者存放不必要的一些配置选项的地方, 同样可以用于服务治理,但是却减轻了注册中心的压力
配置中心
我们平常在开发中可能会遇到这样一个场景。假设我们有个对外接口需要动态调整开/关配置,来达到动态控制效果。我们的实现方案可能有:存数据库、写文件等等,同时我们也可以通过集中式、统一配置方式来动态管理,例如:Apollo、Nacos、Zookeeper等提供了比传统数据库存储更强大的功能,例如:多租户、实时同步、分布式等特性。我们可以利用这些配置中心特性对我们的 Dubbo 中的所有配置参数进行统一的动态管理。
说白了就是,我们希望有一个专门提供给dubbo使用得一个配置中心,进行dubbo得一些动态配置,这个配置中心,可以多个应用共用,这和我们项目中配置文件(或者nacos上得配置)上得dubbo的配置并不冲突
配置中心在 Dubbo 中承担两个职责:
- 外部化配置。启动配置的集中式存储 (简单理解为 dubbo.properties 的外部化存储)。
- 服务治理。服务治理规则的存储与通知。
配置中心源码分析
dubbo配置类很多,不同的前缀,相应的值设置到对应的类中
DubboConfigConfiguration
dubbo生产者中的每个服务都是一个ServiceConfig,配置前缀为
dubbo.接口名
例如
dubbo.service.com.xishan.store.usercenter.userapi.facade.UserReadFacade
配置管理类ConfigManager
可以通过ConfigManager获取到不同的配置,也可以通过环境变量刷新配置类属性。
配置刷新
public void refreshAll() {
write(() -> {
// refresh all configs here,
getApplication().ifPresent(ApplicationConfig::refresh);
getMonitor().ifPresent(MonitorConfig::refresh);
getModule().ifPresent(ModuleConfig::refresh);
getProtocols().forEach(ProtocolConfig::refresh);
getRegistries().forEach(RegistryConfig::refresh);
getProviders().forEach(ProviderConfig::refresh);
getConsumers().forEach(ConsumerConfig::refresh);
});
}
public void refresh() {
Environment env = ApplicationModel.getEnvironment();
try {
CompositeConfiguration compositeConfiguration = env.getPrefixedConfiguration(this);
// loop methods, get override value and set the new value back to method
Method[] methods = getClass().getMethods();
for (Method method : methods) {
if (MethodUtils.isSetter(method)) {
try {
String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
// isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig.
if (StringUtils.isNotEmpty(value) && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)) {
method.invoke(this, ClassUtils.convertPrimitive(method.getParameterTypes()[0], value));
}
} catch (NoSuchMethodException e) {
logger.info("Failed to override the property " + method.getName() + " in " +
this.getClass().getSimpleName() +
", please make sure every property has getter/setter method provided.");
}
} else if (isParametersSetter(method)) {
String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
if (StringUtils.isNotEmpty(value)) {
Map<String, String> map = invokeGetParameters(getClass(), this);
map = map == null ? new HashMap<>() : map;
map.putAll(convert(StringUtils.parseParameters(value), ""));
invokeSetParameters(getClass(), this, map);
}
}
}
} catch (Exception e) {
logger.error("Failed to override ", e);
}
}
看的出,对配置的刷新依靠Environment
环境Environment
从各种来源获取数据
内置五个属性
public Environment() {
this.propertiesConfiguration = new PropertiesConfiguration();
this.systemConfiguration = new SystemConfiguration();
this.environmentConfiguration = new EnvironmentConfiguration();
this.externalConfiguration = new InmemoryConfiguration();
this.appExternalConfiguration = new InmemoryConfiguration();
}
PropertiesConfiguration
PropertiesConfiguration是Environment类的成员变量, 主要加载的是dubbo.properties配置文件中的配置信息。
public class PropertiesConfiguration implements Configuration {
public PropertiesConfiguration() {
//SPI机制加载OrderedPropertiesProvider类, 一个都没有加载到, 因为没有配置
ExtensionLoader<OrderedPropertiesProvider> propertiesProviderExtensionLoader = ExtensionLoader.getExtensionLoader(OrderedPropertiesProvider.class);
Set<String> propertiesProviderNames = propertiesProviderExtensionLoader.getSupportedExtensions();
if (propertiesProviderNames == null || propertiesProviderNames.isEmpty()) {
return;
}
List<OrderedPropertiesProvider> orderedPropertiesProviders = new ArrayList<>();
for (String propertiesProviderName : propertiesProviderNames) {
orderedPropertiesProviders.add(propertiesProviderExtensionLoader.getExtension(propertiesProviderName));
}
//order the propertiesProvider according the priority descending
orderedPropertiesProviders.sort((OrderedPropertiesProvider a, OrderedPropertiesProvider b) -> {
return b.priority() - a.priority();
});
//load the default properties
Properties properties = ConfigUtils.getProperties();
//override the properties.
for (OrderedPropertiesProvider orderedPropertiesProvider :
orderedPropertiesProviders) {
properties.putAll(orderedPropertiesProvider.initProperties());
}
ConfigUtils.setProperties(properties);
}
@Override
public Object getInternalProperty(String key) {
return ConfigUtils.getProperty(key);
}
}
实现了Configuration接口, 这个是所有配置类都实现的接口
会加载实现了OrderedPropertiesProvider接口的配置类, 如果加载导入会根据配置的优先级排序下,
使用ConfigUtils.getProperties()方法加载classPath下的dubbo.properties。 当然这个路径可以在系统参数中修改
最后将第1步加载的配置合并到第2步的配置中
但是我运行demo程序,发现这个方法不会走到第2步中,直接在第一步中加载到空白后就返回了。
这是因为,
ExtensionLoader propertiesProviderExtensionLoader = ExtensionLoader.getExtensionLoader(OrderedPropertiesProvider.class);
spi没有指定实现
SystemConfiguration
Environment类的成员变量, 加载系统的一些基本信息。 这个代码中注释中
/**
* FIXME: is this really necessary? PropertiesConfiguration should have already covered this:
这个类的方法很简单就是实现了从系统中获取配置信息的方法。
*
* @See ConfigUtils#getProperty(String)
* @see PropertiesConfiguration
*/
public class SystemConfiguration implements Configuration {
@Override
public Object getInternalProperty(String key) {
return System.getProperty(key);
}
}
逻辑很简单就是从系统中获取配置信息
EnvironmentConfiguration
Environment类的成员变量, 加载系统环境配置信息 。 功能几乎和SystemConfiguration一致,代码也是超级简单。
/**
* Configuration from system environment
*/
public class EnvironmentConfiguration implements Configuration {
@Override
public Object getInternalProperty(String key) {
String value = System.getenv(key);
if (StringUtils.isEmpty(value)) {
value = System.getenv(StringUtils.toOSStyleKey(key));
}
return value;
}
}
VM options 需要以 -D 或 -X 或 -XX 开头,每个参数最好使用空格隔开。
program arguments 每个参数需要以空格隔开。否则将会被识别成一个参数,自己用的时候还得手动处理。
Environment variable 没有前缀,优先级低于 VM options ,即如果VM options 有一个变量和 Environment variable中的变量的key相同,则以VM options 中为准,(如果用命令行启动,这个参数需要在运行java类以前使用 set JAVA_HOME=D:\jdk1.8.0_05 这种方式进行临时修改,这种方式只在当前cmd窗口有效,点击看详情 设置临时的java环境变量)。
java提供了System类的静态方法getenv()和getProperty()用于返回系统相关的变量与属性,**getenv()**方法返回的变量大多于系统相关,
getProperty() 方法返回的变量大多与java程序有关。
System.getenv() 方法是获取指定的环境变量的值。
System.getenv(String name) 接收参数为任意字符串,当存在指定环境变量时即返回环境变量的值,否则返回null。
System.getProperty() 是获取系统的相关属性,包括文件编码、操作系统名称、区域、用户名等,此属性一般由jvm自动获取,不能设置。
System.getProperty(String key) 接收参数为任意字符串,当存在指定属性时即返回属性的值,否则返回null。
具体应用如下:
externalConfiguration和appExternalConfiguration
这两个成员变量的对象是同一种类InmemoryConfiguration, 我们看下InmemoryConfiguration
InmemoryConfiguration就是一个空白的配置类, 他提供了往里面设置配置信息的方法。
/**
* In-memory configuration
*/
public class InmemoryConfiguration implements Configuration {
// stores the configuration key-value pairs
private Map<String, String> store = new LinkedHashMap<>();
@Override
public Object getInternalProperty(String key) {
return store.get(key);
}
/**
* Add one property into the store, the previous value will be replaced if the key exists
*/
public void addProperty(String key, String value) {
store.put(key, value);
}
/**
* Add a set of properties into the store
*/
public void addProperties(Map<String, String> properties) {
if (properties != null) {
this.store.putAll(properties);
}
}
/**
* set store
*/
public void setProperties(Map<String, String> properties) {
if (properties != null) {
this.store = properties;
}
}
// for unit test
public void clear() {
this.store.clear();
}
}
实际分别配置中心的 dubbo group下 dubbo.propertis和 当前应用名下的group的配置文件
org.apache.dubbo.config.bootstrap.DubboBootstrap#prepareEnvironment
private DynamicConfiguration prepareEnvironment(ConfigCenterConfig configCenter) {
if (configCenter.isValid()) {
if (!configCenter.checkOrUpdateInited()) {
return null;
}
DynamicConfiguration dynamicConfiguration = getDynamicConfiguration(configCenter.toUrl());
String configContent = dynamicConfiguration.getProperties(configCenter.getConfigFile(), configCenter.getGroup());
String appGroup = getApplication().getName();
String appConfigContent = null;
if (isNotEmpty(appGroup)) {
appConfigContent = dynamicConfiguration.getProperties
(isNotEmpty(configCenter.getAppConfigFile()) ? configCenter.getAppConfigFile() : configCenter.getConfigFile(),
appGroup
);
}
try {
environment.setConfigCenterFirst(configCenter.isHighestPriority());
environment.updateExternalConfigurationMap(parseProperties(configContent));
environment.updateAppExternalConfigurationMap(parseProperties(appConfigContent));
} catch (IOException e) {
throw new IllegalStateException("Failed to parse configurations from Config Center.", e);
}
return dynamicConfiguration;
}
return null;
}
分别冲,配置中心(如果没有配置配置中心,注册中心就作为配置中心)获取dubbo group下 dubbo.propertis和 当前应用名下的group的配置存到externalConfiguration和appExternalConfiguration中
配置优先级
顺序在注释中已经给出
org.apache.dubbo.common.config.Environment#getPrefixedConfiguration
if (this.isConfigCenterFirst()) {
// The sequence would be: SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
// Config center has the highest priority
prefixedConfiguration.addConfiguration(systemConfiguration);
prefixedConfiguration.addConfiguration(environmentConfiguration);
prefixedConfiguration.addConfiguration(appExternalConfiguration);
prefixedConfiguration.addConfiguration(externalConfiguration);
prefixedConfiguration.addConfiguration(configuration);
prefixedConfiguration.addConfiguration(propertiesConfiguration);
} else {
// The sequence would be: SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration
// Config center has the highest priority
prefixedConfiguration.addConfiguration(systemConfiguration);
prefixedConfiguration.addConfiguration(environmentConfiguration);
prefixedConfiguration.addConfiguration(configuration);
prefixedConfiguration.addConfiguration(appExternalConfiguration);
prefixedConfiguration.addConfiguration(externalConfiguration);
prefixedConfiguration.addConfiguration(propertiesConfiguration);
}
configuration代表的是配置类本身,初始值为项目配置文件中的配置
如果isConfigCenterFirst 配置中心优先
顺序为,系统属性->环境属性->当前应用名group下配置->dubbo group配置->当前配置本身
否则
顺序为,系统属性->环境属性->当前配置本身->当前应用名group下配置->dubbo group配置
动态刷新配置
添加监听器
org.apache.dubbo.common.config.configcenter.wrapper.CompositeDynamicConfiguration#addListener
dubbo-admin就是利用动态刷新的特性对dubbo服务,路由等进行配置进行动态设置
生产者监听器
ServiceConfigurationListener 接口级别 ,监听目录:接口名:version:goup+.configurators
ProviderConfigurationListener应用级别,监听目录:ApplicationConfig的name值+.configurators
消费者监听器
ConsumerConfigurationListener监听目录:ApplicationConfig的name值+.configurators
ReferenceConfigurationListener监听目录:接口名:version:goup+.configurators
TagRouter监听目录:remote.application属性值+.tag-router
ServiceRouter监听目录:接口名:version:goup+.condition-router
AppRouter监听目录:应用名+.condition-router
上面这些配置都有特定的格式一般都不是我们自己取创建配置,而是使用比如dubbo-admin进行创建,动态配置,路由等。
上面这些监听器都是在其构造方法中将自身作为监听器添加到CacheListener的listeners中。
下面简单介绍前四个监听器的作用:
ServiceConfigurationListener:根据修改后的配置,重新发布服务;
ProviderConfigurationListener:根据修改后的配置,重新发布服务;
ReferenceConfigurationListener:根据修改后的配置,重新建立对远程服务的引用;
ConsumerConfigurationListener:根据修改后的配置,重新建立对远程服务的引用。
Route监听到配置变化会自动改变路由规则。
元数据中心源码分析
完整的url
以dubbo协议的url为例
生产者构建逻辑
org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol
消费者构建逻辑
org.apache.dubbo.config.ReferenceConfig#init
大概看出来url中包含的数据有 dubbo的版本信息,暴露服务的当前时间戳,提供者,protocolConfig,metadataReportConfig,接口信息,方法名称等配置的信息都会被添加到url中。
例如
包含的信息很多
注册到注册中心得有那些数据?
org.apache.dubbo.registry.integration.RegistryProtocol#getUrlToRegistry
private URL getUrlToRegistry(final URL providerUrl, final URL registryUrl) {
//The address you see at the registry
if (!registryUrl.getParameter(SIMPLIFIED_KEY, false)) {
return providerUrl.removeParameters(getFilteredKeys(providerUrl)).removeParameters(
MONITOR_KEY, BIND_IP_KEY, BIND_PORT_KEY, QOS_ENABLE, QOS_HOST, QOS_PORT, ACCEPT_FOREIGN_IP, VALIDATION_KEY,
INTERFACES);
} else {
String extraKeys = registryUrl.getParameter(EXTRA_KEYS_KEY, "");
// if path is not the same as interface name then we should keep INTERFACE_KEY,
// otherwise, the registry structure of zookeeper would be '/dubbo/path/providers',
// but what we expect is '/dubbo/interface/providers'
if (!providerUrl.getPath().equals(providerUrl.getParameter(INTERFACE_KEY))) {
if (StringUtils.isNotEmpty(extraKeys)) {
extraKeys += ",";
}
extraKeys += INTERFACE_KEY;
}
String[] paramsToRegistry = getParamsToRegistry(DEFAULT_REGISTER_PROVIDER_KEYS
, COMMA_SPLIT_PATTERN.split(extraKeys));
return URL.valueOf(providerUrl, paramsToRegistry, providerUrl.getParameter(METHODS_KEY, (String[]) null));
}
}
- 如果没有配置simplified为true,删除这些标签后,存储到注册中心
MONITOR_KEY, BIND_IP_KEY, BIND_PORT_KEY, QOS_ENABLE, QOS_HOST, QOS_PORT, ACCEPT_FOREIGN_IP, VALIDATION_KEY,INTERFACES
注册中心存储了大量的配置信息。
- 如果配置simplified为true,只会注册以下key。
public static final String[] DEFAULT_REGISTER_PROVIDER_KEYS = {
APPLICATION_KEY, CODEC_KEY, EXCHANGER_KEY, SERIALIZATION_KEY, CLUSTER_KEY, CONNECTIONS_KEY, DEPRECATED_KEY,
GROUP_KEY, LOADBALANCE_KEY, MOCK_KEY, PATH_KEY, TIMEOUT_KEY, TOKEN_KEY, VERSION_KEY, WARMUP_KEY,
WEIGHT_KEY, TIMESTAMP_KEY, DUBBO_VERSION_KEY, RELEASE_KEY
};
注册中心存储的配置就很少了主要是服务分组、服务版本、服务名、地址列表等,这些信息将会存储在注册中心中
元数据中内容
发布元数据
org.apache.dubbo.metadata.store.RemoteWritableMetadataService#publishProvider
FullServiceDefinition fullServiceDefinition = ServiceDefinitionBuilder.buildFullDefinition(interfaceClass,
providerUrl.getParameters());
getMetadataReport().storeProviderMetadata(new MetadataIdentifier(providerUrl.getServiceInterface(),
providerUrl.getParameter(VERSION_KEY), providerUrl.getParameter(GROUP_KEY),
PROVIDER_SIDE, providerUrl.getParameter(APPLICATION_KEY)), fullServiceDefinition);
return;
在之前的url基础上又增加了方法详细信息作为元数据,正是因为这些内容,支撑了服务测试等功能。也方便我们排查服务问题。
存储到元数据中心。
暴露完成后进入exported
public void exported() {
// dispatch a ServiceConfigExportedEvent since 2.7.4
dispatch(new ServiceConfigExportedEvent(this));
}
ServiceNameMappingListener
会在注册中心创建映射,执行map方法
@SPI("default")
public interface ServiceNameMapping {
/**
* Map the specified Dubbo service interface, group, version and protocol to current Dubbo service name
*
* @param serviceInterface the class name of Dubbo service interface
* @param group the group of Dubbo service interface (optional)
* @param version the version of Dubbo service interface version (optional)
* @param protocol the protocol of Dubbo service interface exported (optional)
*/
void map(String serviceInterface, String group, String version, String protocol);
/**
* Get the service names from the specified Dubbo service interface, group, version and protocol
*
* @param serviceInterface the class name of Dubbo service interface
* @param group the group of Dubbo service interface (optional)
* @param version the version of Dubbo service interface version (optional)
* @param protocol the protocol of Dubbo service interface exported (optional)
* @return
*/
Set<String> get(String serviceInterface, String group, String version, String protocol);
/**
* Get the default extension of {@link ServiceNameMapping}
*
* @return non-null {@link ServiceNameMapping}
* @see DynamicConfigurationServiceNameMapping
*/
static ServiceNameMapping getDefaultExtension() {
return getExtensionLoader(ServiceNameMapping.class).getDefaultExtension();
}
}
看起注释map的含义是 为当前dubboService name做一个映射。具体作用暂时还不清楚