本文基于dubbo 2.7.5版本代码
详解MetadataService
在上一篇文章《dubbo解析-详解元数据中心MetadataReport》介绍了元数据中心如何启动,如何配置。这篇文章介绍一个和元数据中心密切相关的接口MetadataService。可以这么说没有MetadataService,元数据中心就无法正常使用。上一篇文章介绍的AbstractMetadataReport类里面的方法几乎都是MetadataService实现类RemoteWritableMetadataService调用的。MetadataService与MetadataReport之间的关系如下图:
一、MetadataService
MetadataService有多个实现类:
RemoteMetadataServiceProxy应用于服务发现,以后文章介绍。
从上图中可以看到WritableMetadataService扩展了接口MetadataService。
本文主要分析WritableMetadataService的实现类:InMemoryWritableMetadataService和RemoteWritableMetadataService、RemoteWritableMetadataServiceDelegate。
下面介绍MetadataService对象在哪里创建的。
先看服务端。服务端是在服务暴露完成后通过SPI加载的WritableMetadataService对象。如果服务只在本地暴露,那么dubbo不会创建WritableMetadataService对象。
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
exportLocal(url);//在本地暴露服务,当消费端和服务端在同一个dubbo实例时使用
}
//将服务暴露到外部,消费端可以通过网络访问到该服务
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (CollectionUtils.isNotEmpty(registryURLs)) {
//。。。。
//代码删除,删除的代码主要做将服务通过网络发布出去,将服务注册到注册中心
}
//服务暴露完成后,检查metadata配置,通过SPI加载对应的WritableMetadataService对象
WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
if (metadataService != null) {
//服务定义发布,可以将服务定义发布到元数据中心
metadataService.publishServiceDefinition(url);
}
}
再来看消费端。代码可以参见类ReferenceConfig的createProxy方法。在createProxy方法中,当消费端创建完成远程服务代理对象后,便执行下面代码创建WritableMetadataService对象,并做服务定义的发布:
String metadata = map.get(METADATA_KEY);
WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
if (metadataService != null) {
URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
//服务定义发布,可以将服务定义发布到元数据中心
metadataService.publishServiceDefinition(consumerURL);
}
消费端的代码与服务端基本一样。
其中两者都使用了下面的代码加载WritableMetadataService对象:
WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
代码中的metadata属性是在@Servcie(parameters={“metadata”,“remote”})或者@Reference(parameters={“metadata”,“remote”})中配置的。metadata只有两个值:
- local(默认值)
- remote
如果metadata=local,那么SPI加载的类是InMemoryWritableMetadataService,如果metadata=remote,则类是RemoteWritableMetadataServiceDelegate。对应关系可以参见文件org.apache.dubbo.metadata.WritableMetadataService。
我个人认为metadata的设置方式不合理,如果每个服务接口都使用元数据中心,那么每个@Reference和@Servcie都要设置,这样工作量太大了。dubbo应该提供以applicantion或者module级别的参数设置。
二、InMemoryWritableMetadataService
从名字上可以看出来,这个类的数据都存储在内存中。
该类主要属性有三个:
- serviceDefinitions:保存服务接口定义。该属性是Map对象。key是接口名+分组+version,value是json格式的ServiceDefinition对象。
- subscribedServiceURLs:存储服务端发布的服务参数和消费端引用的服务参数,包括IP地址,路由规则等。也是Map对象。key值同上。value是由参数组成的URL对象。
- exportedServiceURLs:存储的内容与subscribedServiceURLs类似。
后面两个属性与服务发现功能相关。
该类主要是操作上述三个属性,当服务发布或者创建服务代理时,将对应的信息存储到三个属性中。代码比较简单不再展示,有兴趣的可以看代码。
三、RemoteWritableMetadataServiceDelegate
当metadata设置为remote时,SPI加载该类。下面看一下该类的构造方法:
public RemoteWritableMetadataServiceDelegate() {
//加载InMemoryWritableMetadataService
defaultWritableMetadataService = (InMemoryWritableMetadataService) WritableMetadataService.getExtension("local");
//创建RemoteWritableMetadataService,入参是InMemoryWritableMetadataService
remoteWritableMetadataService = new RemoteWritableMetadataService(defaultWritableMetadataService);
}
在构造方法中,创建了InMemoryWritableMetadataService和RemoteWritableMetadataService两个对象。InMemoryWritableMetadataService负责读写本地内存,RemoteWritableMetadataService负责修改远程数据。
该类中修改类型的方法都直接调用InMemoryWritableMetadataService和RemoteWritableMetadataService的方法。查询类型的方法是调用对应的InMemoryWritableMetadataService的方法。
修改数据时,同时修改两个对象,使两个对象数据保持一致,查询时只查询本地内存,加快查询速度。
三、RemoteWritableMetadataService
该类是将服务信息保存到远程,比如zk,redis等。具体保存到哪里,取决于MetadataReport的实现。下面介绍各个方法的作用。
1、refreshMetadata
该方法主要是在服务发现的场景中使用,是由事件ServiceInstancePreRegisteredEvent触发。其入参可以理解为版本号,代码里面将入参版本号与当前RemoteWritableMetadataService对象里面的版本号作对比,不相等时会将InMemoryWritableMetadataService中的数据通过MetadataReport对象传输到元数据中心。
通过对代码的分析,因为入参版本号设的值都是null,该方法判断入参为null后,直接返回,不访问元数据中心。
2、publishServiceDefinition
该方法用于保存服务接口元数据信息。服务端发布服务、客户端创建代理时都会调用该方法。该方法再调用MetadataReport的storeProviderMetadata方法将数据存储到元数据中心。
public void publishServiceDefinition(URL providerUrl) {
try {
//获取服务接口名
String interfaceName = providerUrl.getParameter(INTERFACE_KEY);
if (StringUtils.isNotEmpty(interfaceName)
&& !ProtocolUtils.isGeneric(providerUrl.getParameter(GENERIC_KEY))) {
Class interfaceClass = Class.forName(interfaceName);
//构建服务接口的ServiceDefinition对象
ServiceDefinition serviceDefinition = ServiceDefinitionBuilder.build(interfaceClass);
//将ServiceDefinition发布到元数据中心
getMetadataReport().storeProviderMetadata(new MetadataIdentifier(providerUrl.getServiceInterface(),
providerUrl.getParameter(VERSION_KEY), providerUrl.getParameter(GROUP_KEY),
null, null), serviceDefinition);
return;
}
} catch (ClassNotFoundException e) {
//代码删减
}
publishProvider(providerUrl);
}
3、publishProvider
该方法与publishServiceDefinition功能类似,两个方法都调用MetadataReport的storeProviderMetadata方法。区别在于,publishProvider除了将ServiceDefinition对象发布到元数据中心外,还把配置文件、@Service等设置的参数一并发布到元数据中心。
四、总结
通过上面的分析可以看出,MetadataService主要在服务发现功能中使用,这也是dubbo服务自省的设计要求。后面的文章会介绍服务发现。
另外,在spring发布ContextRefreshedEvent事件之后,MetadataServiceExporter可以将MetadataService对象以dubbo服务的形式发布出去,这样可以通过网络访问到dubbo实例的元数据信息。但是不是任何时候都可以发布成功的,需要InMemoryWritableMetadataService的属性exportedServiceURLs不为空才行,这个属性与服务发现有关,以后介绍。