Dubbo元数据中心

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博客

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容