Dubbo 3.x源码(28)—Dubbo服务发布导出源码(7)应用级服务接口元数据发布

基于Dubbo 3.1,详细介绍了Dubbo服务的发布与引用的源码。

此前我们在Dubbo启动过程的DefaultModuleDeployer#startSync方法中,学习了Dubbo服务的导出exportServices方法和服务的引入referServices方法。

在这两个操作执行完毕之后,将会继续调用onModuleStarted方法,将启动状态设置为STARTED,并且触发模块状态改变回调,进行应用级服务接口元数据发布,应用级服务元数据在Dubbo3的应用级服务发现流程中比较有用,因此我们来看看它的源码。

Dubbo 3.x服务引用源码:

  1. Dubbo 3.x源码(11)---Dubbo服务的发布与引用的入口
  2. Dubbo 3.x源码(18)---Dubbo服务引用源码(1)
  3. Dubbo 3.x源码(19)---Dubbo服务引用源码(2)
  4. Dubbo 3.x源码(20)---Dubbo服务引用源码(3)
  5. Dubbo 3.x源码(21)---Dubbo服务引用源码(4)
  6. Dubbo 3.x源码(22)---Dubbo服务引用源码(5)服务引用bean的获取以及懒加载原理
  7. Dubbo 3.x源码(23)---Dubbo服务引用源码(6)MigrationRuleListener迁移规则监听器
  8. Dubbo 3.x源码(24)---Dubbo服务引用源码(7)接口级服务发现订阅refreshInterfaceInvoker
  9. Dubbo 3.x源码(25)---Dubbo服务引用源码(8)notify订阅服务通知更新
  10. Dubbo 3.x源码(26)---Dubbo服务引用源码(9)应用级服务发现订阅refreshServiceDiscoveryInvoker
  11. Dubbo 3.x源码(27)---Dubbo服务引用源码(10)subscribeURLs订阅应用级服务url

Dubbo 3.x服务发布源码:

  1. Dubbo 3.x源码(11)---Dubbo服务的发布与引用的入口
  2. Dubbo 3.x源码(12)---Dubbo服务发布导出源码(1)
  3. Dubbo 3.x源码(13)---Dubbo服务发布导出源码(2)
  4. Dubbo 3.x源码(14)---Dubbo服务发布导出源码(3)
  5. Dubbo 3.x源码(15)---Dubbo服务发布导出源码(4)
  6. Dubbo 3.x源码(16)---Dubbo服务发布导出源码(5)
  7. Dubbo 3.x源码(17)---Dubbo服务发布导出源码(6)
  8. Dubbo 3.x源码(28)---Dubbo服务发布导出源码(7)应用级服务接口元数据发布

1 onModuleStarted模块状态回调

将启动状态设置为STARTED,并且触发模块状态改变回调,进行应用级服务元数据发布。

java 复制代码
/**
 * DefaultModuleDeployer的方法
 * <p>
 * dubbo模块启动后的处理
 */
private void onModuleStarted() {
    try {
        if (isStarting()) {
            //设置启动状态为STARTED
            setStarted();
            logger.info(getIdentifier() + " has started.");
            /*
             * 模块状态改变回调
             */
            applicationDeployer.notifyModuleChanged(moduleModel, DeployState.STARTED);
        }
    } finally {
        // complete module start future after application state changed
        completeStartFuture(true);
    }
}

2 notifyModuleChanged通知模块状态改变

检查状态,并且发布服务元数据,最后通知模块状态更改或模块更改。

java 复制代码
/**
 * DefaultApplicationDeployer的方法
 * <p>
 * 模块状态改变回调
 *
 * @param moduleModel 模块模型
 * @param state       模块状态
 */
@Override
public void notifyModuleChanged(ModuleModel moduleModel, DeployState state) {
    /*
     * 检查状态,并且发布服务元数据
     */
    checkState(moduleModel, state);

    //通知模块状态更改或模块更改
    synchronized (stateLock) {
        stateLock.notifyAll();
    }
}

3 checkState检查状态

检查状态,并且发布服务元数据。

java 复制代码
* DefaultApplicationDeployer的方法
 * <p>
 * 检查状态,并且发布服务元数据
 *
 * @param moduleModel 模块模型
 * @param moduleState 模块状态
 */
@Override
public void checkState(ModuleModel moduleModel, DeployState moduleState) {
    synchronized (stateLock) {
        //如果不是内部模型并且发布成功
        if (!moduleModel.isInternal() && moduleState == DeployState.STARTED) {
            /*
             * 准备应用程序实例,注册服务实例元数据信息
             */
            prepareApplicationInstance();
        }
        //发布状态转换,触发DeployListener,更新元数据
        DeployState newState = calculateState();
        switch (newState) {
            case STARTED:
                onStarted();
                break;
            case STARTING:
                onStarting();
                break;
            case STOPPING:
                onStopping();
                break;
            case STOPPED:
                onStopped();
                break;
            case FAILED:
                Throwable error = null;
                ModuleModel errorModule = null;
                for (ModuleModel module : applicationModel.getModuleModels()) {
                    ModuleDeployer deployer = module.getDeployer();
                    if (deployer.isFailed() && deployer.getError() != null) {
                        error = deployer.getError();
                        errorModule = module;
                        break;
                    }
                }
                onFailed(getIdentifier() + " found failed module: " + errorModule.getDesc(), error);
                break;
            case PENDING:
                // cannot change to pending from other state
                // setPending();
                break;
        }
    }
}

4 prepareApplicationInstance注册应用程序实例

导出元数据服务,注册本地服务实例信息。

java 复制代码
/**
 * DefaultApplicationDeployer的方法
 * <p>
 * 准备应用程序实例,注册服务实例元数据信息
 */
@Override
public void prepareApplicationInstance() {
    //如果已经注册了服务实例信息,那么直接返回
    if (hasPreparedApplicationInstance.get()) {
        return;
    }
    //是否注册实例到注册中心。当时实例为纯消费者时才设置为false,默认为true。
    if (isRegisterConsumerInstance()) {
        /*
         * 导出元数据服务
         */
        exportMetadataService();
        if (hasPreparedApplicationInstance.compareAndSet(false, true)) {
            // register the local ServiceInstance if required
            /*
             * 如果必要,则注册本地服务实例信息
             */
            registerServiceInstance();
        }
    }
}

5 exportMetadataService导出元数据服务

导出元数据服务。

java 复制代码
/**
 * DefaultApplicationDeployer的方法
 *
 * 导出元数据服务
 */
private void exportMetadataService() {
    //如果不是启动中的状态则返回
    if (!isStarting()) {
        return;
    }
    //遍历监听器
    for (DeployListener<ApplicationModel> listener : listeners) {
        try {
            //如果是ApplicationDeployListener那么执行ApplicationDeployListener方法
            if (listener instanceof ApplicationDeployListener) {
                ((ApplicationDeployListener) listener).onModuleStarted(applicationModel);
            }
        } catch (Throwable e) {
            logger.error(getIdentifier() + " an exception occurred when handle starting event", e);
        }
    }
}

该方法内部调用全部ApplicationDeployListener的onModuleStarted方法。这里默认listener有两个,一个是ExporterDeployListener,另一个是DubboDeployApplicationListener,其中ExporterDeployListener实现了ApplicationDeployListener。

ExporterDeployListener的onModuleStarted方法实现如下:

  1. 首先获取当前dubbo服务的元数据服务实现MetadataServiceDelegation,用于获取当前dubbo实例的元数据信息,然后创建元数据服务导出器metadataServiceExporter。
  2. 最后判断如果metadataType不是remote,则执行服务导出,默认类型为local,那么执行metadataServiceExporter#export方法导出元数据服务。

我们之前就说过,如果metadataType类型为remote,那么consumer将会从注册中心获取元数据,而如果类型为local,那么consumer将会直接从provider通过rpc调用的方式远程调用元数据服务获取元数据,所以需要导出元数据服务。

java 复制代码
/**
 * ExporterDeployListener的方法
 */
@Override
public synchronized void onModuleStarted(ApplicationModel applicationModel) {
    // start metadata service exporter
    //获取当前dubbo服务的元数据服务实现MetadataServiceDelegation,用于通过rpc调用的方式获取当前dubbo实例的元数据信息
    MetadataServiceDelegation metadataService = applicationModel.getBeanFactory().getOrRegisterBean(MetadataServiceDelegation.class);
    //获取元数据服务导出器
    if (metadataServiceExporter == null) {
        //创建一个导出器
        metadataServiceExporter = new ConfigurableMetadataServiceExporter(applicationModel, metadataService);
        // fixme, let's disable local metadata service export at this moment
        //如果metadataType类型为remote,那么consumer将会从注册中心获取元数据,而如果类型为local,那么consumer将会直接从provider通过rpc调用的方式远程调用元数据服务获取元数据
        //所以,如果metadataType不是remote,则执行服务导出,默认类型为local
        if (!REMOTE_METADATA_STORAGE_TYPE.equals(getMetadataType(applicationModel))) {
            metadataServiceExporter.export();
        }
    }
}

5.1 ConfigurableMetadataServiceExporter#export导出元数据服务

该方法首先会调用buildServiceConfig方法构建元数据服务配置实例,然后调用serviceConfig#export方法导出元数据服务,该方法我们此前就讲过了,就是通用的服务导出的入口方法。

java 复制代码
/**
 * ConfigurableMetadataServiceExporter的方法
 * <p>
 * 导出元数据服务
 */
public synchronized ConfigurableMetadataServiceExporter export() {
    //如果serviceConfig为null或者serviceConfig没有导出或者serviceConfig需要导出
    if (serviceConfig == null || !isExported()) {
        /*
         * 构建元数据服务配置实例
         */
        this.serviceConfig = buildServiceConfig();
        /*
         * 调用export方法导出元数据服务,该方法我们此前就讲过了,就是通用的服务导出的入口方法
         */
        serviceConfig.export();
        //设置元数据服务url
        metadataService.setMetadataURL(serviceConfig.getExportedUrls().get(0));
        if (logger.isInfoEnabled()) {
            logger.info("The MetadataService exports urls : " + serviceConfig.getExportedUrls());
        }
    } else {
        if (logger.isWarnEnabled()) {
            logger.warn("The MetadataService has been exported : " + serviceConfig.getExportedUrls());
        }
    }

    return this;
}

5.1.1 buildServiceConfig构建元数据服务实例

该方法通过代码创建一个MetadataService的ServiceConfig实例。可以看到,元数据服务不会像注册中心注册服务信息,并且底层元数据服务的实现类对象为MetadataServiceDelegation。

java 复制代码
/**
 * ConfigurableMetadataServiceExporter的方法
 */
private ServiceConfig<MetadataService> buildServiceConfig() {
    ApplicationConfig applicationConfig = getApplicationConfig();
    //创建serviceConfig
    ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();
    serviceConfig.setScopeModel(applicationModel.getInternalModule());
    serviceConfig.setApplication(applicationConfig);
    //直接连模式,不会像注册中心注册该元数据服务
    RegistryConfig registryConfig = new RegistryConfig("N/A");
    registryConfig.setId("internal-metadata-registry");
    serviceConfig.setRegistry(registryConfig);
    serviceConfig.setRegister(false);
    //协议
    serviceConfig.setProtocol(generateMetadataProtocol());
    serviceConfig.setInterface(MetadataService.class);
    serviceConfig.setDelay(0);
    //底层元数据服务的实现类对象,即MetadataServiceDelegation
    serviceConfig.setRef(metadataService);
    serviceConfig.setGroup(applicationConfig.getName());
    serviceConfig.setVersion(MetadataService.VERSION);
    //getAndListenInstanceMetadata方法配置
    serviceConfig.setMethods(generateMethodConfig());
    //单独的一个连接
    serviceConfig.setConnections(1); // separate connection
    //最大同时运行任务数100
    serviceConfig.setExecutes(100); // max tasks running at the same time
    Map<String, String> threadParams = new HashMap<>();
    threadParams.put(THREADPOOL_KEY, "cached");
    threadParams.put(THREADS_KEY, "100");
    threadParams.put(CORE_THREADS_KEY, "2");
    serviceConfig.setParameters(threadParams);

    return serviceConfig;
}

6 registerServiceInstance注册服务实例

导出元数据服务之后,通常会注册本地服务实例信息到注册中心。这样consumer就能从注册中心获取到当前provider实例的元数据信息。

该方法首先调用ServiceInstanceMetadataUtils#registerMetadataAndInstance方法注册服务实例和应用接口配置元数据,然后启动一个定时任务,默认每30000ms执行一次ServiceInstanceMetadataUtils#refreshMetadataAndInstance方法刷新服务实例元数据。

java 复制代码
/**
 * DefaultApplicationDeployer的方法
 * <p>
 * 注册本地服务实例信息
 */
private void registerServiceInstance() {
    try {
        //注册标志改为true
        registered = true;
        /*
         * 注册服务实例元数据
         */
        ServiceInstanceMetadataUtils.registerMetadataAndInstance(applicationModel);
    } catch (Exception e) {
        logger.error("5-11", "configuration server disconnected", "", "Register instance error.", e);
    }
    if (registered) {
        //更新 Metadata 和 ServiceInstance的定时任务,默认间隔30000ms
        asyncMetadataFuture = frameworkExecutorRepository.getSharedScheduledExecutor().scheduleWithFixedDelay(() -> {

            //停止时忽略刷新元数据
            if (applicationModel.isDestroyed()) {
                return;
            }
            try {
                if (!applicationModel.isDestroyed() && registered) {
                    /*
                     * 刷新服务实例元数据
                     */
                    ServiceInstanceMetadataUtils.refreshMetadataAndInstance(applicationModel);
                }
            } catch (Exception e) {
                if (!applicationModel.isDestroyed()) {
                    logger.error("5-12", "", "", "Refresh instance and metadata error.", e);
                }
            }
        }, 0, ConfigurationUtils.get(applicationModel, METADATA_PUBLISH_DELAY_KEY, DEFAULT_METADATA_PUBLISH_DELAY), TimeUnit.MILLISECONDS);
    }
}

6.1 registerMetadataAndInstance注册服务实例元数据

获取当前provider内存中的所有ServiceDiscoveryRegistry内部的ServiceDiscovery,然后依次调用ServiceDiscovery#register方法注册服务实例和应用接口配置元数据。

ServiceDiscoveryRegistry表示应用级远程服务注册发现协议,内部的ServiceDiscovery表示即真实注册中心,如ZookeeperServiceDiscovery。

java 复制代码
/**
 * ServiceInstanceMetadataUtils的方法
 * <p>
 * 注册服务实例元数据
 */
public static void registerMetadataAndInstance(ApplicationModel applicationModel) {
    LOGGER.info("Start registering instance address to registry.");
    RegistryManager registryManager = applicationModel.getBeanFactory().getBean(RegistryManager.class);
    // register service instance
    //获取当前provider内存中的所有应用级服务注册表ServiceDiscoveryRegistry内部的ServiceDiscovery,即真实注册中心
    //依次调用ServiceDiscovery#register方法注册服务实例元数据
    //ServiceDiscoveryRegistry表示应用级远程服务注册发现协议,内部的ServiceDiscovery,如ZookeeperServiceDiscovery
    registryManager.getServiceDiscoveries().forEach(ServiceDiscovery::register);
}

6.2 ServiceDiscovery#register注册服务实例

ServiceDiscovery#register方法注册服务实例,常见的注册中心如zookeeper,对应的ServiceDiscovery为ZookeeperServiceDiscovery,然后他们的register方法的骨架代码都是由父类AbstractServiceDiscovery实现的。

该放的大概步骤为:首先调用createServiceInstance方法根据服务元数据信息创建应用级服务实例ServiceInstance,然后调用calOrUpdateInstanceRevision方法计算是否更新实例版本号revision,如果更新了revision,那么:

  1. ** 调用reportMetadata方法重新注册服务元数据信息到元数据中心。**
  2. ** 调用doRegister方法重新注册服务实例信息到注册中心。**

java 复制代码
/**
 * AbstractServiceDiscovery的方法
 * <p>
 * 注册服务实例信息
 */
@Override
public synchronized void register() throws RuntimeException {
    /*
     * 根据服务元数据信息创建应用级服务实例ServiceInstance
     */
    this.serviceInstance = createServiceInstance(this.metadataInfo);
    //找不到有效的实例,请停止将实例地址注册到注册中心
    if (!isValidInstance(this.serviceInstance)) {
        logger.warn("No valid instance found, stop registering instance address to registry.");
        return;
    }
    //计算是否更新实例版本号revision
    boolean revisionUpdated = calOrUpdateInstanceRevision(this.serviceInstance);
    //如果更新了revision,那么重新注册服务实例信息到注册中心
    if (revisionUpdated) {
        //注册服务元数据信息到元数据中心
        reportMetadata(this.metadataInfo);
        //注册服务元数据信息到注册中心
        doRegister(this.serviceInstance);
    }
}

6.2.1 createServiceInstance创建服务实例

创建一个DefaultServiceInstance类型的应用级服务实例,然后将应用级服务元数据信息存储进去。

java 复制代码
/**
 * AbstractServiceDiscovery的方法
 * <p>
 * 根据服务元数据信息创建应用级服务实例ServiceInstance
 *
 * @param metadataInfo 服务元数据信息
 * @return 服务实例
 */
protected ServiceInstance createServiceInstance(MetadataInfo metadataInfo) {
    //创建默认服务实例
    DefaultServiceInstance instance = new DefaultServiceInstance(serviceName, applicationModel);
    //设置应用级服务元数据
    instance.setServiceMetadata(metadataInfo);
    //设置元数据存储类型,即设置metadataInfo的dubbo.metadata.storage-type属性值为metadataType
    setMetadataStorageType(instance, metadataType);
    //通过ServiceInstanceCustomizer自定义服务实例
    ServiceInstanceMetadataUtils.customizeInstance(instance, applicationModel);
    return instance;
}

最终,应用级服务实例以及内部的服务元数据信息如下,注意metadata和serviceMetadata是不一样的,serviceMetadata包含了更多的信息,例如内部有一个services集合,其中保存着导出的服务接口的信息,而metadata则是一个map映射,内部保存着基本的元数据信息,例如内部的服务接口的导出端口以及协议,服务url参数,元数据存储类型等等。

6.2.2 calOrUpdateInstanceRevision计算配置版本号

在获取到serviceInstance之后,将会计算元数据的版本号revision来判断是否真的需要上报新的服务元数据信息。

  1. 首先获取目前服务实例版本号信息,如果第一次创建实例则返回null。先获取服务实例内部的ServiceMetadata内部的revision,如果为null则返回服务里实例的metadata内部的dubbo.metadata.revision字段。
  2. 调用calAndGetRevision方法根据最新服务元数据信息计算一个新的元数据版本号。
  3. 如果新的版本号和老的版本号不一致,那么将新的版本号替换metadata内部的dubbo.metadata.revision字段的值,返回true,表示需要上报新的服务元数据信息;否则返回false,表示元数据一致,不需要重新上报。
java 复制代码
/**
 * AbstractServiceDiscovery的方法
 * 计算版本号revision来判断是否真的需要更新
 */
protected boolean calOrUpdateInstanceRevision(ServiceInstance instance) {
    //获取目前服务实例版本号信息,如果第一次创建实例则返回null
    //否则先返回服务实例内部的ServiceMetadata内部的revision,如果为null则返回服务里实例的metadata内部的dubbo.metadata.revision字段
    String existingInstanceRevision = getExportedServicesRevision(instance);
    MetadataInfo metadataInfo = instance.getServiceMetadata();
    //根据服务元数据信息计算一个新的元数据版本号
    String newRevision = metadataInfo.calAndGetRevision();
    //如果新的版本号和老的版本号不一致
    if (!newRevision.equals(existingInstanceRevision)) {
        //那么将新的版本号替换metadata内部的dubbo.metadata.revision字段的值
        instance.getMetadata().put(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, metadataInfo.getRevision());
        //需要上报新的服务元数据信息
        return true;
    }
    //如果版本号一致,则无需上报服务元数据信息
    return false;
}
6.2.2.1 calAndGetRevision计算元数据版本号

该方法计算并返回最新的元数据信息版本号。

  1. 首先如果老的版本号不为null,且元数据信息没更新,那么直接返回原值,否则构建新的revision。
  2. 将服务应用名以及各个导出的服务接口字符串拼接为一个字符串,然后进行med5计算得到最终的revision字符串,例如:4133cb1337c40592501d4c0ee6523637。
  3. 比较新老revision是否一致,如果不一致,那么revision属性设置为为新的版本号,rawMetadataInfo属性设置为为新的服务元数据信息的格式化json字符串,并返回新的revision。
java 复制代码
/**
 * MetadataInfo的方法
 *
 * 元数据实例状态计算
 */
public synchronized String calAndGetRevision() {
    //如果版本号不为null,且信息没更新,直接返回原值
    if (revision != null && !updated) {
        return revision;
    }

    updated = false;
    //如果元数据中没有任何导出的服务接口的数据,那么revision返回固定的"0"字符串
    if (CollectionUtils.isEmptyMap(services)) {
        this.revision = EMPTY_REVISION;
    } else {
        StringBuilder sb = new StringBuilder();
        //当前服务应用名
        sb.append(app);
        for (Map.Entry<String, ServiceInfo> entry : new TreeMap<>(services).entrySet()) {
            //加上各个导出的服务字符串
            sb.append(entry.getValue().toDescString());
        }
        //对字符串进行md5运算得到新的revision
        String tempRevision = RevisionResolver.calRevision(sb.toString());
        //如果新老revision不一致
        if (!StringUtils.isEquals(this.revision, tempRevision)) {
            //打印日志
            if (logger.isInfoEnabled()) {
                logger.info(String.format("metadata revision changed: %s -> %s, app: %s, services: %d", this.revision, tempRevision, this.app, this.services.size()));
            }
            //替换为新的revision
            this.revision = tempRevision;
            //元数据信息的格式化json字符串
            this.rawMetadataInfo = JsonUtils.getJson().toJson(this);
        }
    }
    return revision;
}

6.2.3 reportMetadata报告应用接口配置元数据

如果metadataType为local并且应该报告元数据,或者metadataType为remote,那么将会把服务元数据注册到元数据中心,由于默认metadataType为local,因此不会注册。

java 复制代码
/**
 * AbstractServiceDiscovery的方法
 * 报告元数据到元数据中心
 *
 * @param metadataInfo 元数据信息
 */
protected void reportMetadata(MetadataInfo metadataInfo) {
    //如果元数据中心存在
    if (metadataReport != null) {
        //
        SubscriberMetadataIdentifier identifier = new SubscriberMetadataIdentifier(serviceName, metadataInfo.getRevision());
        //如果metadataType为local并且应该报告元数据,或者metadataType为remote,那么将会把服务元数据注册到元数据中心
        if ((DEFAULT_METADATA_STORAGE_TYPE.equals(metadataType) && metadataReport.shouldReportMetadata()) || REMOTE_METADATA_STORAGE_TYPE.equals(metadataType)) {
            metadataReport.publishAppMetadata(identifier, metadataInfo);
        }
    }
}
6.2.3.1 应用级服务接口元配置数据样式

这是一个非常重要的地址发现数据,另一个就是接口-- 应用映射关系数据。这两个数据在Dubbo 3 消费者执行应用级别服务引用的时候都需要获取到。

接口级配置元数据是作为地址发现的补充,相比于 Spring Cloud 等地址发现模型只能同步 ip、port 信息,Dubbo 的服务发现机制可以同步接口列表、接口定义、接口级参数配置等信息。这部分内容根据当前应用的自身信息、以及接口信息计算而来,并且从性能角度出发,还根据元数据生成 revision,以实现不同机器实例间的元数据聚合。

**为什么这一份数据要在****Dubbo **服务引用之后才可能会上报的注册中心呢?既然它在服务引用之后才上报,那么服务引用的时候不就没法从注册中心获取到元数据了吗?


实际上默认情况下,服务接口元数据不会注册到注册中心,在消费者执行服务引用的时候,消费者也是默认直接通过远程调用的方式从一个服务提供者那里获取服务接口元数据,没有从配置中心获取。

主要目的是因为服务接口元数据量可能会非常的大,特别是对于一些大型服务来说,Dubbo3直接走接口获取,能够最大幅度的减轻注册中心的存储和传输数据的压力,将这个压力转移分摊到不同的服务提供者实例上去。

Dubbo 3 的应用级服务发现机制在宣传时,说该机制是"面向百万实例集群的服务发现机制",就是通过这些优化细节实现的,我们后面会单独总结!

6.2.4 doRegister注册服务实例元数据

doRegister方法会将服务元数据注册到注册中心,该方法由AbstractServiceDiscovery的具体子类自行实现,我们来看看ZookeeperServiceDiscovery的实现。

java 复制代码
/**
 * ZookeeperServiceDiscovery的方法
 * <p>
 * 注册服务实例
 *
 * @param serviceInstance 服务实例
 */
@Override
public void doRegister(ServiceInstance serviceInstance) {
    try {
        //DefaultServiceInstance的registerService方法
        serviceDiscovery.registerService(build(serviceInstance));
    } catch (Exception e) {
        throw new RpcException(REGISTRY_EXCEPTION, "Failed register instance " + serviceInstance.toString(), e);
    }
}

CuratorFrameworkUtils#build方法用于将dubbo服务实例转换为curator服务实例。

java 复制代码
/**
 * CuratorFrameworkUtils的方法
 *
 * @param serviceInstance dubbo服务实例
 * @return curator服务实例
 */
public static org.apache.curator.x.discovery.ServiceInstance<ZookeeperInstance> build(ServiceInstance serviceInstance) {
    ServiceInstanceBuilder builder;
    //服务名
    String serviceName = serviceInstance.getServiceName();
    //host
    String host = serviceInstance.getHost();
    //port
    int port = serviceInstance.getPort();
    //metadata
    Map<String, String> metadata = serviceInstance.getSortedMetadata();
    //host:port
    String id = generateId(host, port);
    //构建ZookeeperInstance作为有效载荷
    ZookeeperInstance zookeeperInstance = new ZookeeperInstance(id, serviceName, metadata);
    try {
        builder = builder()
            .id(id)
            .name(serviceName)
            .address(host)
            .port(port)
            .payload(zookeeperInstance);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    //基于上面的属性构建curator的ServiceInstance
    return builder.build();
}
6.2.4.1 服务实例数据样式

我们这里直接来看看注册后在zookeeper上的数据,可以看到节点路径是应用级数据services/{applicationName}/{ip:port},每一个应用实例才会注册一个节点,节点类型是临时节点,节点内包括服务名称、id、地址、端口、有效载荷等信息。

有效载荷中的数据:

  1. storage-type:就是metadataType配置,消费者用该字段判断是发请求从提供者获取服务接口元数据(local),还是从配置中心获取服务接口元数据(remote),默认local。
  2. Revision:根据元数据计算出来的元数据版本号。消费者在获取服务接口元数据之前,通过该字段将相同版本的服务提供者分成一组,然后每一组选择一个服务提供者来获取服务接口元数据即可。这也是一种优化。

7 refreshMetadataAndInstance定期刷新服务实例元数据

在DefaultApplicationDeployer#registerServiceInstance方法中,除了注册服务元数据之外,还会启动与给定时任务,每30s调用ServiceInstanceMetadataUtils#refreshMetadataAndInstance方法刷新一次服务元数据信息。

java 复制代码
/**
 * ServiceInstanceMetadataUtils的方法
 * <p>
 * 刷新服务实例元数据
 */
public static void refreshMetadataAndInstance(ApplicationModel applicationModel) {
    RegistryManager registryManager = applicationModel.getBeanFactory().getBean(RegistryManager.class);
    // update service instance revision
    //获取当前provider内存中的所有应用级服务注册表ServiceDiscoveryRegistry内部的ServiceDiscovery,即真实注册中心
    //依次调用ServiceDiscovery#update方法更新服务实例元数据
    //ServiceDiscoveryRegistry表示应用级远程服务注册发现协议,内部的ServiceDiscovery,如ZookeeperServiceDiscovery
    registryManager.getServiceDiscoveries().forEach(ServiceDiscovery::update);
}

7.1 ServiceDiscovery#update更新服务实例

更新服务实例的方法,和注册的方法差不多,在此不再赘诉。

java 复制代码
/**
 * AbstractServiceDiscovery的方法
 * <p>
 * 更新服务实例信息
 */
@Override
public synchronized void update() throws RuntimeException {
    if (isDestroy) {
        return;
    }
    //如果服务实例为null,那么先根据服务元数据信息创建应用级服务实例ServiceInstance
    if (this.serviceInstance == null) {
        this.serviceInstance = createServiceInstance(this.metadataInfo);
    }
    //找不到有效的实例,请停止将实例地址注册到注册中心
    else if (!isValidInstance(this.serviceInstance)) {
        ServiceInstanceMetadataUtils.customizeInstance(this.serviceInstance, this.applicationModel);
    }
    //找不到有效的实例,请停止将实例地址注册到注册中心
    if (!isValidInstance(this.serviceInstance)) {
        return;
    }
    //计算是否更新实例版本号revision
    boolean revisionUpdated = calOrUpdateInstanceRevision(this.serviceInstance);
    //如果更新了revision,那么重新注册服务实例信息到注册中心
    if (revisionUpdated) {
        logger.info(String.format("Metadata of instance changed, updating instance with revision %s.", this.serviceInstance.getServiceMetadata().getRevision()));
        //更新服务元数据信息到注册中心
        doUpdate(this.serviceInstance);
    }
}

8 总结

到此,我们的Dubbo3的服务发布导出以及服务引入的源码基本学习完毕,后面我们继续学习Dubbo的服务调用源码!

相关推荐
F-2H1 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
苹果酱05671 小时前
「Mysql优化大师一」mysql服务性能剖析工具
java·vue.js·spring boot·mysql·课程设计
_oP_i2 小时前
Pinpoint 是一个开源的分布式追踪系统
java·分布式·开源
mmsx2 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
武子康3 小时前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
豪宇刘4 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意4 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
FF在路上4 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进5 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人5 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言