AcceptMediocrity commented on issue #16375:
URL: https://github.com/apache/dubbo/issues/16375#issuecomment-5407449068

   > [@AcceptMediocrity](https://github.com/AcceptMediocrity) hello bro. So 
far, the cause of the problem has been identified as follows:
   > 
   > After a Pod in K8s is restarted, the IP address of the service instance 
changes. However, in some cases, the Dubbo consumer still retains the old Pod 
IP address, causing attempts to connect to an no longer existent address to 
continue. The specific trigger point lies in the service discovery notification 
mechanism: when Nacos/K8s sends a service discovery notification, 
ServiceInstancesChangedListener receives the list of new instances. The 
listener then retrieves the metadata based on the revision of each instance. If 
all versions of the metadata are failed to be retrieved during a notification, 
the old logic will directly return. This premature return prevents further 
notifications from being sent to downstream components. Although the downstream 
ServiceDiscoveryRegistryDirectory has the capability to clean up unused 
invokers using destroyUnusedInvokers(), it doesn’t receive any notifications 
indicating that the address has changed or been replaced. Therefore, the old IP 
addr
 ess remains in use.
   > 
   > **The solution** is to remove the premature return of 
ServiceInstancesChangedListener when “all metadata is empty/failed”. This means 
that errors are still recorded, retries are still attempted, but the process 
continues with the reconstruction of service URLs and notification of changes 
in the address. In this way, even if the metadata retrieval fails temporarily, 
the status of the currently resolvable address is still notified. For the old 
service addresses, subsequent directory refreshes will return either an empty 
or new address, thereby triggering the elimination of the old invoker, and 
preventing continued connection to the old Pod IP address.
   
   Dubbo:3.2.13
   
org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener#doOnEvent
   
    private synchronized void doOnEvent(ServiceInstancesChangedEvent event) {
           if (destroyed.get() || !accept(event) || isRetryAndExpired(event)) {
               return;
           }
   
           refreshInstance(event);
   
           if (logger.isDebugEnabled()) {
               logger.debug(event.getServiceInstances().toString());
           }
   
           Map<String, List<ServiceInstance>> revisionToInstances = new 
HashMap<>();
           Map<ServiceInfo, Set<String>> localServiceToRevisions = new 
HashMap<>();
   
           // grouping all instances of this app(service name) by revision
           for (Map.Entry<String, List<ServiceInstance>> entry : 
allInstances.entrySet()) {
               List<ServiceInstance> instances = entry.getValue();
               for (ServiceInstance instance : instances) {
                   String revision = getExportedServicesRevision(instance);
                   if (revision == null || EMPTY_REVISION.equals(revision)) {
                       if (logger.isDebugEnabled()) {
                           logger.debug("Find instance without valid service 
metadata: " + instance.getAddress());
                       }
                       continue;
                   }
                   List<ServiceInstance> subInstances =
                           revisionToInstances.computeIfAbsent(revision, r -> 
new LinkedList<>());
                   subInstances.add(instance);
               }
           }
   
           // get MetadataInfo with revision
           for (Map.Entry<String, List<ServiceInstance>> entry : 
revisionToInstances.entrySet()) {
               String revision = entry.getKey();
               List<ServiceInstance> subInstances = entry.getValue();
   
               MetadataInfo metadata = subInstances.stream()
                       .map(ServiceInstance::getServiceMetadata)
                       .filter(Objects::nonNull)
                       .filter(m -> revision.equals(m.getRevision()))
                       .findFirst()
                       .orElseGet(() -> 
serviceDiscovery.getRemoteMetadata(revision, subInstances));
   
               parseMetadata(revision, metadata, localServiceToRevisions);
               // update metadata into each instance, in case new instance 
created.
               for (ServiceInstance tmpInstance : subInstances) {
                   MetadataInfo originMetadata = 
tmpInstance.getServiceMetadata();
                   if (originMetadata == null || 
!Objects.equals(originMetadata.getRevision(), metadata.getRevision())) {
                       tmpInstance.setServiceMetadata(metadata);
                   }
               }
           }
   
           int emptyNum = hasEmptyMetadata(revisionToInstances);
           if (emptyNum != 0) { // retry every 10 seconds
               hasEmptyMetadata = true;
               if (retryPermission.tryAcquire()) {
                   if (retryFuture != null && !retryFuture.isDone()) {
                       // cancel last retryFuture because only one retryFuture 
will be canceled at destroy().
                       retryFuture.cancel(true);
                   }
                   try {
                       retryFuture = scheduler.schedule(
                               new AddressRefreshRetryTask(retryPermission, 
event.getServiceName()),
                               10_000L,
                               TimeUnit.MILLISECONDS);
                   } catch (Exception e) {
                       logger.error(
                               INTERNAL_ERROR,
                               "unknown error in registry module",
                               "",
                               "Error submitting async retry task.");
                   }
                   logger.warn(
                           INTERNAL_ERROR, "unknown error in registry module", 
"", "Address refresh try task submitted");
               }
   
               // return if all metadata is empty, this notification will not 
take effect.
               if (emptyNum == revisionToInstances.size()) {
                   // 1-17 - Address refresh failed.
                   logger.error(
                           REGISTRY_FAILED_REFRESH_ADDRESS,
                           "metadata Server failure",
                           "",
                           "Address refresh failed because of Metadata Server 
failure, wait for retry or new address refresh event.");
   
                   return;
               }
           }
           hasEmptyMetadata = false;
   
           Map<String, Map<Integer, Map<Set<String>, Object>>> 
protocolRevisionsToUrls = new HashMap<>();
           Map<String, List<ProtocolServiceKeyWithUrls>> newServiceUrls = new 
HashMap<>();
           for (Map.Entry<ServiceInfo, Set<String>> entry : 
localServiceToRevisions.entrySet()) {
               ServiceInfo serviceInfo = entry.getKey();
               Set<String> revisions = entry.getValue();
   
               Map<Integer, Map<Set<String>, Object>> portToRevisions =
                       
protocolRevisionsToUrls.computeIfAbsent(serviceInfo.getProtocol(), k -> new 
HashMap<>());
               Map<Set<String>, Object> revisionsToUrls =
                       portToRevisions.computeIfAbsent(serviceInfo.getPort(), k 
-> new HashMap<>());
               Object urls = revisionsToUrls.computeIfAbsent(
                       revisions,
                       k -> getServiceUrlsCache(
                               revisionToInstances, revisions, 
serviceInfo.getProtocol(), serviceInfo.getPort()));
   
               List<ProtocolServiceKeyWithUrls> list =
                       newServiceUrls.computeIfAbsent(serviceInfo.getPath(), k 
-> new LinkedList<>());
               list.add(new 
ProtocolServiceKeyWithUrls(serviceInfo.getProtocolServiceKey(), (List<URL>) 
urls));
           }
   
           this.serviceUrls = newServiceUrls;
           this.notifyAddressChanged();
       }
   
   没有找到你改动的那块代码
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to