1 元数据中心介绍
元数据中心是dubbo2.7版本之后新增的功能,主要是为了减轻注册中心的压力,将部分存储在注册中心的内容放到元数据中心。元数据中心的数据只是给自己使用的,改动不需要告知对端,比如服务端修改了元数据,不需要通知消费端。这样注册中心存储的数据减少,同时大大降低了因为配置修改导致注册中心频繁通知监听者,从而大大减轻注册中心的压力。
服务治理中的元数据(Metadata)指的是服务分组、服务版本、服务名、方法列表、方法参数列表、超时时间等,这些信息将会存储在元数据中心之中。与元数据平起平坐的一个概念是服务的注册信息,即:服务分组、服务版本、服务名、地址列表等,这些信息将会存储在注册中心中。元数据中心和注册中心存储了一部分共同的服务信息,例如服务名。两者也有差异性,元数据中心还会存储方法列表即参数列表,注册中心存储了服务地址。
2 MetadataReport
MetadataReport
定义了元数据交换的接口,其主要实现类包括:
ZookeeperMetadataReport
RedisMetadataReport
NacosMetadataReport
ConsulMetadataReport
MetadataReportInstance的metadataReport属性是一个静态属性。
2.1 MetadataReport创建
public AbstractMetadataReport(URL reportServerURL) {
setUrl(reportServerURL);
//构建本地文件名,APPLICATION_KEY默认不存在,需要在
//MetadataReportConfig.parameters中配置
String defaultFilename = System.getProperty("user.home") + "/.dubbo/dubbo-metadata-" + reportServerURL.getParameter(APPLICATION_KEY) + "-" + reportServerURL.getAddress().replaceAll(":", "-") + ".cache";
//如果在MetadataReportConfig.parameters中配置了file参数,则使用它作为文件名
String filename = reportServerURL.getParameter(FILE_KEY, defaultFilename);
File file = null;
if (ConfigUtils.isNotEmpty(filename)) {
file = new File(filename);
if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
if (!file.getParentFile().mkdirs()) {
throw new IllegalArgumentException("Invalid service store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
}
}
if (!initialized.getAndSet(true) && file.exists()) {
file.delete();//第一次启动的时候将文件删除
}
}
this.file = file;
loadProperties();//加载数据到内存,第一次没有
//设置是否异步将数据传输到元数据中心
syncReport = reportServerURL.getParameter(SYNC_REPORT_KEY, false);
//当访问元数据中心失败时,MetadataReportRetry设置了两个与重试相关的参数:
//1、重试次数;2、多长时间重试一次
metadataReportRetry = new MetadataReportRetry(reportServerURL.getParameter(RETRY_TIMES_KEY, DEFAULT_METADATA_REPORT_RETRY_TIMES),
reportServerURL.getParameter(RETRY_PERIOD_KEY, DEFAULT_METADATA_REPORT_RETRY_PERIOD));
//参数cycle.report表示是否按照一定的频率将元数据更新到元数据中心,默认是true
if (reportServerURL.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
//使用定时器按照一定的频率将元数据更新到元数据中心
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboMetadataReportTimer", true));
//定时任务是每天运行一次,在每天的2:00至6:00一个随机时间
scheduler.scheduleAtFixedRate(this::publishAll, calculateStartTime(), ONE_DAY_IN_MIll, TimeUnit.MILLISECONDS);
}
}
该构造方法主要做了两件事:
- 在本地创建元数据存储文件;
- 创建元数据更新的定时器,定时器在每天2:00至6:00之间的随机时间运行一次;
2.2 publishAll
void publishAll() {
logger.info("start to publish all metadata.");
//allMetadataReports是一个Map<MetadataIdentifier, Object>类型,
//存储了客户端和服务端的所有与服务相关的元数据
this.doHandleMetadataCollection(allMetadataReports);
}
//遍历allMetadataReports中的值,
//如果是服务端的元数据,调用storeProviderMetadata;
//如果是客户端的元数据,调用storeConsumerMetadata
private boolean doHandleMetadataCollection(Map<MetadataIdentifier, Object> metadataMap) {
if (metadataMap.isEmpty()) {
return true;
}
Iterator<Map.Entry<MetadataIdentifier, Object>> iterable = metadataMap.entrySet().iterator();
while (iterable.hasNext()) {
Map.Entry<MetadataIdentifier, Object> item = iterable.next();
if (PROVIDER_SIDE.equals(item.getKey().getSide())) {
this.storeProviderMetadata(item.getKey(), (FullServiceDefinition) item.getValue());
} else if (CONSUMER_SIDE.equals(item.getKey().getSide())) {
this.storeConsumerMetadata(item.getKey(), (Map) item.getValue());
}
}
return false;
}
publishAll
的作用是调用doHandleMetadataCollection
方法遍历allMetadataReports
,然后将服务端和客户端的数据分别存储到元数据中心。 allMetadataReports
是AbstractMetadataReport
的一个属性,类型是Map<MetadataIdentifier, Object>
,存储两类值:
- 当存储服务端元数据时,value是
ServiceDefinition
对象,该对象详细记录了服务接口的所有信息,包括接口有哪些方法,每个方法的入参返回值是什么; - 当存储客户端元数据时,storeConsumerMetadata`保存客户端url中的请求参数;
MetadataIdentifier
对象记录了服务接口的名字、版本、分组、是服务端还是消费端标示、应用名。
2.3 storeProviderMetadata
public void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {
//是同步保存,还是异步保存,如果异步的话,使用线程池,
//reportCacheExecutor是单线程的线程池,
//reportCacheExecutor=Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveMetadataReport", true));
if (syncReport) {
storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition);
} else {
reportCacheExecutor.execute(() -> storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition));
}
}
private void storeProviderMetadataTask(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {
try {//代码有删减
allMetadataReports.put(providerMetadataIdentifier, serviceDefinition);
//failedReports记录保存失败的数据,如果保存成功,则删除
failedReports.remove(providerMetadataIdentifier);
Gson gson = new Gson();
String data = gson.toJson(serviceDefinition);
//doStoreProviderMetadata由子类实现,
//例如,ZookeeperMetadataReport是将数据直接保存到zookeeper
//providerMetadataIdentifier用于构造zk的路径,data是zk节点上的数据
doStoreProviderMetadata(providerMetadataIdentifier, data);
//将数据保存到本地文件,本地文件名在构造方法中已经创建
saveProperties(providerMetadataIdentifier, data, true, !syncReport);
} catch (Exception e) {
//记录保存失败的元数据
failedReports.put(providerMetadataIdentifier, serviceDefinition);
//重试,下面小节分析
metadataReportRetry.startRetryTask();
}
}
保存到元数据中心和文件中的元数据是以json格式存储的。 saveProperties
方法是将元数据存储到本地文件中,这个方法里面使用了一个自增的数字作为版本,每修改一次文件数字加1,如果下次更新时发现版本比当前版本小,则不修改文件。
2.4 storeConsumerMetadata
与storeProviderMetadata
相对,但是该方法没有原始调用点,可能会在后续版本更新中使用,该方法原理与storeProviderMetadata
类似。
2.5 saveServiceMetadata、getExportedURLs
saveServiceMetadata
方法将本dubbo实例所有暴露的服务存储到元数据中心。与saveServiceMetadata
相对,getExportedURLs
方法是查询暴露的服务。
2.6 saveSubscribedData、getSubscribedURLs
saveSubscribedData
将客户端引用的服务存储到元数据中心。getSubscribedURLs
从元数据中心查询客户端引用的服务。
2.7 MetadataReportRetry
该类是一个内部类。方法storeConsumerMetadata
和storeProviderMetadata
,如果出现访问元数据中心失败,那么会调用MetadataReportRetry
的startRetryTask
进行重试。 在AbstractMetadataReport
构造方法中创建MetadataReportRetry
对象,其入参有两个:
重试次数;
多长时间重试一次。
MetadataReportRetry
的startRetryTask
方法使用异步线程池中的线程按照要求重试。每次重试调用retry
方法,尝试将数据保存至元数据中心:
public boolean retry() {
return doHandleMetadataCollection(failedReports);
}
当全局重试次数超过了retry.times
的设定值,则调用cancelRetryTask
方法:
void cancelRetryTask() {
retryScheduledFuture.cancel(false);//取消正在运行的任务
//关闭线程池,之后重试机制无法使用,
//当超过重试次数后,dubbo认为元数据中心出现问题,可能宕机或者网络断了
//这有一个缺点,即使元数据中心可以使用了或者网络连通了,重试功能也无法使用了,只能重启应用程序
retryExecutor.shutdown();
}
3 MetadataService
MetadataService
是元数据服务中心,提供了元数据服务的相关接口。
MetadataService
的实现类包括:
-
WritableMetadataService:扩展了
MetadataService
接口的接口,下面是实现类;InMemoryWritableMetadataService
RemoteWritableMetadataService
RemoteWritableMetadataServiceDelegate
RemoteMetadataServiceProxy:远程元数据服务代理,应用于服务发现;
3.1 InMemoryWritableMetadataService
该类的数据都存储在内存中。主要属性有三个:
-
serviceDefinitions:保存服务接口定义。该属性是
Map
对象。key是接口名+分组+version,value是json格式的ServiceDefinition
对象。 - subscribedServiceURLs:存储服务端发布的服务参数和消费端引用的服务参数,包括IP地址,路由规则等。也是Map对象。key值同上。value是由参数组成的URL对象。
-
exportedServiceURLs:存储的内容与
subscribedServiceURLs
类似。
后面两个属性与服务发现功能相关。 该类主要是操作上述三个属性,当服务发布或者创建服务代理时,将对应的信息存储到三个属性中。
3.2 RemoteWritableMetadataServiceDelegate
当ApplicationConfig
的metadata
设置为remote
时,SPI加载该类。
public RemoteWritableMetadataServiceDelegate() {
//加载InMemoryWritableMetadataService
defaultWritableMetadataService = (InMemoryWritableMetadataService) WritableMetadataService.getExtension("local");
//创建RemoteWritableMetadataService,入参是InMemoryWritableMetadataService
remoteWritableMetadataService = new RemoteWritableMetadataService(defaultWritableMetadataService);
}
该类中修改类型的方法都直接调用InMemoryWritableMetadataService
和RemoteWritableMetadataService
的方法。查询类型的方法是调用对应的InMemoryWritableMetadataService
的方法。 修改数据时,同时修改两个对象,使两个对象数据保持一致,查询时只查询本地内存,加快查询速度。
3.3 RemoteWritableMetadataService
该类是将服务信息保存到远程,比如zk,redis等。具体保存到哪里,取决于MetadataReport
的实现。
public void publishServiceDefinition(URL url) {
String side = url.getParameter(SIDE_KEY);
if (PROVIDER_SIDE.equalsIgnoreCase(side)) {
//TODO, the params part is duplicate with that stored by exportURL(url), can be further optimized in the future.
publishProvider(url);
} else {
//TODO, only useful for ops showing the url parameters, this is duplicate with subscribeURL(url), can be removed in the future.
publishConsumer(url);
}
}
该方法用于保存服务接口元数据信息。服务端发布服务、客户端创建代理时都会调用该方法。该方法再调用MetadataReport
的storeProviderMetadata
方法将数据存储到元数据中心。