Dubbo应用级服务发现

转自:https://blog.csdn.net/songjunyan/article/details/126573516

 

问题起源

最近在用dubbo的时候,发现消费者会给非相同group的服务提供者发送getMetaInfo的请求,于是想研究一下到底是因为什么。看了一下源码,发现消费者从注册中心获取服务提供者信息是通过服务提供者的serverName,而具体serverName下的元数据中其实没有group的概念,默认dubbo3的元数据存储方式是local,所以即使是非相同group的提供者,也会收到getMetaInfo获取元数据的请求。

 

源码解析

1. Dubbo应用级服务发现

1.1 简介

这里我们要看的是MigrationInvoker类型的refreshServiceDiscoveryInvoker方法:根据应用级服务发现,创建应用级的服务调用器,这里有很多逻辑和接口级服务发现类似,不过与接口级调用的invoker对象不同的是应用级的粒度会比较大一些这一步不会去注意去订阅各个服务接口,只会订阅服务提供者的应用。

默认情况下如果没有配置强制走接口级或者应用级的服务配置,接口级逻辑和应用级服务订阅都会走,这里我们可以直接来看代码吧:
MigrationInvoker类型的refreshServiceDiscoveryInvoker方法

1.2 Invoker对象的创建

1.2.1 刷新服务发现调用器Invoker

下面这个入口代码和接口级的Invoker对象创建类似,唯一不同的是接口级Invoker对象的创建调用的是registryProtocol对象(InterfaceCompatibleRegistryProtocol类型)的getInvoker方法,而这里调用了getServiceDiscoveryInvoker

MigrationInvoker类型的refreshServiceDiscoveryInvoker方法代码如下:

 1   protected void refreshServiceDiscoveryInvoker(CountDownLatch latch) {
 2         clearListener(serviceDiscoveryInvoker);
 3         if (needRefresh(serviceDiscoveryInvoker)) {
 4             if (logger.isDebugEnabled()) {
 5                 logger.debug("Re-subscribing instance addresses, current interface " + type.getName());
 6             }
 7 
 8             if (serviceDiscoveryInvoker != null) {
 9                 serviceDiscoveryInvoker.destroy();
10             }
11             //registryProtocol类型为:InterfaceCompatibleRegistryProtocol
12             serviceDiscoveryInvoker = registryProtocol.getServiceDiscoveryInvoker(cluster, registry, type, url);
13         }
14         setListener(serviceDiscoveryInvoker, () -> {
15             latch.countDown();
16             if (reportService.hasReporter()) {
17                 reportService.reportConsumptionStatus(
18                     reportService.createConsumptionReport(consumerUrl.getServiceInterface(), consumerUrl.getVersion(), consumerUrl.getGroup(), "app"));
19             }
20             if (step == APPLICATION_FIRST) {
21                 calcPreferredInvoker(rule);
22             }
23         });
24     }

1.2.2 InterfaceCompatibleRegistryProtocol类型的getServiceDiscoveryInvoker方法

1   public <T> ClusterInvoker<T> getServiceDiscoveryInvoker(Cluster cluster, Registry registry, Class<T> type, URL url) {
2         //注册中心Registry类型对象获取 ,
3         //这里获取到的是ListenerRegistryWrapper 类型,其中包装了ServiceDiscoveryRegistry类型
4         registry = getRegistry(super.getRegistryUrl(url));
5         //服务发现注册目录对象创建  这里具体逻辑就不说了
6         DynamicDirectory<T> directory = new ServiceDiscoveryRegistryDirectory<>(type, url);
7         //开始创建invoker
8         return doCreateInvoker(directory, cluster, registry, type);
9     }

调用InterfaceCompatibleRegistryProtocol类型的父类型### ### 24.2.3 RegistryProtocol的doCreateInvoker方法:

下面这个代码接口级服务发现逻辑我中我们已经说过了代码是一样的RegistryProtocol的doCreateInvoker方法:

大部分逻辑是一样的 唯一有两个对象是不同的:

  • 应用级:registry类型服务发现的类型ServiceDiscoveryRegistry 接口级为:ZookeeperRegistry类型
  • 应用级:ServiceDiscoveryRegistryDirectory 接口级为:RegistryDirectory 类型
 1 protected <T> ClusterInvoker<T> doCreateInvoker(DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {
 2         directory.setRegistry(registry);
 3         directory.setProtocol(protocol);
 4         // all attributes of REFER_KEY
 5         Map<String, String> parameters = new HashMap<>(directory.getConsumerUrl().getParameters());
 6         URL urlToRegistry = new ServiceConfigURL(
 7             parameters.get(PROTOCOL_KEY) == null ? CONSUMER : parameters.get(PROTOCOL_KEY),
 8             parameters.remove(REGISTER_IP_KEY),
 9             0,
10             getPath(parameters, type),
11             parameters
12         );
13         urlToRegistry = urlToRegistry.setScopeModel(directory.getConsumerUrl().getScopeModel());
14         urlToRegistry = urlToRegistry.setServiceModel(directory.getConsumerUrl().getServiceModel());
15         if (directory.isShouldRegister()) {
16             directory.setRegisteredConsumerUrl(urlToRegistry);
17             //这一行逻辑会走到ServiceDiscoveryRegistry  不过应用级消费者是无需注册服务数据的
18             registry.register(directory.getRegisteredConsumerUrl());
19         }
20         directory.buildRouterChain(urlToRegistry);
21 
22         //发起订阅
23         directory.subscribe(toSubscribeUrl(urlToRegistry));
24 
25         return (ClusterInvoker<T>) cluster.join(directory, true);
26     }

1.3 应用级服务订阅

1.3.1 ServiceDiscoveryRegistry类型的subscribe方法:

 1   public void subscribe(URL url) {
 2         if (moduleModel.getModelEnvironment().getConfiguration().convert(Boolean.class, Constants.ENABLE_CONFIGURATION_LISTEN, true)) {
 3             enableConfigurationListen = true;
 4             //为ConsumerConfigurationListener类型中的listeners列表添加监听器: 监听器类型为ServiceDiscoveryRegistryDirectory
 5             getConsumerConfigurationListener(moduleModel).addNotifyListener(this);
 6             referenceConfigurationListener = new ReferenceConfigurationListener(this.moduleModel, this, url);
 7         } else {
 8             enableConfigurationListen = false;
 9         }
10         //调用父类类型DynamicDirectory的订阅方法subscribe 开始开启订阅逻辑 这个逻辑与接口级的逻辑是一样的
11         super.subscribe(url);
12     }
1 //调用父类类型DynamicDirectory的订阅方法subscribe 开始开启订阅逻辑 这个逻辑与接口级的逻辑是一样的
2     super.subscribe(url);

为了理解起来更直观我重复贴一下代码来重新看一遍:

1.3.2 DynamicDirectory的订阅方法subscribe

DynamicDirectory的订阅方法subscribe方法如下:

1     public void subscribe(URL url) {
2         setSubscribeUrl(url);
3         //这里先走ListenerRegistryWrapper的subscribe逻辑再走包装的ServiceDiscoveryRegistry类型的逻辑
4         registry.subscribe(url, this);
5     }

接下来的subscribe会先走ListenerRegistryWrapper的subscribe逻辑再走包装的ServiceDiscoveryRegistry类型的逻辑
接下来直接看下核心的一些代码:

1.3.3 ListenerRegistryWrapper类型的subscribe方法

 1  public void subscribe(URL url, NotifyListener listener) {
 2         try {
 3             if (registry != null) {
 4                 registry.subscribe(url, listener);
 5             }
 6         } finally {
 7             if (CollectionUtils.isNotEmpty(listeners)) {
 8                 RuntimeException exception = null;
 9                 for (RegistryServiceListener registryListener : listeners) {
10                     if (registryListener != null) {
11                         try {
12                             registryListener.onSubscribe(url, registry);
13                         } catch (RuntimeException t) {
14                             logger.error(t.getMessage(), t);
15                             exception = t;
16                         }
17                     }
18                 }
19                 if (exception != null) {
20                     throw exception;
21                 }
22             }
23         }
24     }

1.3.4 ServiceDiscoveryRegistry类型的subscribe方法:

1  public final void subscribe(URL url, NotifyListener listener) {
2         //前面是否注册shouldRegister为false这里是是否订阅shouldSubscribe方法结果为true
3         if (!shouldSubscribe(url)) { // Should Not Subscribe
4             return;
5         }
6         //执行订阅逻辑
7         doSubscribe(url, listener);
8     }

从这里开始要进入应用级服务订阅的路基了继续往下看

1.3.5 ServiceDiscoveryRegistry类型的doSubscribe方法:

 1 public void doSubscribe(URL url, NotifyListener listener) {
 2         url = addRegistryClusterKey(url);
 3         //服务发现类型为ZookeeperServiceDiscovery
 4         serviceDiscovery.subscribe(url, listener);
 5 
 6         boolean check = url.getParameter(CHECK_KEY, false);
 7 
 8         String key = ServiceNameMapping.buildMappingKey(url);
 9         //应用级服务发现悲观锁先加上一把
10         Lock mappingLock = serviceNameMapping.getMappingLock(key);
11         try {
12             mappingLock.lock();
13             Set<String> subscribedServices = serviceNameMapping.getCachedMapping(url);
14             try {
15                 MappingListener mappingListener = new DefaultMappingListener(url, subscribedServices, listener);
16                 //注意注意这行代码超级重要 当前是服务接口要找到服务的应用名字 将会查询映射信息对应节点:
17                 ///dubbo/mapping/link.elastic.dubbo.entity.DemoService
18                 //这里最终获取到的应用服务提供者名字集合为dubbo-demo-api-provider
19                 subscribedServices = serviceNameMapping.getAndListen(this.getUrl(), url, mappingListener);
20                 mappingListeners.put(url.getProtocolServiceKey(), mappingListener);
21             } catch (Exception e) {
22                 logger.warn("Cannot find app mapping for service " + url.getServiceInterface() + ", will not migrate.", e);
23             }
24 
25             if (CollectionUtils.isEmpty(subscribedServices)) {
26                 logger.info("No interface-apps mapping found in local cache, stop subscribing, will automatically wait for mapping listener callback: " + url);
27 //                if (check) {
28 //                    throw new IllegalStateException("Should has at least one way to know which services this interface belongs to, subscription url: " + url);
29 //                }
30                 return;
31             }
32             //执行订阅url的逻辑
33             subscribeURLs(url, listener, subscribedServices);
34         } finally {
35             mappingLock.unlock();
36         }
37     }

接下来看
服务发现类型为ZookeeperServiceDiscovery的subscribe方法,不过这里封装在父类型里面了,调用其父类型AbstractServiceDiscovery的subscribe方法

先看父类型AbstractServiceDiscovery的subscribe方法

1 public void subscribe(URL url, NotifyListener listener) {
2   //这个metadataInfo类型为MetadataInfo
3       metadataInfo.addSubscribedURL(url);
4   }

MetadataInfo类型的addSubscribedURL方法:

1  public synchronized void addSubscribedURL(URL url) {
2         if (subscribedServiceURLs == null) {
3             subscribedServiceURLs = new ConcurrentSkipListMap<>();
4         }
5         //下面将其url添加到subscribedServiceURLs成员变量里面就结束了
6         addURL(subscribedServiceURLs, url);
7     }

前面服务发现的subscribe并没有做什么逻辑性质的操作仅仅是将url放到了成员变量里面,接下来继续看ServiceDiscoveryRegistry类型的doSubscribe方法:

1.4 通过接口查询映射的应用信息

1.4.1 MetadataServiceNameMapping类型的getAndListen方法

通过服务接口信息查询应用名字 对应注册中心路径为:dubbo/mapping/link.elastic.dubbo.entity.DemoService
对应代码MetadataServiceNameMapping类型的getAndListen方法:

下面这个代码比较长,我们重点看一行代码
mappingServices = (new AsyncMappingTask(listener, subscribedURL, false)).call();

 1 public Set<String> getAndListen(URL registryURL, URL subscribedURL, MappingListener listener) {
 2         String key = ServiceNameMapping.buildMappingKey(subscribedURL);
 3         // use previously cached services.
 4         Set<String> mappingServices = this.getCachedMapping(key);
 5 
 6         // Asynchronously register listener in case previous cache does not exist or cache expired.
 7         if (CollectionUtils.isEmpty(mappingServices)) {
 8             try {
 9                 logger.info("Local cache mapping is empty");
10                 //重点看这个同步调用获取注册中心的路径信息 call方法在父类型AbstractServiceNameMapping中
11                 mappingServices = (new AsyncMappingTask(listener, subscribedURL, false)).call();
12             } catch (Exception e) {
13                 // ignore
14             }
15             if (CollectionUtils.isEmpty(mappingServices)) {
16                 String registryServices = registryURL.getParameter(SUBSCRIBED_SERVICE_NAMES_KEY);
17                 if (StringUtils.isNotEmpty(registryServices)) {
18                     logger.info(subscribedURL.getServiceInterface() + " mapping to " + registryServices + " instructed by registry subscribed-services.");
19                     mappingServices = parseServices(registryServices);
20                 }
21             }
22             if (CollectionUtils.isNotEmpty(mappingServices)) {
23                 this.putCachedMapping(key, mappingServices);
24             }
25         } else {
26             ExecutorService executorService = applicationModel.getFrameworkModel().getBeanFactory()
27                 .getBean(FrameworkExecutorRepository.class).getMappingRefreshingExecutor();
28             executorService.submit(new AsyncMappingTask(listener, subscribedURL, true));
29         }
30 
31         return mappingServices;
32     }

接下来看MetadataServiceNameMapping类型的父类型AbstractServiceNameMapping的call方法
同样道理这里主要关注getAndListen方法即可

 1   public Set<String> call() throws Exception {
 2             synchronized (mappingListeners) {
 3                 Set<String> mappedServices = emptySet();
 4                 try {
 5                     //这个缓存的key与服务接口和分组有关这里我没配置分组那就只有接口了 key为link.elastic.dubbo.entity.DemoService
 6                     String mappingKey = ServiceNameMapping.buildMappingKey(subscribedURL);
 7                     if (listener != null) {
 8                         //这里获取到的应用名字为:dubbo-demo-api-provider
 9                         mappedServices = toTreeSet(getAndListen(subscribedURL, listener));
10                         Set<MappingListener> listeners = mappingListeners.computeIfAbsent(mappingKey, _k -> new HashSet<>());
11                         listeners.add(listener);
12                         if (CollectionUtils.isNotEmpty(mappedServices)) {
13                             if (notifyAtFirstTime) {
14                                 // guarantee at-least-once notification no matter what kind of underlying meta server is used.
15                                 // listener notification will also cause updating of mapping cache.
16                                 listener.onEvent(new MappingChangedEvent(mappingKey, mappedServices));
17                             }
18                         }
19                     } else {
20                         mappedServices = get(subscribedURL);
21                         if (CollectionUtils.isNotEmpty(mappedServices)) {
22                             AbstractServiceNameMapping.this.putCachedMapping(mappingKey, mappedServices);
23                         }
24                     }
25                 } catch (Exception e) {
26                     logger.error("Failed getting mapping info from remote center. ", e);
27                 }
28                 return mappedServices;
29             }
30         }
31     }

1.4.2 AbstractServiceNameMapping的getAndListen方法

然后看AbstractServiceNameMapping的getAndListen方法

 1 public Set<String> getAndListen(URL url, MappingListener mappingListener) {
 2         String serviceInterface = url.getServiceInterface();
 3         // randomly pick one metadata report is ok for it's guaranteed all metadata report will have the same mapping data.
 4         String registryCluster = getRegistryCluster(url);
 5         MetadataReport metadataReport = metadataReportInstance.getMetadataReport(registryCluster);
 6         if (metadataReport == null) {
 7             return Collections.emptySet();
 8         }
 9         return metadataReport.getServiceAppMapping(serviceInterface, mappingListener, url);
10     }

1.4.3 ZookeeperMetadataReport类型的getServiceAppMapping方法:

 1  public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
 2         String path = buildPathKey(DEFAULT_MAPPING_GROUP, serviceKey);
 3         MappingDataListener mappingDataListener = casListenerMap.computeIfAbsent(path, _k -> {
 4             MappingDataListener newMappingListener = new MappingDataListener(serviceKey, path);
 5             zkClient.addDataListener(path, newMappingListener);
 6             return newMappingListener;
 7         });
 8         mappingDataListener.addListener(listener);
 9         //这个拼装后的路径为:/dubbo/mapping/link.elastic.dubbo.entity.DemoService
10         //这里获取到的应用名字为:dubbo-demo-api-provider
11         return getAppNames(zkClient.getContent(path));
12     }

这里贴个图展示下映射数据:

1.5 继续应用级服务订阅

1.5.1 ServiceDiscoveryRegistry类型的subscribeURLs方法

有了应用名字开始订阅服务提供者
ServiceDiscoveryRegistry类型的subscribeURLs方法
代码如下所示:

 1 protected void subscribeURLs(URL url, NotifyListener listener, Set<String> serviceNames) {
 2         serviceNames = toTreeSet(serviceNames);
 3         String serviceNamesKey = toStringKeys(serviceNames);
 4         String protocolServiceKey = url.getProtocolServiceKey();
 5         logger.info(String.format("Trying to subscribe from apps %s for service key %s, ", serviceNamesKey, protocolServiceKey));
 6 
 7         // register ServiceInstancesChangedListener
 8         Lock appSubscriptionLock = getAppSubscription(serviceNamesKey);
 9         try {
10             //再来一把url订阅的悲观锁
11             appSubscriptionLock.lock();
12             ServiceInstancesChangedListener serviceInstancesChangedListener = serviceListeners.get(serviceNamesKey);
13             if (serviceInstancesChangedListener == null) {
14                 serviceInstancesChangedListener = serviceDiscovery.createListener(serviceNames);
15                 serviceInstancesChangedListener.setUrl(url);
16                 //这个应用名字为:dubbo-demo-api-provider
17                 for (String serviceName : serviceNames) {
18                     //这个代码调用的是curator 框架的方法 通过应用名字节点查询节点下面的所有服务提供者的应用信息待会截图看
19                     List<ServiceInstance> serviceInstances = serviceDiscovery.getInstances(serviceName);
20                     if (CollectionUtils.isNotEmpty(serviceInstances)) {
21                         //发现了存在服务提供者则触发监听器开始进行应用发现通知
22                         serviceInstancesChangedListener.onEvent(new ServiceInstancesChangedEvent(serviceName, serviceInstances));
23                     }
24                 }
25                 serviceListeners.put(serviceNamesKey, serviceInstancesChangedListener);
26             }
27 
28             if (!serviceInstancesChangedListener.isDestroyed()) {
29                 serviceInstancesChangedListener.setUrl(url);
30                 listener.addServiceListener(serviceInstancesChangedListener);
31                 serviceInstancesChangedListener.addListenerAndNotify(protocolServiceKey, listener);
32                 serviceDiscovery.addServiceInstancesChangedListener(serviceInstancesChangedListener);
33             } else {
34                 logger.info(String.format("Listener of %s has been destroyed by another thread.", serviceNamesKey));
35                 serviceListeners.remove(serviceNamesKey);
36             }
37         } finally {
38             appSubscriptionLock.unlock();
39         }
40     }

查询到的应用信息:

DefaultServiceInstance{
    serviceName='dubbo-demo-api-provider', 
    host='192.168.1.169', port=20880, 
    enabled=true, healthy=true, 
    metadata={
        dubbo.endpoints=[{"port":20880,"protocol":"dubbo"}], 
        dubbo.metadata-service.url-params={"connections":"1","version":"1.0.0","dubbo":"2.0.2","release":"3.0.10","side":"provider","port":"20880","protocol":"dubbo"}, 
        dubbo.metadata.revision=af365a420dba83941ddf2087b998a1d2, 
        dubbo.metadata.storage-type=local, timestamp=1661078418865}}

这里展示图注册中心的数据:

1.6 服务通知

ServiceInstancesChangedListener类型的时间通知方法onEvent

1  public void onEvent(ServiceInstancesChangedEvent event) {
2         if (destroyed.get() || !accept(event) || isRetryAndExpired(event)) {
3             return;
4         }
5         doOnEvent(event);
6     }

1.6.1 ServiceInstancesChangedListener类型的时间通知方法的doOnEvent

继续看ServiceInstancesChangedListener类型的时间通知方法的doOnEvent

 1 private synchronized void doOnEvent(ServiceInstancesChangedEvent event) {
 2         if (destroyed.get() || !accept(event) || isRetryAndExpired(event)) {
 3             return;
 4         }
 5         //刷新内存数据
 6         refreshInstance(event);
 7 
 8         if (logger.isDebugEnabled()) {
 9             logger.debug(event.getServiceInstances().toString());
10         }
11 
12         Map<String, List<ServiceInstance>> revisionToInstances = new HashMap<>();
13         Map<String, Map<String, Set<String>>> localServiceToRevisions = new HashMap<>();
14 
15         // grouping all instances of this app(service name) by revision
16           //刷新内存数据
17         for (Map.Entry<String, List<ServiceInstance>> entry : allInstances.entrySet()) {
18             List<ServiceInstance> instances = entry.getValue();
19             for (ServiceInstance instance : instances) {
20                 String revision = getExportedServicesRevision(instance);
21                 if (revision == null || EMPTY_REVISION.equals(revision)) {
22                     if (logger.isDebugEnabled()) {
23                         logger.debug("Find instance without valid service metadata: " + instance.getAddress());
24                     }
25                     continue;
26                 }
27                 List<ServiceInstance> subInstances = revisionToInstances.computeIfAbsent(revision, r -> new LinkedList<>());
28                 subInstances.add(instance);
29             }
30         }
31 
32         // get MetadataInfo with revision
33         //重点看这里
34         for (Map.Entry<String, List<ServiceInstance>> entry : revisionToInstances.entrySet()) {
35             String revision = entry.getKey();
36             List<ServiceInstance> subInstances = entry.getValue();
37             //这里对应ZookeeperServiceDiscovery类型
38             MetadataInfo metadata = serviceDiscovery.getRemoteMetadata(revision, subInstances);
39             //解析元数据 最终结果存在localServiceToRevisions变量中 key为协议 值为服务接口与服务元数据信息
40             parseMetadata(revision, metadata, localServiceToRevisions);
41             // update metadata into each instance, in case new instance created.
42             //为每个实例更新其元数据信息
43             for (ServiceInstance tmpInstance : subInstances) {
44                 MetadataInfo originMetadata = tmpInstance.getServiceMetadata();
45                 if (originMetadata == null || !Objects.equals(originMetadata.getRevision(), metadata.getRevision())) {
46                     tmpInstance.setServiceMetadata(metadata);
47                 }
48             }
49         }
50 
51         int emptyNum = hasEmptyMetadata(revisionToInstances);
52         if (emptyNum != 0) {// retry every 10 seconds
53             hasEmptyMetadata = true;
54             if (retryPermission.tryAcquire()) {
55                 if (retryFuture != null && !retryFuture.isDone()) {
56                     // cancel last retryFuture because only one retryFuture will be canceled at destroy().
57                     retryFuture.cancel(true);
58                 }
59                 retryFuture = scheduler.schedule(new AddressRefreshRetryTask(retryPermission, event.getServiceName()), 10_000L, TimeUnit.MILLISECONDS);
60                 logger.warn("Address refresh try task submitted");
61             }
62             // return if all metadata is empty, this notification will not take effect.
63             if (emptyNum == revisionToInstances.size()) {
64                 logger.error("Address refresh failed because of Metadata Server failure, wait for retry or new address refresh event.");
65                 return;
66             }
67         }
68         hasEmptyMetadata = false;
69 
70         Map<String, Map<Set<String>, Object>> protocolRevisionsToUrls = new HashMap<>();
71         Map<String, Object> newServiceUrls = new HashMap<>();
72         for (Map.Entry<String, Map<String, Set<String>>> entry : localServiceToRevisions.entrySet()) {
73             String protocol = entry.getKey();
74             entry.getValue().forEach((protocolServiceKey, revisions) -> {
75                 Map<Set<String>, Object> revisionsToUrls = protocolRevisionsToUrls.computeIfAbsent(protocol, k -> new HashMap<>());
76                 Object urls = revisionsToUrls.get(revisions);
77                 if (urls == null) {
78                     urls = getServiceUrlsCache(revisionToInstances, revisions, protocol);
79                     revisionsToUrls.put(revisions, urls);
80                 }
81 
82                 newServiceUrls.put(protocolServiceKey, urls);
83             });
84         }
85 
86         this.serviceUrls = newServiceUrls;
87         this.notifyAddressChanged();
88     }

1.7 查询元数据(从提供者那里查询)

1.7.1 ZookeeperServiceDiscovery类型的getRemoteMetadata方法

ZookeeperServiceDiscovery类型的getRemoteMetadata方法

 1  public MetadataInfo getRemoteMetadata(String revision, List<ServiceInstance> instances) {
 2         MetadataInfo metadata = metaCacheManager.get(revision);
 3         //缓存的元数据为空将从元数据中心远端获取
 4         if (metadata != null && metadata != MetadataInfo.EMPTY) {
 5             metadata.init();
 6             // metadata loaded from cache
 7             if (logger.isDebugEnabled()) {
 8                 logger.debug("MetadataInfo for revision=" + revision + ", " + metadata);
 9             }
10             return metadata;
11         }
12  
13        //这里将从元数据中心获取数据
14         synchronized (metaCacheManager) {
15             // try to load metadata from remote.
16             int triedTimes = 0;
17             //失败则重试3次
18             while (triedTimes < 3) {
19                 //根据版本号查询元数据 发起一次RPC请求
20                 metadata = MetadataUtils.getRemoteMetadata(revision, instances, metadataReport);
21 
22                 if (metadata != MetadataInfo.EMPTY) {// succeeded
23                  //前面RPC请求元数据成功接下来开始初始化
24                     metadata.init();
25                     break;
26                 } else {// failed
27                     if (triedTimes > 0) {
28                         if (logger.isDebugEnabled()) {
29                             logger.debug("Retry the " + triedTimes + " times to get metadata for revision=" + revision);
30                         }
31                     }
32                     triedTimes++;
33                     try {
34                         Thread.sleep(1000);
35                     } catch (InterruptedException e) {
36                     }
37                 }
38             }
39 
40             if (metadata == MetadataInfo.EMPTY) {
41                 logger.error("Failed to get metadata for revision after 3 retries, revision=" + revision);
42             } else {
43                 //缓存查询到的元数据到元数据缓存管理器中
44                 metaCacheManager.put(revision, metadata);
45             }
46         }
47         return metadata;
48     }

1.7.2 MetadataUtils类型的getRemoteMetadata方法:

 1 public static MetadataInfo getRemoteMetadata(String revision, List<ServiceInstance> instances, MetadataReport metadataReport) {
 2        //随机轮训一台主机查询它的应用实例信息
 3         ServiceInstance instance = selectInstance(instances);
 4         //元数据提供者存储的位置默认为本地存储local 消费者从提供者那里拿,
 5         //remote - Provider 把 metadata 放到远端注册中心,Consumer 从注册中心获取;
 6         //local - Provider 把 metadata 放在本地,Consumer 从 Provider 处直接获取;
 7         //这个配置可以看链接:https://dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/config/properties/
 8         String metadataType = ServiceInstanceMetadataUtils.getMetadataStorageType(instance);
 9         MetadataInfo metadataInfo;
10         try {
11             if (logger.isDebugEnabled()) {
12                 logger.debug("Instance " + instance.getAddress() + " is using metadata type " + metadataType);
13             }
14 
15             //remote - Provider 把 metadata 放到远端注册中心,Consumer 从注册中心获取;
16             //local - Provider 把 metadata 放在本地,Consumer 从 Provider 处直接获取;
17             if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataType)) {
18                 //这里走的是remote配置
19                 metadataInfo = MetadataUtils.getMetadata(revision, instance, metadataReport);
20             } else {
21                 // change the instance used to communicate to avoid all requests route to the same instance
22                 ProxyHolder proxyHolder = null;
23                 try {
24                     //手动调用服务提供者内置的MetadataService Dubbo服务
25                     proxyHolder = MetadataUtils.referProxy(instance);
26                     //发起RPC调用 调用提供者的提供的元数据请求RPC接口
27                     metadataInfo = proxyHolder.getProxy().getMetadataInfo(ServiceInstanceMetadataUtils.getExportedServicesRevision(instance));
28                 } finally {
29                     MetadataUtils.destroyProxy(proxyHolder);
30                 }
31             }
32         } catch (Exception e) {
33             logger.error("Failed to get app metadata for revision " + revision + " for type " + metadataType + " from instance " + instance.getAddress(), e);
34             metadataInfo = null;
35         }
36 
37         if (metadataInfo == null) {
38             metadataInfo = MetadataInfo.EMPTY;
39         }
40         return metadataInfo;
41     }

1.7.3 MetadataUtils类型的referProxy

 1  public static ProxyHolder referProxy(ServiceInstance instance) {
 2       MetadataServiceURLBuilder builder;
 3       ExtensionLoader<MetadataServiceURLBuilder> loader = instance.getApplicationModel()
 4           .getExtensionLoader(MetadataServiceURLBuilder.class);
 5 
 6       Map<String, String> metadata = instance.getMetadata();
 7       // METADATA_SERVICE_URLS_PROPERTY_NAME is a unique key exists only on instances of spring-cloud-alibaba.
 8       String dubboUrlsForJson = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME);
 9       //
10       if (metadata.isEmpty() || StringUtils.isEmpty(dubboUrlsForJson)) {
11           builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
12       } else {
13           builder = loader.getExtension(SpringCloudMetadataServiceURLBuilder.NAME);
14       }
15       //默认的builder类型为StandardMetadataServiceURLBuilder 将远数据对象转url配置
16       //例如:dubbo://192.168.1.169:20880/org.apache.dubbo.metadata.MetadataService?connections=1&corethreads=2&dubbo=2.0.2&group=dubbo-demo-api-provider&port=20880&protocol=dubbo&release=3.0.10&retries=0&side=provider&threadpool=cached&threads=100&timeout=5000&version=1.0.0
17       List<URL> urls = builder.build(instance);
18       if (CollectionUtils.isEmpty(urls)) {
19           throw new IllegalStateException("Introspection service discovery mode is enabled "
20               + instance + ", but no metadata service can build from it.");
21       }
22 
23       URL url = urls.get(0);
24 
25       // Simply rely on the first metadata url, as stated in MetadataServiceURLBuilder.
26       ApplicationModel applicationModel = instance.getApplicationModel();
27       ModuleModel internalModel = applicationModel.getInternalModule();
28       ConsumerModel consumerModel = applicationModel.getInternalModule().registerInternalConsumer(MetadataService.class, url);
29 
30       Protocol protocol = applicationModel.getExtensionLoader(Protocol.class).getAdaptiveExtension();
31 
32       url.setServiceModel(consumerModel);
33       //!!!! 重点看这一行与普通的服务一样这里默认也是使用DubboProtocol来引用元数据服务
34       Invoker<MetadataService> invoker = protocol.refer(MetadataService.class, url);
35 
36       ProxyFactory proxyFactory = applicationModel.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
37 
38       //为其将要调用的invoker生成对应代理对象
39       MetadataService metadataService = proxyFactory.getProxy(invoker);
40 
41       consumerModel.getServiceMetadata().setTarget(metadataService);
42       consumerModel.getServiceMetadata().addAttribute(PROXY_CLASS_REF, metadataService);
43       consumerModel.setProxyObject(metadataService);
44       consumerModel.initMethodModels();
45 
46       return new ProxyHolder(consumerModel, metadataService, internalModel);
47   }

1.7.4 metadata的init方法初始化从提供者那里获取到的元数据

 1  public void init() {
 2         if (!initiated.compareAndSet(false, true)) {
 3             return;
 4         }
 5         if (CollectionUtils.isNotEmptyMap(services)) {
 6             //遍历所有的服务信息然后初始化 ServiceInfo
 7             services.forEach((_k, serviceInfo) -> {
 8                 serviceInfo.init();
 9                 // create duplicate serviceKey(without protocol)->serviceInfo mapping to support metadata search when protocol is not specified on consumer side.
10                 if (subscribedServices == null) {
11                     subscribedServices = new HashMap<>();
12                 }
13                 Set<ServiceInfo> serviceInfos = subscribedServices.computeIfAbsent(serviceInfo.getServiceKey(), _key -> new HashSet<>());
14                 serviceInfos.add(serviceInfo);
15             });
16         }
17     }

ServiceInfo类型的init方法

 1 protected void init() {
 2             //初始化matchKey变量 格式为:service + group + version + protocol
 3             buildMatchKey();
 4             //初始化服务keyserviceKey 格式为:service + group + version
 5             buildServiceKey(name, group, version);
 6             // init method params
 7             //初始化与方法匹配的参数
 8             this.methodParams = URLParam.initMethodParameters(params);
 9             // Actually, consumer params is empty after deserialized on the consumer side, so no need to initialize.
10             // Check how InstanceAddressURL operates on consumer url for more detail.
11 //            this.consumerMethodParams = URLParam.initMethodParameters(consumerParams);
12             // no need to init numbers for it's only for cache purpose
13         }

1.8 继续服务通知

1.8.1 ServiceDiscoveryRegistryDirectory接收订阅到的服务的通知方法:

 1  public synchronized void notify(List<URL> instanceUrls) {
 2         if (isDestroyed()) {
 3             return;
 4         }
 5         // Set the context of the address notification thread.
 6         RpcServiceContext.getServiceContext().setConsumerUrl(getConsumerUrl());
 7 
 8         //  3.x added for extend URL address
 9         ExtensionLoader<AddressListener> addressListenerExtensionLoader = getUrl().getOrDefaultModuleModel().getExtensionLoader(AddressListener.class);
10         List<AddressListener> supportedListeners = addressListenerExtensionLoader.getActivateExtension(getUrl(), (String[]) null);
11         if (supportedListeners != null && !supportedListeners.isEmpty()) {
12             for (AddressListener addressListener : supportedListeners) {
13                 instanceUrls = addressListener.notify(instanceUrls, getConsumerUrl(), this);
14             }
15         }
16 
17         refreshOverrideAndInvoker(instanceUrls);
18     }
1   private synchronized void refreshOverrideAndInvoker(List<URL> instanceUrls) {
2         // mock zookeeper://xxx?mock=return null
3         refreshInvoker(instanceUrls);
4     }

1.8.2 刷新调用器refreshInvoker

 1 private void refreshInvoker(List<URL> invokerUrls) {
 2         Assert.notNull(invokerUrls, "invokerUrls should not be null, use EMPTY url to clear current addresses.");
 3         this.originalUrls = invokerUrls;
 4 
 5         if (invokerUrls.size() == 1 && EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
 6             logger.warn("Received url with EMPTY protocol, will clear all available addresses.");
 7             this.forbidden = true; // Forbid to access
 8             routerChain.setInvokers(BitList.emptyList());
 9             destroyAllInvokers(); // Close all invokers
10         } else {
11             this.forbidden = false; // Allow accessing
12             if (CollectionUtils.isEmpty(invokerUrls)) {
13                 logger.warn("Received empty url list, will ignore for protection purpose.");
14                 return;
15             }
16 
17             // use local reference to avoid NPE as this.urlInvokerMap will be set null concurrently at destroyAllInvokers().
18             Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
19             // can't use local reference as oldUrlInvokerMap's mappings might be removed directly at toInvokers().
20             Map<String, Invoker<T>> oldUrlInvokerMap = null;
21             if (localUrlInvokerMap != null) {
22                 // the initial capacity should be set greater than the maximum number of entries divided by the load factor to avoid resizing.
23                 oldUrlInvokerMap = new LinkedHashMap<>(Math.round(1 + localUrlInvokerMap.size() / DEFAULT_HASHMAP_LOAD_FACTOR));
24                 localUrlInvokerMap.forEach(oldUrlInvokerMap::put);
25             }
26             //主要这一行做一些协议的过滤与实例禁用数据的过滤得到最终结果需要的调用器
27             Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(oldUrlInvokerMap, invokerUrls);// Translate url list to Invoker map
28             logger.info("Refreshed invoker size " + newUrlInvokerMap.size());
29 
30             if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
31                 logger.error(new IllegalStateException("Cannot create invokers from url address list (total " + invokerUrls.size() + ")"));
32                 return;
33             }
34             List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
35             this.setInvokers(multiGroup ? new BitList<>(toMergeInvokerList(newInvokers)) : new BitList<>(newInvokers));
36             // pre-route and build cache
37             routerChain.setInvokers(this.getInvokers());
38             this.urlInvokerMap = newUrlInvokerMap;
39 
40             if (oldUrlInvokerMap != null) {
41                 try {
42                     destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
43                 } catch (Exception e) {
44                     logger.warn("destroyUnusedInvokers error. ", e);
45                 }
46             }
47         }
48 
49         // notify invokers refreshed
50         this.invokersChanged();
51     }
 1 private Map<String, Invoker<T>> toInvokers(Map<String, Invoker<T>> oldUrlInvokerMap, List<URL> urls) {
 2         Map<String, Invoker<T>> newUrlInvokerMap = new ConcurrentHashMap<>(urls == null ? 1 : (int) (urls.size() / 0.75f + 1));
 3         if (urls == null || urls.isEmpty()) {
 4             return newUrlInvokerMap;
 5         }
 6         for (URL url : urls) {
 7             InstanceAddressURL instanceAddressURL = (InstanceAddressURL) url;
 8             if (EMPTY_PROTOCOL.equals(instanceAddressURL.getProtocol())) {
 9                 continue;
10             }
11             if (!getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).hasExtension(instanceAddressURL.getProtocol())) {
12                 logger.error(new IllegalStateException("Unsupported protocol " + instanceAddressURL.getProtocol() +
13                     " in notified url: " + instanceAddressURL + " from registry " + getUrl().getAddress() +
14                     " to consumer " + NetUtils.getLocalHost() + ", supported protocol: " +
15                     getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).getSupportedExtensions()));
16                 continue;
17             }
18 
19             instanceAddressURL.setProviderFirstParams(providerFirstParams);
20 
21             // Override provider urls if needed
22             if (enableConfigurationListen) {
23                 instanceAddressURL = overrideWithConfigurator(instanceAddressURL);
24             }
25 
26             Invoker<T> invoker = oldUrlInvokerMap == null ? null : oldUrlInvokerMap.get(instanceAddressURL.getAddress());
27             if (invoker == null || urlChanged(invoker, instanceAddressURL)) { // Not in the cache, refer again
28                 try {
29                     boolean enabled = true;
30                     if (instanceAddressURL.hasParameter(DISABLED_KEY)) {
31                         enabled = !instanceAddressURL.getParameter(DISABLED_KEY, false);
32                     } else {
33                         enabled = instanceAddressURL.getParameter(ENABLED_KEY, true);
34                     }
35                     if (enabled) {
36                         invoker = protocol.refer(serviceType, instanceAddressURL);
37                     }
38                 } catch (Throwable t) {
39                     logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + instanceAddressURL + ")" + t.getMessage(), t);
40                 }
41                 if (invoker != null) { // Put new invoker in cache
42                     newUrlInvokerMap.put(instanceAddressURL.getAddress(), invoker);
43                 }
44             } else {
45                 newUrlInvokerMap.put(instanceAddressURL.getAddress(), invoker);
46                 oldUrlInvokerMap.remove(instanceAddressURL.getAddress(), invoker);
47             }
48         }
49         return newUrlInvokerMap;
50     }

这里的逻辑基本与接口级服务引用一样了感兴趣可以看接口级服务引用的逻辑

 

posted @ 2022-12-15 15:38  Boblim  阅读(653)  评论(0编辑  收藏  举报