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,并且触发模块状态改变回调,进行应用级服务元数据发布。

/**
 * 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通知模块状态改变

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

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

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

3 checkState检查状态

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

* 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注册应用程序实例

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

/**
 * 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导出元数据服务

导出元数据服务。

/**
 * 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调用的方式远程调用元数据服务获取元数据,所以需要导出元数据服务。

/**
 * 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方法导出元数据服务,该方法我们此前就讲过了,就是通用的服务导出的入口方法。

/**
 * 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。

/**
 * 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方法刷新服务实例元数据。

/**
 * 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。

/**
 * 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方法重新注册服务实例信息到注册中心。**

/**
 * 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类型的应用级服务实例,然后将应用级服务元数据信息存储进去。

/**
 * 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,表示元数据一致,不需要重新上报。
/**
 * 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。
/**
 * 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,因此不会注册。

/**
 * 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的实现。

/**
 * 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服务实例。

/**
 * 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方法刷新一次服务元数据信息。

/**
 * 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更新服务实例

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

/**
 * 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的服务调用源码!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/942124.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电脑使用CDR时弹出错误“计算机丢失mfc140u.dll”是什么原因?“计算机丢失mfc140u.dll”要怎么解决?

电脑使用CDR时弹出“计算机丢失mfc140u.dll”错误&#xff1a;原因与解决方案 在日常电脑使用中&#xff0c;我们时常会遇到各种系统报错和文件丢失问题。特别是当我们使用某些特定软件&#xff0c;如CorelDRAW&#xff08;简称CDR&#xff09;时&#xff0c;可能会遇到“计算…

深入解读数据资产化实践指南(2024年)

本指南主要介绍了数据资产化的概念、目标和意义&#xff0c;以及实施数据资产化的过程。指南详细阐述了数据资产化的内涵&#xff0c;包括数据资产的定义、数据资产化的目标与意义&#xff0c;并介绍了数据资产化的过程包括业务数据化、数据资源化、数据产品化和数据资本化。 …

【算法篇】——数据结构中常见八大排序算法的过程原理详解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、插入排序1.直接插入法2.希尔排序法 二、交换排序1. 冒泡排序2. 快速排序 三、选择排序1. 简单选择排序2. 堆排序 四、归并排序五、基数排序 前言 C数据结构…

仿闲鱼的二手交易小程序软件开发闲置物品回收平台系统源码

市场前景 闲置物品交易软件的市场前景广阔&#xff0c;主要基于以下几个方面的因素&#xff1a; 环保意识提升&#xff1a;随着人们环保意识的增强&#xff0c;越来越多的人开始关注资源的循环利用&#xff0c;闲置物品交易因此受到了广泛的关注。消费升级与时尚节奏加快&…

情报信息收集能力

红队专题-Web渗透之资产思路框架知识整理 钓鱼社工 钓鱼自动化zip域名ARP欺骗快捷方式ToolsburpsuiteApp 抓包ffuf模糊测试QingScanWiresharkCloudCFEn-Decodeffffffff0xInfodirbdirmapdirsearchdnsenum使用测试常规使用使用字典文件进行dns查询子域名暴力查询部分C类IP地址IP块…

ensp 关于acl的运用和讲解

ACL&#xff08;Access Control List&#xff0c;访问控制列表&#xff09;是一种常用于网络设备&#xff08;如路由器、交换机&#xff09;上的安全机制&#xff0c;用于控制数据包的流动与访问权限。ACL 可以指定哪些数据包允许进入或离开某个网络接口&#xff0c;基于不同的…

5、mysql的读写分离

主从复制 主从复制的含义 主从复制&#xff1a;在一个mysql的集群当中&#xff0c;至少3台&#xff0c;即主1台&#xff0c;从2台。 当有数据写入时&#xff0c;主负责写入本库&#xff0c;然后把数据同步到从服务器。 一定是在主服务器写入数据&#xff0c;从服务器的写入…

高质量配音如何影响游戏的受欢迎度

在游戏行业中&#xff0c;创造沉浸式、引人入胜且令人难忘的体验往往决定了游戏的成功或失败。在影响游戏流行度的众多因素中&#xff0c;配音脱颖而出&#xff0c;成为将叙事与玩家互动连接起来的重要元素。高质量的配音将游戏中的对白转化为游戏的活跃部分&#xff0c;让玩家…

鸿蒙-expandSafeArea使用

应用未使用setWindowLayoutFullScreen()接口设置窗口全屏布局时&#xff0c;默认使能组件安全区布局。可以使用expandSafeArea属性扩展安全区域属性进行调整 扩展安全区域属性原理 布局阶段按照安全区范围大小进行UI元素布局。布局完成后查看设置了expandSafeArea的组件边界&…

Java测试开发平台搭建(四)拦截器

1. 拦截器的作用及使用场景 能够在请求的生命周期的不同阶段进行拦截和处理。常见的使用场景包括&#xff1a;1. 日志记录&#xff1a;记录请求和响应的日志。 2. 权限验证&#xff1a;检查用户的登录状态、权限。 3. 性能监控&#xff1a;记录请求的处理时间&#xff0c;监控…

window安装TradingView

目录 下载安装包 修改文件后缀&#xff0c;解压 将K线换成国内涨红跌绿样式 下载安装包 https://www.tradingview.com/desktop/ 下载完成后是.msix格式文件 &#xff08;我在win10和win11的系统中尝试运行msix都没有成功&#xff0c;所以放弃直接双击运行msix&#xff…

电子应用设计方案70:智能挂钟系统设计

智能挂钟系统设计 一、引言 随着科技的不断发展&#xff0c;传统挂钟也逐渐向智能化方向演进。智能挂钟不仅能够准确显示时间&#xff0c;还具备多种实用功能和智能交互特性&#xff0c;为用户带来更便捷、丰富的体验。 二、系统概述 1. 系统目标 - 高精度显示时间&#xff0…

vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0

一、vueelementui实现下拉表格多选搜索1.0 二、vueelementui实现下拉表格多选搜索分页回显全选2.0 在1.0的基础上&#xff0c;终于可以实现在下拉框表格分页的前提下不同页码的回显辣&#xff0c;分页是前端来分页的&#xff08;代码略乱且没有封装还很长&#xff0c;随便看看…

被裁20240927 --- 嵌入式硬件开发 前篇

前篇主要介绍一些相关的概念&#xff0c;用于常识扫盲&#xff0c;后篇开始上干货&#xff01; 他捧着一只碗吃过百家的饭 1. 处理器芯片1.1 处理器芯片制造商一、 英特尔&#xff08;Intel&#xff09;二、 三星&#xff08;SAMSUNG&#xff09;三、 高通&#xff08;Qualcomm…

【Web】2024“国城杯”网络安全挑战大赛决赛题解(全)

最近在忙联通的安全准入测试&#xff0c;很少有时间看CTF了&#xff0c;今晚抽点时间回顾下上周线下的题(期末还没开始复习&#x1f622;) 感觉做渗透测试一半的时间在和甲方掰扯&水垃圾洞&#xff0c;没啥惊喜感&#xff0c;还是CTF有意思 目录 Mountain ez_zhuawa 图…

高阶:基于Python paddleocr库 提取pdf 文档高亮显示的内容

预览 第1步&#xff1a;理解基本结构和导入必要的库 # 1. 首先导入需要的库 import os # 用于处理文件和路径 import cv2 # 用于图像处理 import numpy as np # 用于数值计算 from paddleocr import PaddleOCR # 用于文字识别 from pdf2image import convert_from_path #…

保护模式基本概念

CPU 架构 RISC&#xff08;Reduced Instruction Set Computer&#xff09; 中文即"精简指令集计算机”。RISC构架的指令格式和长度通常是固定的&#xff08;如ARM是32位的指令&#xff09;、且指令和寻址方式少而简单、大多数指令在一个周期内就可以执行完毕 CISC&…

@vue/cli启动异常:ENOENT: no such file or directory, scandir

参考:https://blog.csdn.net/qq_44355188/article/details/122239566 首先异常报错是&#xff1a;ENOENT: no such file or directory, scandir ‘D:\Data\Project\VueProject\hello\node_modulesvue\cli-plugin-eslint\locales’&#xff1b;我的vue/cli版本是 4.5.15 重点是…

全视通物联数据中台解决方案助力智慧医院新时代

全国医院物联网大会系列活动暨【行走的课堂】标杆研学 四川站“医院物联网应用创新经验交流会”&#xff0c;近日在成都召开。珠海全视通信息技术有限公司总经理林三朝以《物联网技术助力医院高质量发展》为题做了精彩演讲。林总就物联网技术如何助力医院高质量发展&#xff0c…

QT程序发布后,mysql在其它电脑设备无法连接数据库

QT程序发布后&#xff0c;mysql在其它电脑设备无法连接数据库 D:\mysql-5.7.24-winx64\lib, mysql-5.7.24-winx64是一个压缩包&#xff0c;用于启动mysql服务&#xff0c;创建数据库 压缩包 解决方法&#xff1a; 拷贝库到exe的相同目录&#xff0c;libmysql.dll,libmysql.li…