Dubbo 3.x源码(26)—Dubbo服务引用源码(9)应用级服务发现订阅refreshServiceDiscoveryInvoker
基于Dubbo 3.1,详细介绍了Dubbo服务的发布与引用的源码。
此前我们学习了MigrationRuleHandler这个处理器,它用于通过动态更改规则来控制迁移行为。MigrationRuleListener的onrefer方法是Dubbo2.x 接口级服务发现与Dubbo3.x应用级服务发现之间迁移的关键。
我们最后讲到了MigrationRuleHandler的refreshInvoker方法,该方法除了刷新invoker迁移新规则之外,还负责远程服务发现订阅的逻辑,即消费者能发现远程服务提供方的地址列表,而应用级的服务引入订阅则是通过refreshServiceDiscoveryInvoker方法实现的。
我们此前学习了接口级服务引入的方法refreshInterfaceInvoker的源码,应用级的服务引入和此方法有很多相似之处,对于相同的方法,我们不再赘述。
Dubbo 3.x服务引用源码:
- Dubbo 3.x源码(11)—Dubbo服务的发布与引用的入口
- Dubbo 3.x源码(18)—Dubbo服务引用源码(1)
- Dubbo 3.x源码(19)—Dubbo服务引用源码(2)
- Dubbo 3.x源码(20)—Dubbo服务引用源码(3)
- Dubbo 3.x源码(21)—Dubbo服务引用源码(4)
- Dubbo 3.x源码(22)—Dubbo服务引用源码(5)服务引用bean的获取以及懒加载原理
- Dubbo 3.x源码(23)—Dubbo服务引用源码(6)MigrationRuleListener迁移规则监听器
- Dubbo 3.x源码(24)—Dubbo服务引用源码(7)接口级服务发现订阅refreshInterfaceInvoker
- Dubbo 3.x源码(25)—Dubbo服务引用源码(8)notify订阅服务通知更新
- Dubbo 3.x源码(26)—Dubbo服务引用源码(9)应用级服务发现订阅refreshServiceDiscoveryInvoker
- Dubbo 3.x源码(27)—Dubbo服务引用源码(10)subscribeURLs订阅应用级服务url
Dubbo 3.x服务发布源码:
- Dubbo 3.x源码(11)—Dubbo服务的发布与引用的入口
- Dubbo 3.x源码(12)—Dubbo服务发布导出源码(1)
- Dubbo 3.x源码(13)—Dubbo服务发布导出源码(2)
- Dubbo 3.x源码(14)—Dubbo服务发布导出源码(3)
- Dubbo 3.x源码(15)—Dubbo服务发布导出源码(4)
- Dubbo 3.x源码(16)—Dubbo服务发布导出源码(5)
- Dubbo 3.x源码(17)—Dubbo服务发布导出源码(6)
1 refreshServiceDiscoveryInvoker刷新应用级inovker
该方法具有应用级别的远程服务发现、引入、订阅能力,大概逻辑为:
- 首先判断是否需要刷新serviceDiscoveryInvoker,即重新创建真实的服务级invoker:如果真实serviceDiscoveryInvoker不存在,或者已被销毁,或者内部没有Directory,则需要刷新。
- 一般情况下,当启动消费者并首次执行refer的时候,真实serviceDiscoveryInvoker为null,需要创建serviceDiscoveryInvoker。
- 通过注册中心操作类registryProtocol#getServiceDiscoveryInvoker方法来引入服务提供者serviceDiscoveryInvoker,这是消费者进行应用级别服务发现订阅的核心逻辑。
/*** MigrationInvoker的方法** 刷新应用invoker* @param latch 倒计数器*/
protected void refreshServiceDiscoveryInvoker(CountDownLatch latch) {/** 1 如果MigrationInvoker内部的真实serviceDiscoveryInvoker存在,那么清空真实serviceDiscoveryInvoker的directory的*/clearListener(serviceDiscoveryInvoker);/** 2 判断是否需要刷新服务级serviceDiscoveryInvoker* 如果真实invoker不存在,或者已被销毁,或者内部没有Directory* 一般情况下,当启动消费者并首次执行refer的时候,真实invoker为null,需要创建*/if (needRefresh(serviceDiscoveryInvoker)) {if (logger.isDebugEnabled()) {logger.debug("Re-subscribing instance addresses, current interface " + type.getName());}//如果不为null,则销毁if (serviceDiscoveryInvoker != null) {serviceDiscoveryInvoker.destroy();}/** 3 通过注册中心操作类registryProtocol获取真实serviceDiscoveryInvoker** 这是消费者进行应用级服务发现订阅的核心逻辑,设这里的registryProtocol类型为InterfaceCompatibleRegistryProtocol*/serviceDiscoveryInvoker = registryProtocol.getServiceDiscoveryInvoker(cluster, registry, type, url);}/** 设置监听器*/setListener(serviceDiscoveryInvoker, () -> {latch.countDown();if (reportService.hasReporter()) {reportService.reportConsumptionStatus(reportService.createConsumptionReport(consumerUrl.getServiceInterface(), consumerUrl.getVersion(), consumerUrl.getGroup(), "app"));}if (step == APPLICATION_FIRST) {calcPreferredInvoker(rule);}});
}
2 getServiceDiscoveryInvoker获取invoker
这是默认消费者进行应用级服务发现订阅的核心逻辑,这里的registryProtocol类型为InterfaceCompatibleRegistryProtocol。
- 调用父类RegistryProtocol#getRegistryUrl方法,将注册中心协议url转换为应用级服务发现协议url,即service-discovery-registry协议。
- 随后调用getRegistry方法,根据应用级服务发现协议url获取注册中心操作类Registry,service-discovery-registry协议对应着ServiceDiscoveryRegistry。
- 创建应用级动态注册心中目录ServiceDiscoveryRegistryDirectory,随后调用doCreateInvoker方法创建服务引入invoker。
/*** InterfaceCompatibleRegistryProtocol的方法* <p>* 获取应用级别invoker** @param cluster 集群操作对象* @param registry 注册中心对象,例如ListenerRegistryWrapper(ZookeeperRegistry)* @param type 接口类型* @param url 注册中心协议url,协议是真实注册中心协议,例如zookeeper* @return 真实invoker*/
@Override
public <T> ClusterInvoker<T> getServiceDiscoveryInvoker(Cluster cluster, Registry registry, Class<T> type, URL url) {//调用父类RegistryProtocol的getRegistryUrl方法,将注册中心协议url转换为应用级服务发现协议url,即service-discovery-registry协议//根据应用级服务发现协议url获取注册中心操作类Registry,service-discovery-registry对应着ServiceDiscoveryRegistryregistry = getRegistry(super.getRegistryUrl(url));/** 创建应用级动态注册心中目录ServiceDiscoveryRegistryDirectory*/DynamicDirectory<T> directory = new ServiceDiscoveryRegistryDirectory<>(type, url);/** 创建invoker*/return doCreateInvoker(directory, cluster, registry, type);
}
3 ServiceDiscoveryRegistryDirectory注册中心目录
ServiceDiscoveryRegistryDirectory是基于应用级注册中心的服务发现使用的服务目录,我们在接口级服务发现订阅refreshInterfaceInvoker部分已经讲过Directory的作用了,在此不再赘述。
public ServiceDiscoveryRegistryDirectory(Class<T> serviceType, URL url) {//父类DynamicDirectory的构造器super(serviceType, url);moduleModel = getModuleModel(url.getScopeModel());//服务提供者优先的属性Set<ProviderFirstParams> providerFirstParams = url.getOrDefaultApplicationModel().getExtensionLoader(ProviderFirstParams.class).getSupportedExtensionInstances();if (CollectionUtils.isEmpty(providerFirstParams)) {this.providerFirstParams = null;} else {if (providerFirstParams.size() == 1) {this.providerFirstParams = Collections.unmodifiableSet(providerFirstParams.iterator().next().params());} else {Set<String> params = new HashSet<>();for (ProviderFirstParams paramsFilter : providerFirstParams) {if (paramsFilter.params() == null) {break;}params.addAll(paramsFilter.params());}this.providerFirstParams = Collections.unmodifiableSet(params);}}//获取消费者需要查询过滤的协议String protocol = consumerUrl.getParameter(PROTOCOL_KEY, consumerUrl.getProtocol());//消费者协议服务keyconsumerProtocolServiceKey = new ProtocolServiceKey(consumerUrl.getServiceInterface(), consumerUrl.getVersion(), consumerUrl.getGroup(),!CommonConstants.CONSUMER.equals(protocol) ? protocol : null);
}
4 doCreateInvoker创建invoker
该方法由InterfaceCompatibleRegistryProtocol的父类RegistryProtocol实现。大概步骤为:
- 首先根据消费者信息转换为消费者注册信息url,内部包括消费者ip、指定引用的protocol(默认consumer协议)、指定引用的服务接口、指定引用的方法以及其他消费者信息。
- 调用registry.register方法将消费者注册信息url注册到注册中心。
- 调用directory.buildRouterChain方法构建服务调用路由链。
- 调用directory.subscribe方法进行服务发现、引入并订阅服务。
- 调用cluster.join方法进行集群容错能力包装。
接口级的服务发现同样是调用该方法,区别是应用级调用的方法中registry参数底层对象为ServiceDiscoveryRegistry类型,directory参数为ServiceDiscoveryRegistryDirectory类型,接口级调用的方法中registry参数底层对象为ZookeeperRegistry类型,directory参数为RegistryDirectory类型。
/*** RegistryProtocol的方法* 创建ClusterInvoker** @param directory 动态目录* @param cluster 集群* @param registry 注册中心* @param type 服务接口类型* @return ClusterInvoker*/
protected <T> ClusterInvoker<T> doCreateInvoker(DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {//注册中心操作类directory.setRegistry(registry);//设置协议,Protocol$Adaptivedirectory.setProtocol(protocol);// all attributes of REFER_KEY 消费者服务引用参数Map<String, String> parameters = new HashMap<>(directory.getConsumerUrl().getParameters());//消费者信息转消费者注册信息urlURL urlToRegistry = new ServiceConfigURL(//获取protocol属性,只调用指定协议的服务提供方,其它协议忽略,默认值consumerparameters.get(PROTOCOL_KEY) == null ? CONSUMER : parameters.get(PROTOCOL_KEY),//消费者ipparameters.remove(REGISTER_IP_KEY),//端口0,//服务接口路径getPath(parameters, type),//服务引用参数parameters);urlToRegistry = urlToRegistry.setScopeModel(directory.getConsumerUrl().getScopeModel());urlToRegistry = urlToRegistry.setServiceModel(directory.getConsumerUrl().getServiceModel());//是否应该注册,默认trueif (directory.isShouldRegister()) {//设置注册的消费者urldirectory.setRegisteredConsumerUrl(urlToRegistry);/** 1 消费者注册信息url注册到注册中心*/registry.register(directory.getRegisteredConsumerUrl());}/** 2 构建服务路由器链*/directory.buildRouterChain(urlToRegistry);/** 3 服务发现并订阅服务*/directory.subscribe(toSubscribeUrl(urlToRegistry));/** 4 集群容错包装*/return (ClusterInvoker<T>) cluster.join(directory, true);
}
4.1 register注册应用级消费者信息
该方法的源码我们在此前学习provider****导出服务并且应用级服务注册到注册中心的时候就讲过了,即注册应用级别服务消费者和提供者信息是同一个方法。
与之前讲的接口级服务引入的注册不同的是,应用级服务引入的服务消费者url将不会注册到注册中心,这样减轻了注册中心的压力。
/*** ServiceDiscoveryRegistry的方法* * @param url Registration information , is not allowed to be empty, e.g: dubbo://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin*/
@Override
public final void register(URL url) {//只注册提供者,如果是消费者url直接返回if (!shouldRegister(url)) { // Should Not Registerreturn;}//执行注册doRegister(url);
}
4.2 subscribe应用级服务发现和订阅
该方法首先将当前RegistryDirectory实例加入到节点目录变化的回调通知监听器集合中,用以接收通知。随后调用父类DynamicDirectory的subscribe方法订阅服务。
/*** ServiceDiscoveryRegistryDirectory的方法** 应用级服务发现并订阅服务** @param url 服务消费者url*/
@Override
public void subscribe(URL url) {//获取enable-configuration-listen属性,即是否支持配置监听,默认trueif (moduleModel.getModelEnvironment().getConfiguration().convert(Boolean.class, Constants.ENABLE_CONFIGURATION_LISTEN, true)) {//设置为trueenableConfigurationListen = true;//将当前ServiceDiscoveryRegistryDirectory加入到节点目录变化的回调通知监听器集合中getConsumerConfigurationListener(moduleModel).addNotifyListener(this);//引用配置监听器referenceConfigurationListener = new ReferenceConfigurationListener(this.moduleModel, this, url);} else {enableConfigurationListen = false;}//调用父类DynamicDirectory的subscribe方法super.subscribe(url);
}
DynamicDirectory的subscribe方法如下,可以看到最终还是依靠ServiceDiscoveryRegistry#subscribe方法实现应用级服务订阅的。
/*** DynamicDirectory的方法* 订阅服务** @param url 服务消费者url*/
public void subscribe(URL url) {//设置subscribeUrl属性setSubscribeUrl(url);//调用registry注册中心的subscribe方法实现服务订阅registry.subscribe(url, this);
}
4.2.1 ServiceDiscoveryRegistry#subscribe应用级服务订阅
ServiceDiscoveryRegistry实现了该方法,而ZookeeperRegistry没有实现改方法。
- 首先调用!shouldSubscribe方法判断是否不应该订阅,内部调用的!shouldRegister方法。也就是说应用级服务提供者只会注册不会订阅,而应用级的服务消费者只会订阅不会注册。
- 然后调用doSubscribe方法对应用级的服务消费者执行服务订阅。
/*** ServiceDiscoveryRegistry的方法** @param url 订阅者url* @param listener 通知监听器*/
@Override
public final void subscribe(URL url, NotifyListener listener) {//是否不应该订阅,内部调用的shouldRegister方法//也就是说应用级服务提供者只会注册不会订阅,而应用级的服务消费者只会订阅不会注册if (!shouldSubscribe(url)) { // Should Not Subscribereturn;}//应用级的服务消费者执行服务订阅doSubscribe(url, listener);
}
4.3 ServiceDiscoveryRegistry#doSubscribe执行服务发现订阅
doSubscribe方法执行应用级服务发现订阅,大概步骤为:
- 通过ZookeeperServiceDiscovery#subscribe方法执行应用级服务发现订阅。这里仅仅是将订阅者url加入到metadataInfo的subscribedServiceURLs缓存中,没有真正的订阅操作。
- 通serviceNameMapping#getAndListen从元数据中心获取当前服务接口映射的服务提供者服务名列表。在应用级服务提供者启动过程中,会将服务接口到服务名的映射关系发布到远程元数据中心。
- 调用subscribeURLs方法,继续执行服务url订阅,这里才会执行真正的订阅逻辑,将会根据服务名去注册中心找到服务实例,然后对服务实例分组并通过rpc调用服务实例的MetaDataService服务获取服务元数据。最后构建服务实例url,notify通知ServiceDiscoveryRegistryDirectory,进一步的创建invoker。
/*** ServiceDiscoveryRegistry的方法* <p>* 执行服务发现订阅** @param url 订阅者url* @param listener 通知监听器,ServiceDiscoveryRegistryDirectory*/@Overridepublic void doSubscribe(URL url, NotifyListener listener) {url = addRegistryClusterKey(url);/** 1 通过ZookeeperServiceDiscoveryy#subscribe方法执行应用级服务发现订阅* 这里仅仅是将订阅者url加入到metadataInfo的subscribedServiceURLs缓存中,没有真正的订阅操作。*/serviceDiscovery.subscribe(url, listener);boolean check = url.getParameter(CHECK_KEY, false);/** 2 从元数据中心获取当前服务接口映射的服务提供者服务名列表** 在应用级服务提供者启动过程中,会将服务接口到服务名的映射关系发布到远程元数据中心*///构建服务接口到服务名的映射关系key,就是serviceInterface,即服务接口全路径名String key = ServiceNameMapping.buildMappingKey(url);//获取对应的锁,每一个key对应一把锁Lock mappingLock = serviceNameMapping.getMappingLock(key);try {//加锁mappingLock.lock();/** 尝试从缓存获取需要订阅的服务名列表,作为初始化服务名列表,默认null* 这是一个Dubbo实现的LUR缓存,默认最多10000个mapping缓存,原理是很简单的继承LinkedHashMap的方式*/Set<String> subscribedServices = serviceNameMapping.getCachedMapping(url);try {//服务映射监听器MappingListener mappingListener = new DefaultMappingListener(url, subscribedServices, listener);/** 核心方法* 根据注册中心协议url,服务消费者url,服务映射监听器,从元数据中心获取当前服务接口对应的服务提供者服务名列表*/subscribedServices = serviceNameMapping.getAndListen(this.getUrl(), url, mappingListener);//存入监听器缓存mappingListeners.put(url.getProtocolServiceKey(), mappingListener);} catch (Exception e) {logger.warn("Cannot find app mapping for service " + url.getServiceInterface() + ", will not migrate.", e);}if (CollectionUtils.isEmpty(subscribedServices)) {logger.info("No interface-apps mapping found in local cache, stop subscribing, will automatically wait for mapping listener callback: " + url);
// if (check) {
// throw new IllegalStateException("Should has at least one way to know which services this interface belongs to, subscription url: " + url);
// }return;}/** 3 应用级别的服务url订阅** 这里才会执行真正的订阅*/subscribeURLs(url, listener, subscribedServices);} finally {mappingLock.unlock();}}
4.3.1 ZookeeperServiceDiscovery#subscribe基于zk的应用级服务订阅
该方法是父类AbstractServiceDiscovery实现的,内部调用metadataInfo的addService方法。
我们此前学习应用级服务注册的时候,ServiceDiscoveryRegistry#register方法同样是依靠父类AbstractServiceDiscovery实现,其内部也是调用的,内部调用metadataInfo的addService方法。
/*** AbstractServiceDiscovery的方法** 执行应用级订阅** @param url 订阅者url* @param listener 通知监听器,ServiceDiscoveryRegistryDirectory*/
@Override
public void subscribe(URL url, NotifyListener listener) {//添加订阅urlmetadataInfo.addSubscribedURL(url);
}
addSubscribedURL方法实际上仅仅将订阅者url加入到subscribedServiceURLs缓存map中就结束了,key为serviceKey,规则为{group}/{interfaceName}:{version},除此之外没有其他的操作。
/*** MetadataInfo的方法* 添加订阅url* @param url 订阅者url*/
public synchronized void addSubscribedURL(URL url) {//第一次添加时初始化subscribedServiceURLs集合,这里使用的是一个跳表if (subscribedServiceURLs == null) {subscribedServiceURLs = new ConcurrentSkipListMap<>();}//将url加入到subscribedServiceURLs集合addURL(subscribedServiceURLs, url);
}private boolean addURL(Map<String, SortedSet<URL>> serviceURLs, URL url) {//加入到serviceURLs集合, key为{group}/{interfaceName}:{version}SortedSet<URL> urls = serviceURLs.computeIfAbsent(url.getServiceKey(), this::newSortedURLs);// make sure the parameters of tmpUrl is variablereturn urls.add(url);
}
4.3.2 MetadataServiceNameMapping#getAndListen获取并订阅服务映射信息
我们此前在应用级服务提供者启动过程中讲过,在最后会将服务接口到服务名的映射关系发布到远程元数据中心。
而在应用级别消费者启动过程中,在引用服务的时候也会根据接口(服务接口到服务名的映射关系key,就是serviceInterface,即服务接口全路径名)去元数据中心查找当前要引入的服务接口对应的服务提供者服务名列表,并且还会进行订阅,当服务映射数据变更时会更新内存数据。
该方法的大概步骤为:
- 首先尝试从本地LUR缓存中获取mapping,如果没有获取到,那么将会创建一个异步监听器AsyncMappingTask,主动调用call方法同步拉取元数据中心的mapping映射信息,获取的数据不会通知监听器DefaultMappingListener立即更新缓存。
- 如果缓存已存在,那么将会创建一个异步监听器AsyncMappingTask提交到线程池,异步的获取的数据,并且会通知监听器DefaultMappingListener更新缓存。
/*** AbstractServiceNameMapping的方法** 获取并监听服务映射** @param registryURL 注册中心协议url* @param subscribedURL 服务消费者url* @param listener 通知监听器DefaultMappingListener,内部含有ServiceDiscoveryRegistryDirectory* @return 构建服务接口到服务名的映射关系key,就是serviceInterface,即服务接口全路径名*/
@Override
public Set<String> getAndListen(URL registryURL, URL subscribedURL, MappingListener listener) {//构建服务接口到服务名的映射关系key,就是serviceInterface,即服务接口全路径名String key = ServiceNameMapping.buildMappingKey(subscribedURL);//首先从本地LUR缓存中获取mappingSet<String> mappingServices = this.getCachedMapping(key);// Asynchronously register listener in case previous cache does not exist or cache expired.//如果缓存不存在if (CollectionUtils.isEmpty(mappingServices)) {try {logger.info("Local cache mapping is empty");/** 创建一个异步监听器,主动调用call方法同步拉取元数据中心的mapping映射信息,获取的数据不会通知监听器DefaultMappingListener*/mappingServices = (new AsyncMappingTask(listener, subscribedURL, false)).call();} catch (Exception e) {// ignore}//如果注册中心数据不存在if (CollectionUtils.isEmpty(mappingServices)) {//从url获取subscribed-services参数,表示手动指定的需要订阅的服务名,以,分割多个值String registryServices = registryURL.getParameter(SUBSCRIBED_SERVICE_NAMES_KEY);if (StringUtils.isNotEmpty(registryServices)) {logger.info(subscribedURL.getServiceInterface() + " mapping to " + registryServices + " instructed by registry subscribed-services.");//以,分割多个值mappingServices = parseServices(registryServices);}}//如果找到了mapping数据,那么存入LRU缓存中if (CollectionUtils.isNotEmpty(mappingServices)) {this.putCachedMapping(key, mappingServices);}}//如果存在本地缓存else {//获取异步mapping任务执行器mappingRefreshingExecutorExecutorService executorService = applicationModel.getFrameworkModel().getBeanFactory().getBean(FrameworkExecutorRepository.class).getMappingRefreshingExecutor();//创建一个异步监听器提交到线程池,获取的数据将会通知监听器DefaultMappingListener更新缓存executorService.submit(new AsyncMappingTask(listener, subscribedURL, true));}return mappingServices;
}
4.3.2.1 AsyncMappingTask#call从元数据中心获取mapping
AsyncMappingTask#call方法将会从元数据中心远程拉取服务映射信息。实际上内部仍然是调用MetadataServiceNameMapping的另一个重载的getAndListen方法从元数据中心获取服务接口到服务名的映射信息。
/*** AsyncMappingTask的方法** 从元数据中心远程拉取服务映射信息*/
@Override
public Set<String> call() throws Exception {synchronized (mappingListeners) {//构建空集合Set<String> mappedServices = emptySet();try {//key,目前是仅仅是服务接口全路径名String mappingKey = ServiceNameMapping.buildMappingKey(subscribedURL);//如果监听器不为空if (listener != null) {//调用getAndListen方法元数据中心远程拉取服务映射信息并且注册监听mappedServices = toTreeSet(getAndListen(subscribedURL, listener));//讲key和对应的listener加入到外部类的mappingListeners映射中Set<MappingListener> listeners = mappingListeners.computeIfAbsent(mappingKey, _k -> new HashSet<>());listeners.add(listener);//是否立即通知监听器,if (CollectionUtils.isNotEmpty(mappedServices)) {if (notifyAtFirstTime) {// guarantee at-least-once notification no matter what kind of underlying meta server is used.// listener notification will also cause updating of mapping cache.//通知更新缓存listener.onEvent(new MappingChangedEvent(mappingKey, mappedServices));}}} else {//没有监听器的情况mappedServices = get(subscribedURL);//直接存入缓存if (CollectionUtils.isNotEmpty(mappedServices)) {AbstractServiceNameMapping.this.putCachedMapping(mappingKey, mappedServices);}}} catch (Exception e) {logger.error("Failed getting mapping info from remote center. ", e);}return mappedServices;}
}
4.3.2.2 MetadataServiceNameMapping#getAndListen获取并监听服务映射信息
metadataReport.getServiceAppMapping方法从元数据中心远程拉取获取服务接口对应的服务名映射集合并且注册监听。
/*** MetadataServiceNameMapping的方法** 从元数据中心远程拉取服务映射信息并且注册监听** @param url 消费者url* @param mappingListener 监听器MappingListener* @return 服务映射信息*/
@Override
public Set<String> getAndListen(URL url, MappingListener mappingListener) {//服务接口String serviceInterface = url.getServiceInterface();// randomly pick one metadata report is ok for it's guaranteed all metadata report will have the same mapping data. //随机获取一个配置的注册中心idString registryCluster = getRegistryCluster(url);//首先获取注册中心id对应的元数据中心实例,如果没有则从metadataReports列表获取第一个元数据中心实例MetadataReport metadataReport = metadataReportInstance.getMetadataReport(registryCluster);if (metadataReport == null) {return Collections.emptySet();}//从元数据中心获取服务接口对应的服务名映射集合return metadataReport.getServiceAppMapping(serviceInterface, mappingListener, url);
}
获取元数据中实例时,首先获取随机注册中心id对应的元数据中心实例,如果没有则从metadataReports列表获取第一个元数据中心实例。
/*** MetadataReportInstance的方法** @param registryKey 注册中心id* @return 元数据中心实例*/
public MetadataReport getMetadataReport(String registryKey) {//首先获取注册中心id对应的元数据中心实例MetadataReport metadataReport = metadataReports.get(registryKey);//如果没有则从metadataReports列表获取第一个元数据中心实例if (metadataReport == null && metadataReports.size() > 0) {metadataReport = metadataReports.values().iterator().next();}return metadataReport;
}
4.3.2.3 ZookeeperMetadataReport#getServiceAppMapping获取服务映射
如果元数据中心是zookeeper,那么对应ZookeeperMetadataReport。他的getServiceAppMapping方法很简单,构建节点路径默认 dubbo/mapping/{serviceKey},例如/dubbo/mapping/org.apache.dubbo.demo.DemoService,然后注册监听器监听该节点的变化,最后获取获取节点的内容,也就是服务名映射字符串,并且通过,拆分为set集合。
/*** ZookeeperMetadataReport的方法* <p>* 从元数据中心远程拉取获取服务接口对应的服务名映射集合并且注册监听** @param serviceKey 服务接口* @param listener 监听器MappingListener* @param url 消费者url* @return 服务接口对应的服务名映射集合*/
@Override
public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {//构建节点路径,默认 dubbo/mapping/{serviceKey},例如String path = buildPathKey(DEFAULT_MAPPING_GROUP, serviceKey);//监听节点变化MappingDataListener mappingDataListener = casListenerMap.computeIfAbsent(path, _k -> {MappingDataListener newMappingListener = new MappingDataListener(serviceKey, path);//添加监听器zkClient.addDataListener(path, newMappingListener);return newMappingListener;});mappingDataListener.addListener(listener);//获取节点的内容,也就是服务名映射字符串,通过,拆分为set集合return getAppNames(zkClient.getContent(path));
}
我们在应用级服务提供者注册的时候就讲过了服务映射数据在元数据中心的样子,现在再来看看。
可以很明显的看出来所谓的服务映射是什么意思,也就是一个服务接口到对应的服务名的关系节点。有了服务映射,那么应用级消费者就能通过服务接口来查询对应的服务应用名了,这在consumer应用级服务发现的时候很有用。
5 总结
本次我们学习了Dubbo3的应用级服务发现订阅refreshServiceDiscoveryInvoker方法的源码,下文我们将会学习应用级服务发现订阅后半部分的源码,即在获取到服务应用名之后,通过subscribeURLs方法进行应用级别的服务url订阅的源码。
相关文章:
Dubbo 3.x源码(26)—Dubbo服务引用源码(9)应用级服务发现订阅refreshServiceDiscoveryInvoker
基于Dubbo 3.1,详细介绍了Dubbo服务的发布与引用的源码。 此前我们学习了MigrationRuleHandler这个处理器,它用于通过动态更改规则来控制迁移行为。MigrationRuleListener的onrefer方法是Dubbo2.x 接口级服务发现与Dubbo3.x应用级服务发现之间迁移的关键…...
java client http请求 返回数据 实时循环监听 url 中资源是否生成
1、php 中 执行 exec 调用操作系统 命令行 执行 以下 java 代码 生成 的jar 2、php 执行命令是 以上1 需要命令行 输入 参数 taskid 3、实现实时监听 MP3 url 是否生成 4、 package com.example.filedemo.controller;import java.io.BufferedReader; import java.io.InputStre…...
ONES 功能上新|ONES Copilot、ONES Wiki 新功能一览
ONES Copilot 可基于工作项的标题、描述、属性信息,对工作项产生的动态和评论生成总结。 针对不同类型的工作项,总结输出的内容有对应的侧重点。 应用场景: 在一些流程步骤复杂、上下游参与成员角色丰富的场景中,工作项动态往往会…...
【自适应】postcss-pxtorem适配Web端页面
在进行页面开发时,自适应设计是一个关键的考虑因素。为了实现这一点,postcss-pxtorem是一个非常有用的工具,它可以将CSS中的px单位转换为rem单位,从而实现基于根元素字体大小的自适应布局。下面介绍一下在项目中如何引入并配置pos…...
BOE(京东方)“向新2025”年终媒体智享会首站落地上海 六大维度创新开启产业发展新篇章
12月17日,BOE(京东方)以“向新2025”为主题的年终媒体智享会在上海启动。正值BOE(京东方)新三十年的开局之年,活动全面回顾了2024年BOE(京东方)在各领域所取得的领先成果,深度解读了六大维度的“向新”发展格局,同时详细剖析了BOE(京东方)在智能制造领域的领先实践。BOE(京东方…...
Moretl安全日志采集工具
永久免费: 至Gitee下载 使用教程: Moretl使用说明 使用咨询: 用途 定时全量或增量采集工控机,电脑文件或日志. 优势 开箱即用: 解压直接运行.不需额外下载.管理设备: 后台统一管理客户端.无人值守: 客户端自启动,自更新.稳定安全: 架构简单,兼容性好,通过授权控制访问. 架…...
LabVIEW农机自主导航监控系统
随着现代农业技术的快速发展,自主导航农机的需求日益增加,提高作业效率和减少劳动成本成为农业现代化的关键目标。本文介绍了一个基于LabVIEW的农机自主导航监控系统的开发案例,该系统通过先进的传感器与控制技术,实现农机在田间作…...
ChatGPT重大更新:新增实时搜索和高级语音
12月17日消息,据报道,OpenAI开启了第八天技术分享直播,对ChatGPT搜索功能进行了大量更新。 此次ChatGPT新增的功能亮点纷呈。其中,实时搜索功能尤为引人注目。OpenAI对搜索算法进行了深度优化,使得用户提出问题后&…...
爬虫基础学习
爬虫概念与工作原理 爬虫是什么:爬虫(Web Scraping)是自动化地访问网站并提取数据的技术。它模拟用户浏览器的行为,通过HTTP请求访问网页,解析HTML文档并提取有用信息。 爬虫的基本工作流程: 发送HTTP请求…...
一般行业安全管理人员考试题库分享
1.在高速运转的机械飞轮外部安装防护罩,属于(B)安全技术措施。 A.限制能量 B.隔离 C.故障设计 D.设置薄弱环节 2.生产经营单位的(B)是本单位安全生产的第一责任人,对落实本单位安全生产主体责任全面负责,具体履行安全生产管理职责。 A.全员 B…...
递归问题(c++)
递归设计思路 数列递归 : 如果一个数列的项与项之间存在关联性,那么可以使用递归实现 ; 原理 : 如果一个函数可以求A(n),那么该函数就可以求A(n-1),就形成了递归调用 ; 注意: 一般起始项是不需要求解的,是已知条件 这就是一个典型…...
企业数字化转型规划“秘籍”全解析
一、规划前奏:明确目标与洞察现状 (一)描绘数字化转型愿景 数字化转型愿景是工程设计总承包企业未来发展的蓝图,是企业数字化征程的指引。它不仅涉及技术更新,更是企业战略、运营模式和组织文化的深度重塑。企业需确保…...
达梦8-达梦数据的示例用户和表
1、示例库说明: 创建达梦数据的示例用户和表,导入测试数据。 在完成达梦数据库的安装之后,在/opt/dmdbms/samples/instance_script目录下有用于创建示例用户的SQL文件。samples目录前的路径根据实际安装情况进行修改,本文将达梦…...
day08-别名-重定向-去重排序等
1.重复用touch命令创建同一份文件,会修改文件的时间戳。 alias命令: 别名 查看已有别名:alias [rootoldboy ~]# alias alias cpcp -i alias egrepegrep --colorauto alias fgrepfgrep --colorauto alias grepgrep --colorauto alias l.ls…...
如何在 .NET Core 中轻松实现异步编程并提升性能
目录 初识异步编程 与多线程关系 异步编程操作 初识异步编程 异步编程:是指在执行某些任务时程序可以在等待某个操作完成的过程中继续执行其他任务,而不是阻塞当前线程,这在处理I/O密集型操作(如文件读取、数据库查询、网络请求等)时尤为重…...
makefile文件
简介: 自动化编译:只需要一个make命令,整个工程自动编译 提高编译效率:再次编译时,只编译修改的文件(查看时间戳,根据修改文件的时间判断文件是否被修改) 基本语法: …...
MybatisPlus使用LambdaQueryWrapper更新时 int默认值问题
问题: User user new User();user.setBalance(1000);QueryWrapper<User> queryWrapper new QueryWrapper<>();queryWrapper.eq("username","Jack");userMapper.update(user, queryWrapper);通过用户名,更新金额&…...
泷羽sec学习打卡-brupsuite7搭建IP炮台
声明 学习视频来自B站UP主 泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都 与本人无关,切莫逾越法律红线,否则后果自负 关于brupsuite的那些事儿-Brup-IP炮台搭建 搭建炮台服务端安装zmap1、更新系统和安装基础依赖ÿ…...
WPF系列一:窗口设置无边框
WindowStyle 设置:WindowStyle"None",窗口无法拖拽,但可纵向和横向拉伸 <Window x:Class"WPFDemo.MainWindow.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x&quo…...
《C++与 Armadillo:线性代数助力人工智能算法简化之路》
在人工智能领域,线性代数运算可谓是构建各类模型与算法的基石。从神经网络中的矩阵乘法、向量运算,到数据处理中的特征分解、奇异值分解等,无一不依赖高效且精准的线性代数计算。而 C作为一种强大且高效的编程语言,在人工智能开发…...
【新界面】基于卷积神经网络的垃圾分类(Matlab)
基于CNN的垃圾识别与分类GUI【新界面】 有需要可直接联系我,基本都在在线,能秒回!可加我看演示视频,不懂可以远程教学 1.此项目设计包括两份完整的源代码,有GUI界面的代码和无GUI界面系统的代码。 (以下部…...
阿尔茨海默症数据集,使用yolo,voc,coco格式对2013张原始图片进行标注,可识别轻微,中等和正常的症状
阿尔茨海默症数据集,使用yolo,voc,coco格式对2013张原始图片进行标注,可识别轻微,中等,严重和正常的症状 数据集分割 训练组100% 2013图片 有效集% 0图片 测试集…...
评估二分类模型性能之AUC-ROC 曲线
AUC-ROC 曲线 是评估二分类模型性能的重要工具。它结合了 受试者工作特性曲线 (Receiver Operating Characteristic, ROC) 和 曲线下面积 (Area Under the Curve, AUC),全面衡量分类器在不同阈值下的表现。 概念解释 1. ROC 曲线 ROC 曲线展示了分类器在不同阈值下…...
睡岗和玩手机数据集,4653张原始图,支持YOLO,VOC XML,COCO JSON格式的标注
睡岗和玩手机数据集,4653张原始图,支持YOLO,VOC XML,COCO JSON格式的标注 数据集分割 训练组70% 3257图片 有效集20% 931图片 测试集10% 465图片 预处理 没有采用任何预处…...
景联文科技:精准语音标注,驱动语音技术新发展
在人工智能迅速发展的今天,语音技术的应用已经渗透到我们生活的方方面面。从智能音箱、语音助手到自动语音识别系统,高质量的语音数据是这些应用成功的关键。景联文科技作为领先的AI数据服务提供商,专注于为客户提供高精度、高效的语音标注服…...
Linux 查看目录命令 ls 详细介绍
Linux 和 Unix 系统中 ls 命令是用于列出目录内容。用户可以查看指定目录下的文件和子目录,还可以获取有关这些文件和子目录的详细信息。 基本语法: ls [选项] [目录]如果不指定目录,ls 将列出当前工作目录下的内容。 01、-a 或 --all ls…...
Flux Tools 结构简析
Flux Tools 结构简析 BFL 这次一共发布了 Canny、Depth、Redux、Fill 四个 Tools 模型系列,分别对应我们熟悉的 ControlNets、Image Variation(IP Adapter)和 Inpainting 三种图片条件控制方法。虽然实现功能是相同的,但是其具体…...
从零开始:PHP基础教程系列-第13篇:构建简单的Web应用
从零开始:PHP基础教程系列 第13篇:构建简单的Web应用 在本篇文章中,我们将学习如何使用PHP构建一个简单的Web应用。这个应用将实现用户注册和登录功能,并使用PDO与MySQL数据库进行交互。我们将逐步实现这个应用的基本功能。 一…...
文件夹属性变0字节:全面解析与恢复指南
一、文件夹属性变0字节现象概述 在日常使用电脑的过程中,我们可能会遇到文件夹属性突然变为0字节的情况。这意味着文件夹中的文件列表或元数据被某种方式清空或损坏,导致系统无法正确读取文件夹的内容。当您尝试打开此类文件夹时,通常会收到…...
PDFMathTranslate 一个基于AI优秀的PDF论文翻译工具
PDFMathTranslate 是一个设想中的工具,旨在翻译PDF文档中的数学内容。以下是这个工具的主要特点和使用方法: 功能特点 数学公式识别:利用先进的OCR(光学字符识别)技术,精准识别PDF文档中的数学公式和文本…...
35. Three.js案例-创建带阴影的球体与平面
35. Three.js案例-创建带阴影的球体与平面 实现效果 知识点 WebGLRenderer WebGLRenderer 是Three.js中用于渲染场景的主要类之一,它负责将场景中的对象渲染到画布上。 构造器 new THREE.WebGLRenderer(parameters : Object) 参数类型描述parametersObject可选…...
【Linux】深入理解进程信号机制:信号的产生、捕获与阻塞
🎬 个人主页:谁在夜里看海. 📖 个人专栏:《C系列》《Linux系列》《算法系列》 ⛰️ 时间不语,却回答了所有问题 目录 📚前言 📚一、信号的本质 📖1.异步通信 📖2.信…...
Vue3.0使用JavaScript脚本实现Vue Router路由:页面跳转、获取URL参数
Vue 使用 Vue Router 路由系列文章: 《Vue使用Vue Router路由:开发单页应用》 《Vue使用Vue Router路由:通过URL传递与获取参数》 《Vue3.0使用JavaScript脚本实现Vue Router路由:页面跳转、获取URL参数》 1、路由基础 在单页 Web 应用中,整个项目只有一个 HTML 文件,不…...
2025山东科技大学考研专业课复习资料一览
[冲刺]2025年山东科技大学020200应用经济学《814经济学之西方经济学[宏观部分]》考研学霸狂刷870题[简答论述计算题]1小时前[强化]2025年山东科技大学085600材料与化工《817物理化学》考研强化检测5套卷22小时前[冲刺]2025年山东科技大学030100法学《704综合一[法理学、国际法学…...
lambda 表达式 闭包写法
lambda 表达式 1.用于 匿名委托函数表达 2.用于linq 查询表达 匿名方法表达 (参数)》{ 逻辑} 比如 (x,y)>{return xy;} 如果一个参数可不带(),如果逻辑简单可以不{} 比如 x>x 如果没有参…...
什么是正则化?Regularization: The Stabilizer of Machine Learning Models(中英双语)
正则化:机器学习模型的稳定器 1. 什么是正则化? 正则化(Regularization)是一种在机器学习模型训练中,通过约束模型复杂性以防止过拟合的技术。 它的核心目标是让模型不仅在训练集上表现良好,还能在测试集上…...
【西门子PLC.博途】——面向对象编程及输入输出映射FC块
当我们做面向对象编程的时候,需要用到输入输出的映射。这样建立的变量就能够被复用,从而最大化利用了我们建立的udt对象。 下面就来讲讲映射是什么。 从本质上来说,映射就是拿实际物理对象对应程序虚拟对象,假设程序对象是I0.0&…...
CS61a.1 textbook1.2 编程要素
1.structure and interpretation of computer programs Python 内置了对各种常见编程活动的支持, 例如,操作文本、显示图形以及通过 互联网。Python 代码行 >>> from urllib.request import urlopen是一个 import 语句,用于加载用…...
计算机毕业设计Django+Tensorflow音乐推荐系统 音乐可视化 卷积神经网络CNN LSTM音乐情感分析 机器学习 深度学习 Flask
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
使用国内镜像网站在线下载安装Qt(解决官网慢的问题)——Qt
国内镜像网站 中国科学技术大学:http://mirrors.ustc.edu.cn/qtproject/清华大学:https://mirrors.tuna.tsinghua.edu.cn/qt/北京理工大学:http://mirror.bit.edu.cn/qtproject/ 南京大学:https://mirror.nju.edu.cn/qt腾讯镜像&…...
乳腺癌多模态诊断解释框架:CNN + 可解释 AI 可视化
乳腺癌多模态诊断解释框架:CNN 可解释 AI 可视化 论文大纲理解1. 确认目标2. 分析过程(目标-手段分析)3. 实现步骤4. 效果展示 结构分析1. 层级结构分析叠加形态(从基础到高级)构成形态(部分到整体&#x…...
MySQL篇之对MySQL进行参数优化,提高MySQL性能
1. MySQL参数优化说明 MySQL 参数调优是提高数据库性能的重要手段之一。通过调整 MySQL 的配置参数,可以优化查询速度、提升并发处理能力、减少资源消耗等。 MySQL 的性能优化涉及到多个方面,包括内存管理、磁盘 I/O、查询优化、连接管理、复制配置等。…...
Scratch节日 | 快乐圣诞节——用编程传递节日祝福! ✨
今天为大家推荐一款充满节日气氛的Scratch项目——《快乐圣诞节》!这款圣诞主题动画贺卡项目不仅让小朋友们学习编程知识,还提供了一种用创意传递祝福的方式。通过编程打造星星闪烁的圣诞树,播放经典圣诞音乐,制作一张属于自己的节…...
android studio更改应用图片,和应用名字。
更改应用图标,和名字 先打开AndroidManifest.xml文件。 更改图片文件名字( 右键-->构建-->重命名(R))...
PHP8.4下webman直接使用topthink/think-orm
环境信息 操作系统win11php 8.4.1webman-framework ^1.6.8MySQL 8.4.3topthink/think-orm ^3.0 说明 PHP8.3以下版本 直接使用webman提供的webman/think-orm更方便。 PHP 环境换为 8.4 使用webman/think-orm 报了个错;所以换topthink/think-orm,根据文…...
uniapp 微信小程序 功能入口
单行单独展示 效果图 html <view class"shopchoose flex jsb ac" click"routerTo(要跳转的页面)"><view class"flex ac"><image src"/static/dyd.png" mode"aspectFit" class"shopchooseimg"&g…...
Halcon 机器视觉案例 之 连接件测量
第一篇 机器视觉案例 之 连接件测量 文章目录 第一篇 机器视觉案例 之 连接件测量1.案例要求2.实现思路2.1 读取单张图片并创建图像模板2.2 画出圆和直线2.3 创建测量模型2.4 循环读取多张图片并查找图像中连接件位置2.5 根据偏移量补偿使得测量模型移动至指定位置 3.实现效果4…...
druid与pgsql结合踩坑记
最近项目里面突然出现一个怪问题,数据库是pgsql,jdbc连接池是alibaba开源的druid,idea里面直接启动没问题,打完包放在centos上和windows上cmd窗口都能直接用java -jar命令启动,但是放到国产信创系统上就是报错…...
Windows环境 (Ubuntu 24.04.1 LTS ) 国内镜像,用apt-get命令安装RabbitMQ,java代码样例
一、环境 Windows11 WSL(Ubuntu 24.04.1) 二、思路 1 用Windows中的Ubuntu安装RabbitMQ,贴近Linux的线上环境; 2 RabbitMQ用erlang语言编写的,先安装erlang的运行环境; 2 用Linux的apt-get命令安装,解决软件依赖…...
RabbitMQ的核心组件有哪些?
大家好,我是锋哥。今天分享关于【RabbitMQ的核心组件有哪些?】面试题。希望对大家有帮助; RabbitMQ的核心组件有哪些? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 RabbitMQ是一个开源的消息代理(Messag…...