【Spring Cloud】Eureka缓存机制

spring,cloud,eureka,缓存,机制 · 浏览次数 : 286

小编点评

**内容简介** 本文介绍了Eureka缓存机制的原理,包括如何缓存数据、如何处理缓存刷新事件、如何优化服务性能等。 **主要内容** * **Eureka 缓存机制** * 缓存数据如何被缓存 * 如何处理缓存刷新事件 * 如何优化服务性能 * **缓存刷新事件** * 如何定义缓存刷新事件 * 如何处理缓存刷新事件 * 如何更新InstanceRemoteStatus **代码分析** **CacheRefreshThread**类中的 **fetchRegistry**方法** * 使用 Stopwatch 计时器开始缓存刷新事件 * 尝试获取应用的注册应用程序数量 * 如果应用没有注册应用程序,将缓存刷新事件设置为 false * 缓存刷新成功后,更新 InstanceRemoteStatus * 返回缓存刷新结果 **其他** * 缓存机制的优缺点 * 如何使用 Eureka 缓存机制优化服务性能 * Eureka 缓存机制的最佳实践

正文

Eureka分为Client端和Server端,Client端向Server端注册自己的服务信息,并且拉取所有服务的注册信息,Server端作为注册中心,负责接收Client端的注册信息,维护所有服务的注册信息,Server端也可以开启集群模式,相互之间同步服务的注册信息。

与缓存相关的三个变量

1.registry
    private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry

AbstractInstanceRegistry的成员变量,相当于服务端的注册表,维护所有服务的注册信息,第一层Map的key是服务的应用名称,value也是一个map,其中key是实例id,value是实例的详细信息(Lease)。

2.readWriteCacheMap
   private final LoadingCache<Key, ResponseCacheImpl.Value> readWriteCacheMap;

ResponseCacheImpl的成员变量,使用的是guava的LoadingCache构建的缓存,其中key为com.netflix.eureka.registry包下的Key对象,value是ResponseCacheImpl的内部类Value对象,从名称上可以看出它是一个读写缓存。

3.readOnlyCacheMap
    private final ConcurrentMap<Key, ResponseCacheImpl.Value> readOnlyCacheMap = new ConcurrentHashMap();

ResponseCacheImpl的成员变量,同样是一个ConcurrentMap,从名称上可以看出它是一个只读的缓存对象。

所以Eureka Server使用了一个读写分离的两级缓存机制,registry负责维护所有服务的注册信息,当register数据有变化,将会更新readWriteCacheMap的内容,后台开启定时任务,默认30s从readWriteCacheMap同步数据到readOnlyCacheMap,Eureka Client拉取服务的注册信息时,从readOnlyCacheMap读取数据,并没有直接从readWriteCacheMap中获取数据:

Client从Server端拉取数据流程:

(1)Client端DiscoveryClient的构造函数中,会初始化定时任务;

(2)缓存刷新任务,定时发送请求到Server端,拉取服务的注册数据,Server端收到请求,首先会根据请求信息构建key,根据key从缓存中获取数据;

(3)Server端根据key获取缓存数据时,会判断是否开启了使用readOnly缓存:

如果开启:先从readOnlyCacheMap获取,如果从readOnlyCacheMap未获取到数据,再从readWriteCacheMap中查找;

如果未开启:直接从readWriteCacheMap中获取;

(4)从readWriteCacheMap获取数据也有两种结果:

第一:获取到数据,如果开启了readOnly缓存,会将结果设置到readOnlyCacheMap中,否则直接返回结果即可;

第二:未获取到数据,由于readWriteCacheMap使用的是guava构建的缓存,从readWriteCacheMap根据key获取数据,假如key不存在的活,就会触发load方法,在这个方法中会调用generatePayload方法从registry中获取数据构并建缓存数据返回;

(5)Server端将结果设置到response返回到Client端;

源码

Eureka Server

AbstractInstanceRegistry

以register服务注册方法看一下缓存更新的流程,在register方法中,服务进行注册的时候,会根据服务的应用名称appName,调用invalidateCache方法清除之前存在的缓存。

public abstract class AbstractInstanceRegistry implements InstanceRegistry{
    
    // server端的注册表,key是服务的应用名称,value又是一个map, key是实例id,value是实例的详细信息(Lease<InstanceInfo>)
    private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap();
    
    ...
    
    // 注册服务
    public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
        try {
            this.read.lock();
            // 根据应用名称从注册表中获取实例
            Map<String, Lease<InstanceInfo>> gMap = (Map)this.registry.get(registrant.getAppName());
            EurekaMonitors.REGISTER.increment(isReplication);
            // 如果获取的实例为空
            if(gMap == null) {
                // 创建一个hashmap
                ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap();
                // 加入注册表
                gMap = (Map)this.registry.putIfAbsent(registrant.getAppName(), gNewMap);
                if(gMap == null) {
                    gMap = gNewMap;
                }
            }
            // 根据实例ID获取,实例的详细信息
            Lease<InstanceInfo> existingLease = (Lease)((Map)gMap).get(registrant.getId());
            
            ...
            
            registrant.setActionType(ActionType.ADDED);
            this.recentlyChangedQueue.add(new AbstractInstanceRegistry.RecentlyChangedItem(lease));
            registrant.setLastUpdatedTimestamp();
            // 清除缓存
            this.invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
            logger.info("Registered instance {}/{} with status {} (replication={})", new Object[]{registrant.getAppName(), registrant.getId(), registrant.getStatus(), Boolean.valueOf(isReplication)});
        } finally {
            this.read.unlock();
        }

    }
    
    // 清除缓存
    private void invalidateCache(String appName, @Nullable String vipAddress, @Nullable String secureVipAddress) {
        this.responseCache.invalidate(appName, vipAddress, secureVipAddress);
    }

}

ResponseCacheImpl

(1)ResponseCacheImpl中包含了readOnlyCacheMap只读缓存对象和readWriteCacheMap读写缓存对象,其中readWriteCacheMap使用的是guava的缓存机制,如果根据key从readWriteCacheMap获取数据时没有这个key,将会调用load方法,在load方法中是调用generatePayload构建Value对象的,在generatePayload是从registry获取数据的,也就是说当readWriteCacheMap不存在某个key是会从registry获取数据。

(2)在构造函数中,初始化了readWriteCacheMap对象,并判断是否使用readOnlyCacheMap缓存,如果开启,设置定时任务getCacheUpdateTask,定时从readWriteCacheMap同步数据到readOnlyCacheMap,同步方式是遍历readOnlyCacheMap,判断value是否与readWriteCacheMap的数据一致,如果不一致,以readWriteCacheMap的数据为准,更新readOnlyCacheMap的数据。

(3)invalidate方法用来清除readWriteCacheMap中的缓存.

public class ResponseCacheImpl implements ResponseCache {
    ...
    // readOnlyCacheMap缓存
    private final ConcurrentMap<Key, ResponseCacheImpl.Value> readOnlyCacheMap = new ConcurrentHashMap();
    // guava构建的readWriteCacheMap缓存
    private final LoadingCache<Key, ResponseCacheImpl.Value> readWriteCacheMap;
    ...
    
    // 构造函数
    ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
        this.serverConfig = serverConfig;
        this.serverCodecs = serverCodecs;
        // 是否使用只读缓存
        this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();
        this.registry = registry;
        // 缓存更新间隔
        long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
        // 构建缓存对象,初始容量为1000
        this.readWriteCacheMap = CacheBuilder.newBuilder().initialCapacity(1000).expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS).removalListener(new RemovalListener<Key, ResponseCacheImpl.Value>() {
            public void onRemoval(RemovalNotification<Key, ResponseCacheImpl.Value> notification) {
                Key removedKey = (Key)notification.getKey();
                if(removedKey.hasRegions()) {
                    Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
                    ResponseCacheImpl.this.regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
                }

            }
        }).build(new CacheLoader<Key, ResponseCacheImpl.Value>() {
            // 如果缓存中key不存在,会调用load方法
            public ResponseCacheImpl.Value load(Key key) throws Exception {
                if(key.hasRegions()) {
                    Key cloneWithNoRegions = key.cloneWithoutRegions();
                    ResponseCacheImpl.this.regionSpecificKeys.put(cloneWithNoRegions, key);
                }
                // 根据key构建缓存数据
                ResponseCacheImpl.Value value = ResponseCacheImpl.this.generatePayload(key);
                return value;
            }
        });
        // 如果使用只读缓存
        if(this.shouldUseReadOnlyResponseCache) {
            // 注册定时任务,同步数据
            this.timer.schedule(this.getCacheUpdateTask(), new Date(System.currentTimeMillis() / responseCacheUpdateIntervalMs * responseCacheUpdateIntervalMs + responseCacheUpdateIntervalMs), responseCacheUpdateIntervalMs);
        }

        try {
            Monitors.registerObject(this);
        } catch (Throwable var7) {
            logger.warn("Cannot register the JMX monitor for the InstanceRegistry", var7);
        }

    }
    
    // 清除缓存
    public void invalidate(String appName, @Nullable String vipAddress, @Nullable String secureVipAddress) {
        KeyType[] var4 = KeyType.values();
        int var5 = var4.length;

        for(int var6 = 0; var6 < var5; ++var6) {
            KeyType type = var4[var6];
            Version[] var8 = Version.values();
            int var9 = var8.length;

            for(int var10 = 0; var10 < var9; ++var10) {
                Version v = var8[var10];
                // 实际上调用的invalidate(Key... keys)方法
                this.invalidate(new Key[]{new Key(EntityType.Application, appName, type, v, EurekaAccept.full), new Key(EntityType.Application, appName, type, v, EurekaAccept.compact), new Key(EntityType.Application, "ALL_APPS", type, v, EurekaAccept.full), new Key(EntityType.Application, "ALL_APPS", type, v, EurekaAccept.compact), new Key(EntityType.Application, "ALL_APPS_DELTA", type, v, EurekaAccept.full), new Key(EntityType.Application, "ALL_APPS_DELTA", type, v, EurekaAccept.compact)});
                if(null != vipAddress) {
                    this.invalidate(new Key[]{new Key(EntityType.VIP, vipAddress, type, v, EurekaAccept.full)});
                }

                if(null != secureVipAddress) {
                    this.invalidate(new Key[]{new Key(EntityType.SVIP, secureVipAddress, type, v, EurekaAccept.full)});
                }
            }
        }

    }

    public void invalidate(Key... keys) {
        Key[] var2 = keys;
        int var3 = keys.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            Key key = var2[var4];
            logger.debug("Invalidating the response cache key : {} {} {} {}, {}", new Object[]{key.getEntityType(), key.getName(), key.getVersion(), key.getType(), key.getEurekaAccept()});
            // 根据key清除缓存
            this.readWriteCacheMap.invalidate(key);
            Collection<Key> keysWithRegions = this.regionSpecificKeys.get(key);
            if(null != keysWithRegions && !keysWithRegions.isEmpty()) {
                Iterator var7 = keysWithRegions.iterator();

                while(var7.hasNext()) {
                    Key keysWithRegion = (Key)var7.next();
                    logger.debug("Invalidating the response cache key : {} {} {} {} {}", new Object[]{key.getEntityType(), key.getName(), key.getVersion(), key.getType(), key.getEurekaAccept()});
                    this.readWriteCacheMap.invalidate(keysWithRegion);
                }
            }
        }

    }
    
    // 定时任务,从readWriteCacheMap同步数据到readOnlyCacheMap
    private TimerTask getCacheUpdateTask() {
        return new TimerTask() {
            public void run() {
                ResponseCacheImpl.logger.debug("Updating the client cache from response cache");
                Iterator var1 = ResponseCacheImpl.this.readOnlyCacheMap.keySet().iterator();
                // 遍历readOnlyCacheMap
                while(var1.hasNext()) {
                    Key key = (Key)var1.next();
                    if(ResponseCacheImpl.logger.isDebugEnabled()) {
                        ResponseCacheImpl.logger.debug("Updating the client cache from response cache for key : {} {} {} {}", new Object[]{key.getEntityType(), key.getName(), key.getVersion(), key.getType()});
                    }

                    try {
                        CurrentRequestVersion.set(key.getVersion());
                        // 从readWriteCacheMap根据key获取数据
                        ResponseCacheImpl.Value cacheValue = (ResponseCacheImpl.Value)ResponseCacheImpl.this.readWriteCacheMap.get(key);
                        // 从readOnlyCacheMap获取数据
                        ResponseCacheImpl.Value currentCacheValue = (ResponseCacheImpl.Value)ResponseCacheImpl.this.readOnlyCacheMap.get(key);
                        // 对比数据是否一致
                        if(cacheValue != currentCacheValue) {
                           // 如果数据不一致,更新readOnlyCacheMap的数据 ResponseCacheImpl.this.readOnlyCacheMap.put(key, cacheValue);
                        }
                    } catch (Throwable var5) {
                        ResponseCacheImpl.logger.error("Error while updating the client cache from response cache for key {}", key.toStringCompact(), var5);
                    }
                }

            }
        };
    }
    
    // 根据key生成ResponseCacheImpl.Value对象
    private ResponseCacheImpl.Value generatePayload(Key key) {
        Stopwatch tracer = null;

        ResponseCacheImpl.Value var8;
        try {
            String payload;
            switch(null.$SwitchMap$com$netflix$eureka$registry$Key$EntityType[key.getEntityType().ordinal()]) {
            case 1:
                boolean isRemoteRegionRequested = key.hasRegions();
                // 全量获取
                if("ALL_APPS".equals(key.getName())) {
                    if(isRemoteRegionRequested) {
                        tracer = this.serializeAllAppsWithRemoteRegionTimer.start();
                        payload = this.getPayLoad(key, this.registry.getApplicationsFromMultipleRegions(key.getRegions()));
                    } else {
                        tracer = this.serializeAllAppsTimer.start();
                        // 从registry获取数据
                        payload = this.getPayLoad(key, this.registry.getApplications());
                    }
                } else if("ALL_APPS_DELTA".equals(key.getName())) {// 增量获取
                    if(isRemoteRegionRequested) {
                        tracer = this.serializeDeltaAppsWithRemoteRegionTimer.start();
                        this.versionDeltaWithRegions.incrementAndGet();
                        versionDeltaWithRegionsLegacy.incrementAndGet();
                        payload = this.getPayLoad(key, this.registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));
                    } else {
                        tracer = this.serializeDeltaAppsTimer.start();
                        this.versionDelta.incrementAndGet();
                        versionDeltaLegacy.incrementAndGet();
                        payload = this.getPayLoad(key, this.registry.getApplicationDeltas());
                    }
                } else {
                    tracer = this.serializeOneApptimer.start();
                    payload = this.getPayLoad(key, this.registry.getApplication(key.getName()));
                }
                break;
            case 2:
            case 3:
                tracer = this.serializeViptimer.start();
                payload = this.getPayLoad(key, getApplicationsForVip(key, this.registry));
                break;
            default:
                logger.error("Unidentified entity type: {} found in the cache key.", key.getEntityType());
                payload = "";
            }

            var8 = new ResponseCacheImpl.Value(payload);
        } finally {
            if(tracer != null) {
                tracer.stop();
            }

        }

        return var8;
    }
}

ApplicationsResource

ApplicationsResource的getContainers接收Client端的拉取请求,在getContainers方法中,首先会根据请求信息构建缓存Key,根据key从缓存中获取数据,并设置到response中返回给Client端。

public class ApplicationsResource {
    @GET
    public Response getContainers(@PathParam("version") String version, @HeaderParam("Accept") String acceptHeader, @HeaderParam("Accept-Encoding") String acceptEncoding, @HeaderParam("X-Eureka-Accept") String eurekaAccept, @Context UriInfo uriInfo, @Nullable @QueryParam("regions") String regionsStr) {
        boolean isRemoteRegionRequested = null != regionsStr && !regionsStr.isEmpty();
        String[] regions = null;
        if(!isRemoteRegionRequested) {
            EurekaMonitors.GET_ALL.increment();
        } else {
            regions = regionsStr.toLowerCase().split(",");
            Arrays.sort(regions);
            EurekaMonitors.GET_ALL_WITH_REMOTE_REGIONS.increment();
        }

        if(!this.registry.shouldAllowAccess(isRemoteRegionRequested)) {
            return Response.status(Status.FORBIDDEN).build();
        } else {
            CurrentRequestVersion.set(Version.toEnum(version));
            KeyType keyType = KeyType.JSON;
            String returnMediaType = "application/json";
            if(acceptHeader == null || !acceptHeader.contains("json")) {
                keyType = KeyType.XML;
                returnMediaType = "application/xml";
            }
            // 构建缓存key
            Key cacheKey = new Key(EntityType.Application, "ALL_APPS", keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions);
            Response response;
            // 如果acceptEncoding是gizp
            if(acceptEncoding != null && acceptEncoding.contains("gzip")) {
                response = Response.ok(this.responseCache.getGZIP(cacheKey)).header("Content-Encoding", "gzip").header("Content-Type", returnMediaType).build();
            } else {
                // 从缓存中根据key获取数据设置到response中
                response = Response.ok(this.responseCache.get(cacheKey)).build();
            }
            return response;
        }
    }
}

回到ResponseCacheImpl,get方法用来根据key从缓存中获取数据,最终调用的是getValue方法,在getValue方法中可以看到,如果开启了只读缓存,先从readOnlyCacheMap中获取数据,如果未获取到再从readWriteCacheMap中获取,如果未开启只读缓存,直接从readWriteCacheMap中获取,如果readWriteCacheMap不存在某个key,可以回看ResponseCacheImpl构造函数初始化readWriteCacheMap时有一个load方法,不存在某个key时触发该方法,实际上是去registry中取数据并构建缓存数据:

public class ResponseCacheImpl implements ResponseCache {
    ...
    // 根据keky获取缓存数据
    public String get(Key key) {
        return this.get(key, this.shouldUseReadOnlyResponseCache);
    }

    @VisibleForTesting
    String get(Key key, boolean useReadOnlyCache) {
        // 根据key从缓存map中获取value
        ResponseCacheImpl.Value payload = this.getValue(key, useReadOnlyCache);
        return payload != null && !payload.getPayload().equals("")?payload.getPayload():null;
    }
    
     @VisibleForTesting
    ResponseCacheImpl.Value getValue(Key key, boolean useReadOnlyCache) {
        ResponseCacheImpl.Value payload = null;

        try {
            // 如果使用ReadOnlyCache
            if(useReadOnlyCache) {
                // 从ReadOnlyCache获取缓存数据
                ResponseCacheImpl.Value currentPayload = (ResponseCacheImpl.Value)this.readOnlyCacheMap.get(key);
                // 如果获取不为空
                if(currentPayload != null) {
                    payload = currentPayload;
                } else {
                    // 如果获取为空,从readWriteCacheMap中获取数据
                    payload = (ResponseCacheImpl.Value)this.readWriteCacheMap.get(key);
                    // 将获取的数据加入readOnlyCacheMap中
                    this.readOnlyCacheMap.put(key, payload);
                }
            } else {
                // 如果不使用readOnlyCacheMap,直接从readWriteCacheMap获取数据
                payload = (ResponseCacheImpl.Value)this.readWriteCacheMap.get(key);
            }
        } catch (Throwable var5) {
            logger.error("Cannot get value for key : {}", key, var5);
        }

        return payload;
    }
}

Eureka Client

DiscoveryClient

(1)在DiscoveryClient的构造方法中,会判断是否需要拉取数据,并且初始化定时任务;

(2)初始化定时任务方法中,会添加缓存更新的定时任务,是在CacheRefreshThread的run方法中实现的,在run方法中调用refreshRegistry刷新缓存,在refreshRegistry方法中又调用了fetchRegistry,所以最终使用的fetchRegistry方法从服务端拉取数据;

(3)fetchRegistry方法中会判断是全量还是增量从Server端拉取数据;

public class DiscoveryClient implements EurekaClient {

     DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider) {
        ...
        // 如果需要拉取数据
        if(this.clientConfig.shouldFetchRegistry() && !this.fetchRegistry(false)) {
            // 从备用服务拉取数据
            this.fetchRegistryFromBackup();
        }
        ...
        
        // 初始化定时任务
        this.initScheduledTasks();
        
        ...
    }
    
    // 初始化定时任务
    private void initScheduledTasks() {
        int renewalIntervalInSecs;
        int expBackOffBound;
        // 是否需要拉取数据
        if(this.clientConfig.shouldFetchRegistry()) {
            renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds();
            expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            // 注册刷新缓存的定时任务
            this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
        }

        if(this.clientConfig.shouldRegisterWithEureka()) {
            renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: renew interval is: {}", Integer.valueOf(renewalIntervalInSecs));
            // 注册发送心跳的定时任务
            this.scheduler.schedule(new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread(null)), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
            // 构建InstanceInfoReplicator,其run方法中会调用discoveryClient.register()发送HTTP请求进行服务注册
            this.instanceInfoReplicator = new InstanceInfoReplicator(this, this.instanceInfo, this.clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2);
            this.statusChangeListener = new StatusChangeListener() {
                public String getId() {
                    return "statusChangeListener";
                }

                public void notify(StatusChangeEvent statusChangeEvent) {
                    if(InstanceStatus.DOWN != statusChangeEvent.getStatus() && InstanceStatus.DOWN != statusChangeEvent.getPreviousStatus()) {
                        DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent);
                    } else {
                        DiscoveryClient.logger.warn("Saw local status change event {}", statusChangeEvent);
                    }

                    DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();
                }
            };
            if(this.clientConfig.shouldOnDemandUpdateStatusChange()) {
                this.applicationInfoManager.registerStatusChangeListener(this.statusChangeListener);
            }

            this.instanceInfoReplicator.start(this.clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }

    }
    
    // 缓存刷新任务
    class CacheRefreshThread implements Runnable {
        CacheRefreshThread() {
        }

        public void run() {
            // 调用refreshRegistry刷新缓存
            DiscoveryClient.this.refreshRegistry();
        }
    }
    
    @VisibleForTesting
    void refreshRegistry() { 
        try {
            ...
            
            // 调用fetchRegistry从服务端拉取数据
            boolean success = this.fetchRegistry(remoteRegionsModified);
            if(success) {
                this.registrySize = ((Applications)this.localRegionApps.get()).size();
                this.lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
            }

            ...
        } catch (Throwable var9) {
            logger.error("Cannot fetch registry from server", var9);
        }

    }
    
    // 拉取数据
    private boolean fetchRegistry(boolean forceFullRegistryFetch) {
        Stopwatch tracer = this.FETCH_REGISTRY_TIMER.start();

        label122: {
            boolean var4;
            try {
                Applications applications = this.getApplications();
                if(!this.clientConfig.shouldDisableDelta() && Strings.isNullOrEmpty(this.clientConfig.getRegistryRefreshSingleVipAddress()) && !forceFullRegistryFetch && applications != null && applications.getRegisteredApplications().size() != 0 && applications.getVersion().longValue() != -1L) {
                    // 增量拉取数据
                    this.getAndUpdateDelta(applications);
                } else {
                    logger.info("Disable delta property : {}", Boolean.valueOf(this.clientConfig.shouldDisableDelta()));
                    logger.info("Single vip registry refresh property : {}", this.clientConfig.getRegistryRefreshSingleVipAddress());
                    logger.info("Force full registry fetch : {}", Boolean.valueOf(forceFullRegistryFetch));
                    logger.info("Application is null : {}", Boolean.valueOf(applications == null));
                    logger.info("Registered Applications size is zero : {}", Boolean.valueOf(applications.getRegisteredApplications().size() == 0));
                    logger.info("Application version is -1: {}", Boolean.valueOf(applications.getVersion().longValue() == -1L));
                    // 全量拉取数据
                    this.getAndStoreFullRegistry();
                }

                applications.setAppsHashCode(applications.getReconcileHashCode());
                this.logTotalInstances();
                break label122;
            } catch (Throwable var8) {
                logger.error("DiscoveryClient_{} - was unable to refresh its cache! status = {}", new Object[]{this.appPathIdentifier, var8.getMessage(), var8});
                var4 = false;
            } finally {
                if(tracer != null) {
                    tracer.stop();
                }

            }

            return var4;
        }
        // 缓存刷新事件
        this.onCacheRefreshed();
        this.updateInstanceRemoteStatus();
        return true;
    }
}

参考:

sharedCode:深入理解Eureka缓存机制(八)

[xmz_java:Eureka 缓存结构以及服务感知优化](https://www.cnblogs.com/xmzJava/p/11359636.html)

[宜信技术:详解Eureka 缓存机制](https://www.cnblogs.com/yixinjishu/p/10871243.html)

Spring Cloud版本:Finchley.RELEASE

与【Spring Cloud】Eureka缓存机制相似的内容:

【Spring Cloud】Eureka缓存机制

Eureka分为Client端和Server端,Client端向Server端注册自己的服务信息,并且拉取所有服务的注册信息,Server端作为注册中心,负责接收Client端的注册信息,维护所有服务的注册信息,Server端也可以开启集群模式,相互之间同步服务的注册信息。 与缓存相关的三个变量 1

[转帖]k8s发布Spring cloud+eureka架构服务优雅启动停止方案

本文转载自昆仑枫的简书https://www.jianshu.com/p/6d393cbb694a Spring cloud+eureka是目前微服务主流解决方案之一,kubernetes则是广泛应用的发布工具,两者结合使用很常见。而两者结合时如何优雅启停从而实现无感发布很关键。下面将从不做特殊处理

Spring Cloud 部署时如何使用 Kubernetes 作为注册中心和配置中心

一、Spring Cloud 支持的常见注册中心和配置中心。 Spring Cloud 自带的注册中心Eureka以及config配置中心 Nacos,支持注册中心和配置中心等,可以参考:https://www.cnblogs.com/laoqing/p/17797759.html Zookeepe

Eureka Server 实现在线扩容

Eureka Server 实现在线扩容 作者:Grey 原文地址: 博客园:Eureka Server 实现在线扩容 CSDN:Eureka Server 实现在线扩容 需求 Eureka 是 Spring Cloud Netflix 套件中的服务注册中心组件,作为微服务的核心组件,需要支持在线扩

SpringCloud搭建保姆级教程

一、搭建服务注册与发现中⼼ 使⽤Spring Cloud Netflix 中的 Eureka 搭建服务注册与发现中⼼ 1、创建SpringBoot应用添加依赖 1、spring web 2、eureka server 2、配置服务注册与发现中⼼ ## 设置服务注册与发现中⼼的端⼝ server: p

Spring Cloud微服务核心架构分析

Spring Cloud是一个相对比较成熟的微服务框架。虽然,Spring Cloud于2016年才推出1.0的release版本, 时间最短, 但是相比Dubbo等RPC框架, Spring Cloud提供的全套的分布式系统解决方案。 Spring Cloud是一系列框架的有序集合。它利用Spri

Spring Cloud微服务下如何配置I8n

什么是I8n 国际化(I18n)指的是设计和开发产品的过程,使得它们能够适应多种语言和文化环境,而不需要进行大量的代码更改。这通常涉及到创建一个基础版本的产品,然后通过配置和资源文件来添加对不同语言和地区的支持。 这样,当产品需要在新的地理区域或语言环境中使用时,只需要添加或更新相应的资源文件,而不

spring cloud 上云的情况下,Ribbon 客户端负载均衡 与 ALB 服务端负载均衡的选择

在云环境(例如AWS)中,由于云提供商通常提供强大的负载均衡服务(如AWS的ALB),一般不再需要使用Ribbon这种客户端负载均衡方案。云环境中的负载均衡器通常能够提供更高的可靠性、可扩展性和简化的配置,因此在上云的情况下,使用云提供的负载均衡器是更优的选择。 理由分析 云提供的负载均衡服务(如A

聊聊Spring Cloud Alibaba Sentinel的限流

Spring Cloud Alibaba Sentinel限流功能概览,目前先整理一版,东西有点多,想慢慢打开;后续继续更新......

聊聊Spring Cloud Gateway

Spring Cloud Gateway是基于Spring Boot 2.0、Spring WebFlux和Project Reactor等技术开发的网关,它不仅提供了统一的路由请求的方式,还基于过滤链的方式提供了网关最基本的功能;解决了Spring Cloud Zuul的性能问题。