美文网首页
Dubbo元数据中心

Dubbo元数据中心

作者: 爱健身的兔子 | 来源:发表于2021-02-08 15:57 被阅读0次

    1 元数据中心介绍

    元数据中心是dubbo2.7版本之后新增的功能,主要是为了减轻注册中心的压力,将部分存储在注册中心的内容放到元数据中心。元数据中心的数据只是给自己使用的,改动不需要告知对端,比如服务端修改了元数据,不需要通知消费端。这样注册中心存储的数据减少,同时大大降低了因为配置修改导致注册中心频繁通知监听者,从而大大减轻注册中心的压力。

    服务治理中的元数据(Metadata)指的是服务分组、服务版本、服务名、方法列表、方法参数列表、超时时间等,这些信息将会存储在元数据中心之中。与元数据平起平坐的一个概念是服务的注册信息,即:服务分组、服务版本、服务名、地址列表等,这些信息将会存储在注册中心中。元数据中心和注册中心存储了一部分共同的服务信息,例如服务名。两者也有差异性,元数据中心还会存储方法列表即参数列表,注册中心存储了服务地址。

    2 MetadataReport

    MetadataReport定义了元数据交换的接口,其主要实现类包括:

    • ZookeeperMetadataReport

    • RedisMetadataReport

    • NacosMetadataReport

    • ConsulMetadataReport

    元数据中心的启动流程图,元数据中心以zookeeper为例:

    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);
            }
        }
    

    该构造方法主要做了两件事:

    1. 在本地创建元数据存储文件;
    2. 创建元数据更新的定时器,定时器在每天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,然后将服务端和客户端的数据分别存储到元数据中心。 allMetadataReportsAbstractMetadataReport的一个属性,类型是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

    该类是一个内部类。方法storeConsumerMetadatastoreProviderMetadata,如果出现访问元数据中心失败,那么会调用MetadataReportRetrystartRetryTask进行重试。 在AbstractMetadataReport构造方法中创建MetadataReportRetry对象,其入参有两个:

    • 重试次数;

    • 多长时间重试一次。

    MetadataReportRetrystartRetryTask方法使用异步线程池中的线程按照要求重试。每次重试调用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

    ApplicationConfigmetadata设置为remote时,SPI加载该类。

        public RemoteWritableMetadataServiceDelegate() {
            //加载InMemoryWritableMetadataService
            defaultWritableMetadataService = (InMemoryWritableMetadataService) WritableMetadataService.getExtension("local");
            //创建RemoteWritableMetadataService,入参是InMemoryWritableMetadataService
            remoteWritableMetadataService = new RemoteWritableMetadataService(defaultWritableMetadataService);
        }
    

    该类中修改类型的方法都直接调用InMemoryWritableMetadataServiceRemoteWritableMetadataService的方法。查询类型的方法是调用对应的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);
            }
        }
    

    该方法用于保存服务接口元数据信息。服务端发布服务、客户端创建代理时都会调用该方法。该方法再调用MetadataReportstoreProviderMetadata方法将数据存储到元数据中心。

    dubbo解析-详解元数据中心MetadataReport_龚厂长的博客-CSDN博客

    相关文章

      网友评论

          本文标题:Dubbo元数据中心

          本文链接:https://www.haomeiwen.com/subject/hrxfxltx.html